Skip to content
Open

v0.38 #1018

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
b1cdc2a
Bump minor version
penelopeysm Aug 8, 2025
f742103
Merge branch 'main' into breaking
penelopeysm Aug 10, 2025
5a9e9d2
bump benchmarks compat
penelopeysm Aug 10, 2025
f4db67a
Merge branch 'main' into breaking
penelopeysm Aug 13, 2025
7b55aa3
add a skeletal changelog
penelopeysm Aug 13, 2025
991e825
`InitContext`, part 3 - Introduce `InitContext` (#981)
penelopeysm Aug 13, 2025
2d18ce3
Merge branch 'main' into breaking
penelopeysm Aug 30, 2025
c8e5841
use `varname_leaves` from AbstractPPL instead (#1030)
penelopeysm Aug 31, 2025
fead2a2
tidy occurrences of varname_leaves as well (#1031)
penelopeysm Aug 31, 2025
1e1cd94
Merge branch 'main' into breaking
penelopeysm Sep 15, 2025
729bfba
`InitContext`, part 4 - Use `init!!` to replace `evaluate_and_sample!…
penelopeysm Sep 18, 2025
2ca382e
Merge branch 'main' into breaking
penelopeysm Sep 18, 2025
6d43231
Merge branch 'main' into breaking
penelopeysm Sep 20, 2025
0114e64
Merge branch 'main' into breaking
penelopeysm Sep 20, 2025
d3d32e4
`InitContext`, part 5 - Remove `SamplingContext`, `SampleFrom{Prior,U…
penelopeysm Sep 24, 2025
5a98037
fix missing import
penelopeysm Sep 24, 2025
7311465
Shuffle context code around and remove dead code (#1050)
penelopeysm Sep 25, 2025
c08cfa5
Delete the `"del"` flag (#1058)
mhauru Sep 29, 2025
11d0b69
Merge branch 'main' into breaking
penelopeysm Sep 30, 2025
08212a2
Fixes for Turing 0.41 (#1057)
penelopeysm Sep 30, 2025
7abd5fb
Remove `resume_from` and `default_chain_type` (#1061)
penelopeysm Oct 2, 2025
908d402
remove initial_params warning
penelopeysm Oct 3, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 76 additions & 0 deletions HISTORY.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,81 @@
# DynamicPPL Changelog

## 0.38.0

**Breaking changes**

### Introduction of `InitContext`

DynamicPPL 0.38 introduces a new evaluation context, `InitContext`.
It is used to generate fresh values for random variables in a model.

Evaluation contexts are stored inside a `DynamicPPL.Model` object, and control what happens with tilde-statements when a model is run.
The two major leaf (basic) contexts are `DefaultContext` and, now, `InitContext`.
`DefaultContext` is the default context, and it simply uses the values that are already stored in the `VarInfo` object passed to the model evaluation function.
On the other hand, `InitContext` ignores values in the VarInfo object and inserts new values obtained from a specified source.
(It follows also that the VarInfo being used may be empty, which means that `InitContext` is now also the way to obtain a fresh VarInfo for a model.)

DynamicPPL 0.38 provides three flavours of _initialisation strategies_, which are specified as the second argument to `InitContext`:

- `InitContext(rng, InitFromPrior())`: New values are sampled from the prior distribution (on the right-hand side of the tilde).
- `InitContext(rng, InitFromUniform(a, b))`: New values are sampled uniformly from the interval `[a, b]`, and then invlinked to the support of the distribution on the right-hand side of the tilde.
- `InitContext(rng, InitFromParams(p, fallback))`: New values are obtained by indexing into the `p` object, which can be a `NamedTuple` or `Dict{<:VarName}`. If a variable is not found in `p`, then the `fallback` strategy is used, which is simply another of these strategies. In particular, `InitFromParams` enables the case where different variables are to be initialised from different sources.

(It is possible to define your own initialisation strategy; users who wish to do so are referred to the DynamicPPL API documentation and source code.)

**The main impact on the upcoming Turing.jl release** is that, instead of providing initial values for sampling, the user will be expected to provide an initialisation strategy instead.
This is a more flexible approach, and not only solves a number of pre-existing issues with initialisation of Turing models, but also improves the clarity of user code.
In particular:

- When providing a set of fixed parameters (i.e. `InitFromParams(p)`), `p` must now either be a NamedTuple or a Dict. Previously Vectors were allowed, which is error-prone because the ordering of variables in a VarInfo is not obvious.
- The parameters in `p` must now always be provided in unlinked space (i.e., in the space of the distribution on the right-hand side of the tilde). Previously, whether a parameter was expected to be in linked or unlinked space depended on whether the VarInfo was linked or not, which was confusing.

### Removal of `SamplingContext`

For developers working on DynamicPPL, `InitContext` now completely replaces what used to be `SamplingContext`, `SampleFromPrior`, and `SampleFromUniform`.
Evaluating a model with `SamplingContext(SampleFromPrior())` (e.g. with `DynamicPPL.evaluate_and_sample!!(model, VarInfo(), SampleFromPrior())` has a direct one-to-one replacement in `DynamicPPL.init!!(model, VarInfo(), InitFromPrior())`.
Please see the docstring of `init!!` for more details.
Likewise `SampleFromUniform()` can be replaced with `InitFromUniform()`.
`InitFromParams()` provides new functionality which was previously implemented in the roundabout way of manipulating the VarInfo (e.g. using `unflatten`, or even more hackily by directly modifying values in the VarInfo), and then evaluating using `DefaultContext`.

The main change that this is likely to create is for those who are implementing samplers or inference algorithms.
The exact way in which this happens will be detailed in the Turing.jl changelog when a new release is made.
Broadly speaking, though, `SamplingContext(MySampler())` will be removed so if your sampler needs custom behaviour with the tilde-pipeline you will likely have to define your own context.

### Simplification of the tilde-pipeline

There are now only two functions in the tilde-pipeline that need to be overloaded to change the behaviour of tilde-statements, namely, `tilde_assume!!` and `tilde_observe!!`.
Other functions such as `tilde_assume` and `assume` (and their `observe` counterparts) have been removed.

Note that this was effectively already the case in DynamicPPL 0.37 (where they were just wrappers around each other).
The separation of these functions was primarily implemented to avoid performing extra work where unneeded (e.g. to not calculate the log-likelihood when `PriorContext` was being used). This functionality has since been replaced with accumulators (see the 0.37 changelog for more details).

### Removal of the `"del"` flag

Previously `VarInfo` (or more correctly, the `Metadata` object within a `VarInfo`), had a flag called `"del"` for all variables. If it was set to `true` the variable was to be overwritten with a new value at the next evaluation. The new `InitContext` and related changes above make this flag unnecessary, and it has been removed.

### Removal of `resume_from`

The `resume_from=chn` keyword argument to `sample` has been removed; please use `initial_state=DynamicPPL.loadstate(chn)` instead.
`loadstate` is exported from DynamicPPL.

**Other changes**

### `setleafcontext(model, context)`

This convenience method has been added to quickly modify the leaf context of a model.

### Reimplementation of functions using `InitContext`

A number of functions have been reimplemented and unified with the help of `InitContext`.
In particular, this release brings substantial performance improvements for `returned` and `predict`.
Their APIs are the same.

### Upstreaming of VarName functionality

The implementation of the `varname_leaves` and `varname_and_value_leaves` functions have been moved to AbstractPPL.jl.
Their behaviour is otherwise identical, and they are still accessible from the DynamicPPL module (though still not exported).

## 0.37.5

A minor optimisation for Enzyme AD on DynamicPPL models.
Expand Down
4 changes: 2 additions & 2 deletions Project.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name = "DynamicPPL"
uuid = "366bfd00-2699-11ea-058f-f148b4cae6d8"
version = "0.37.5"
version = "0.38.0"

[deps]
ADTypes = "47edcb42-4c32-4615-8424-f2b9edc5f35b"
Expand Down Expand Up @@ -48,7 +48,7 @@ DynamicPPLMooncakeExt = ["Mooncake"]
[compat]
ADTypes = "1"
AbstractMCMC = "5"
AbstractPPL = "0.13"
AbstractPPL = "0.13.1"
Accessors = "0.1"
BangBang = "0.4.1"
Bijectors = "0.13.18, 0.14, 0.15"
Expand Down
4 changes: 2 additions & 2 deletions benchmarks/Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,11 @@ DynamicPPL = {path = "../"}
ADTypes = "1.14.0"
BenchmarkTools = "1.6.0"
Distributions = "0.25.117"
DynamicPPL = "0.37"
DynamicPPL = "0.38"
Enzyme = "0.13"
ForwardDiff = "0.10.38, 1"
LogDensityProblems = "2.1.2"
Mooncake = "0.4"
PrettyTables = "3"
ReverseDiff = "1.15.3"
StableRNGs = "1"
StableRNGs = "1"
2 changes: 1 addition & 1 deletion docs/Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ Accessors = "0.1"
Distributions = "0.25"
Documenter = "1"
DocumenterMermaid = "0.1, 0.2"
DynamicPPL = "0.37"
DynamicPPL = "0.38"
FillArrays = "0.13, 1"
ForwardDiff = "0.10, 1"
JET = "0.9, 0.10"
Expand Down
56 changes: 34 additions & 22 deletions docs/src/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ Part of the API of DynamicPPL is defined in the more lightweight interface packa

A core component of DynamicPPL is the [`@model`](@ref) macro.
It can be used to define probabilistic models in an intuitive way by specifying random variables and their distributions with `~` statements.
These statements are rewritten by `@model` as calls of [internal functions](@ref model_internal) for sampling the variables and computing their log densities.
These statements are rewritten by `@model` as calls of internal functions for sampling the variables and computing their log densities.

```@docs
@model
Expand Down Expand Up @@ -360,6 +360,13 @@ Base.empty!
SimpleVarInfo
```

### Tilde-pipeline

```@docs
tilde_assume!!
tilde_observe!!
```

### Accumulators

The subtypes of [`AbstractVarInfo`](@ref) store the cumulative log prior and log likelihood, and sometimes other variables that change during executing, in what are called accumulators.
Expand Down Expand Up @@ -451,8 +458,6 @@ DynamicPPL.maybe_invlink_before_eval!!
Base.merge(::AbstractVarInfo)
DynamicPPL.subset
DynamicPPL.unflatten
DynamicPPL.varname_leaves
DynamicPPL.varname_and_value_leaves
```

### Evaluation Contexts
Expand All @@ -465,33 +470,46 @@ AbstractPPL.evaluate!!

This method mutates the `varinfo` used for execution.
By default, it does not perform any actual sampling: it only evaluates the model using the values of the variables that are already in the `varinfo`.
To perform sampling, you can either wrap `model.context` in a `SamplingContext`, or use this convenience method:

```@docs
DynamicPPL.evaluate_and_sample!!
```
If you wish to sample new values, see the section on [VarInfo initialisation](#VarInfo-initialisation) just below this.

The behaviour of a model execution can be changed with evaluation contexts, which are a field of the model.
Contexts are subtypes of `AbstractPPL.AbstractContext`.

```@docs
SamplingContext
DefaultContext
PrefixContext
ConditionContext
InitContext
```

### Samplers
### VarInfo initialisation

The function `init!!` is used to initialise, or overwrite, values in a VarInfo.
It is really a thin wrapper around using `evaluate!!` with an `InitContext`.

```@docs
DynamicPPL.init!!
```

In DynamicPPL two samplers are defined that are used to initialize unobserved random variables:
[`SampleFromPrior`](@ref) which samples from the prior distribution, and [`SampleFromUniform`](@ref) which samples from a uniform distribution.
To accomplish this, an initialisation _strategy_ is required, which defines how new values are to be obtained.
There are three concrete strategies provided in DynamicPPL:

```@docs
SampleFromPrior
SampleFromUniform
InitFromPrior
InitFromUniform
InitFromParams
```

Additionally, a generic sampler for inference is implemented.
If you wish to write your own, you have to subtype [`DynamicPPL.AbstractInitStrategy`](@ref) and implement the `init` method.

```@docs
DynamicPPL.AbstractInitStrategy
DynamicPPL.init
```

### Samplers

In DynamicPPL a generic sampler for inference is implemented.

```@docs
Sampler
Expand All @@ -502,7 +520,7 @@ The default implementation of [`Sampler`](@ref) uses the following unexported fu
```@docs
DynamicPPL.initialstep
DynamicPPL.loadstate
DynamicPPL.initialsampler
DynamicPPL.init_strategy
```

Finally, to specify which varinfo type a [`Sampler`](@ref) should use for a given [`Model`](@ref), this is specified by [`DynamicPPL.default_varinfo`](@ref) and can thus be overloaded for each `model`-`sampler` combination. This can be useful in cases where one has explicit knowledge that one type of varinfo will be more performant for the given `model` and `sampler`.
Expand All @@ -517,9 +535,3 @@ There is also the _experimental_ [`DynamicPPL.Experimental.determine_suitable_va
DynamicPPL.Experimental.determine_suitable_varinfo
DynamicPPL.Experimental.is_suitable_varinfo
```

### [Model-Internal Functions](@id model_internal)

```@docs
tilde_assume
```
2 changes: 0 additions & 2 deletions ext/DynamicPPLEnzymeCoreExt.jl
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@ else
using ..EnzymeCore
end

@inline EnzymeCore.EnzymeRules.inactive_type(::Type{<:DynamicPPL.SamplingContext}) = true

# Mark istrans as having 0 derivative. The `nothing` return value is not significant, Enzyme
# only checks whether such a method exists, and never runs it.
@inline EnzymeCore.EnzymeRules.inactive(::typeof(DynamicPPL.istrans), args...) = nothing
Expand Down
39 changes: 21 additions & 18 deletions ext/DynamicPPLJETExt.jl
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ using JET: JET
function DynamicPPL.Experimental.is_suitable_varinfo(
model::DynamicPPL.Model, varinfo::DynamicPPL.AbstractVarInfo; only_ddpl::Bool=true
)
# Let's make sure that both evaluation and sampling doesn't result in type errors.
f, argtypes = DynamicPPL.DebugUtils.gen_evaluator_call_with_types(model, varinfo)
# If specified, we only check errors originating somewhere in the DynamicPPL.jl.
# This way we don't just fall back to untyped if the user's code is the issue.
Expand All @@ -21,32 +20,36 @@ end
function DynamicPPL.Experimental._determine_varinfo_jet(
model::DynamicPPL.Model; only_ddpl::Bool=true
)
# Use SamplingContext to test type stability.
sampling_model = DynamicPPL.contextualize(
model, DynamicPPL.SamplingContext(model.context)
)

# First we try with the typed varinfo.
varinfo = DynamicPPL.typed_varinfo(sampling_model)
# Generate a typed varinfo to test model type stability with
varinfo = DynamicPPL.typed_varinfo(model)

# Let's make sure that both evaluation and sampling doesn't result in type errors.
issuccess, result = DynamicPPL.Experimental.is_suitable_varinfo(
sampling_model, varinfo; only_ddpl
# Check type stability of evaluation (i.e. DefaultContext)
model = DynamicPPL.setleafcontext(model, DynamicPPL.DefaultContext())
eval_issuccess, eval_result = DynamicPPL.Experimental.is_suitable_varinfo(
model, varinfo; only_ddpl
)
if !eval_issuccess
@debug "Evaluation with typed varinfo failed with the following issues:"
@debug eval_result
end

if !issuccess
# Useful information for debugging.
@debug "Evaluaton with typed varinfo failed with the following issues:"
@debug result
# Check type stability of initialisation (i.e. InitContext)
model = DynamicPPL.setleafcontext(model, DynamicPPL.InitContext())
init_issuccess, init_result = DynamicPPL.Experimental.is_suitable_varinfo(
model, varinfo; only_ddpl
)
if !init_issuccess
@debug "Initialisation with typed varinfo failed with the following issues:"
@debug init_result
end

# If we didn't fail anywhere, we return the type stable one.
return if issuccess
# If neither of them failed, we can return the typed varinfo as it's type stable.
return if (eval_issuccess && init_issuccess)
varinfo
else
# Warn the user that we can't use the type stable one.
@warn "Model seems incompatible with typed varinfo. Falling back to untyped varinfo."
DynamicPPL.untyped_varinfo(sampling_model)
DynamicPPL.untyped_varinfo(model)
end
end

Expand Down
53 changes: 34 additions & 19 deletions ext/DynamicPPLMCMCChainsExt.jl
Original file line number Diff line number Diff line change
@@ -1,12 +1,7 @@
module DynamicPPLMCMCChainsExt

if isdefined(Base, :get_extension)
using DynamicPPL: DynamicPPL
using MCMCChains: MCMCChains
else
using ..DynamicPPL: DynamicPPL
using ..MCMCChains: MCMCChains
end
using DynamicPPL: DynamicPPL, AbstractPPL
using MCMCChains: MCMCChains

# Load state from a `Chains`: By convention, it is stored in `:samplerstate` metadata
function DynamicPPL.loadstate(chain::MCMCChains.Chains)
Expand All @@ -28,7 +23,7 @@ end

function _check_varname_indexing(c::MCMCChains.Chains)
return DynamicPPL.supports_varname_indexing(c) ||
error("Chains do not support indexing using `VarName`s.")
error("This `Chains` object does not support indexing using `VarName`s.")
end

function DynamicPPL.getindex_varname(
Expand All @@ -42,6 +37,17 @@ function DynamicPPL.varnames(c::MCMCChains.Chains)
return keys(c.info.varname_to_symbol)
end

function chain_sample_to_varname_dict(
c::MCMCChains.Chains{Tval}, sample_idx, chain_idx
) where {Tval}
_check_varname_indexing(c)
d = Dict{DynamicPPL.VarName,Tval}()
for vn in DynamicPPL.varnames(c)
d[vn] = DynamicPPL.getindex_varname(c, sample_idx, vn, chain_idx)
end
return d
end

"""
predict([rng::AbstractRNG,] model::Model, chain::MCMCChains.Chains; include_all=false)

Expand Down Expand Up @@ -114,14 +120,20 @@ function DynamicPPL.predict(

iters = Iterators.product(1:size(chain, 1), 1:size(chain, 3))
predictive_samples = map(iters) do (sample_idx, chain_idx)
DynamicPPL.setval_and_resample!(varinfo, parameter_only_chain, sample_idx, chain_idx)
varinfo = last(DynamicPPL.evaluate_and_sample!!(rng, model, varinfo))

# Extract values from the chain
values_dict = chain_sample_to_varname_dict(parameter_only_chain, sample_idx, chain_idx)
# Resample any variables that are not present in `values_dict`
_, varinfo = DynamicPPL.init!!(
rng,
model,
varinfo,
DynamicPPL.InitFromParams(values_dict, DynamicPPL.InitFromPrior()),
)
vals = DynamicPPL.values_as_in_model(model, false, varinfo)
varname_vals = mapreduce(
collect,
vcat,
map(DynamicPPL.varname_and_value_leaves, keys(vals), values(vals)),
map(AbstractPPL.varname_and_value_leaves, keys(vals), values(vals)),
)

return (varname_and_values=varname_vals, logp=DynamicPPL.getlogjoint(varinfo))
Expand Down Expand Up @@ -248,13 +260,16 @@ function DynamicPPL.returned(model::DynamicPPL.Model, chain_full::MCMCChains.Cha
varinfo = DynamicPPL.VarInfo(model)
iters = Iterators.product(1:size(chain, 1), 1:size(chain, 3))
return map(iters) do (sample_idx, chain_idx)
# TODO: Use `fix` once we've addressed https://github.com/TuringLang/DynamicPPL.jl/issues/702.
# Update the varinfo with the current sample and make variables not present in `chain`
# to be sampled.
DynamicPPL.setval_and_resample!(varinfo, chain, sample_idx, chain_idx)
# NOTE: Some of the varialbes can be a view into the `varinfo`, so we need to
# `deepcopy` the `varinfo` before passing it to the `model`.
model(deepcopy(varinfo))
# Extract values from the chain
values_dict = chain_sample_to_varname_dict(chain, sample_idx, chain_idx)
# Resample any variables that are not present in `values_dict`, and
# return the model's retval.
retval, _ = DynamicPPL.init!!(
model,
varinfo,
DynamicPPL.InitFromParams(values_dict, DynamicPPL.InitFromPrior()),
)
retval
end
end

Expand Down
Loading
Loading