Samplers
Goal of this page
This page gives a code-oriented overview of the seven samplers implemented in PDMPFlux. The focus is not on theory, but on where the key building blocks live in the codebase and how they are wired together: flow / rate / upper bounds / thinning / velocity_jump.
Samplers covered here (exported):
ZigZagBPS(Bouncy Particle Sampler)ForwardECMC(Forward Event Chain Monte Carlo)BoomerangSpeedUpZigZagStickyZigZagRHMC(Randomized Hamiltonian Monte Carlo)
Common implementation: the minimal AbstractPDMP interface
Where things live:
- Sampler types:
src/Samplers/*.jl - Common initialization:
src/Samplers/AbstractPDMP.jl - Upper bounds (
BoundBox):src/UpperBound.jl/src/Composites.jl - Sampling loop:
src/SamplingLoopInplace.jl(non-!wrappers insrc/SamplingLoop.jl) - Sticky-specific loop:
src/StickySamplingLoop.jl
Each sampler is a subtype of AbstractPDMP and is mostly defined by closures assembled in its constructor:
flow(x, v, t): deterministic dynamics between events (possibly a numerical integrator)rate(x, v, t)/rate_vect(x, v, t): event rate (hazard)signed_rate*: for signed-bound strategies (when enabled)velocity_jump(x, v, rng): velocity update at accepted events (reflection / flip / refresh, etc.)grid_size, tmax, adaptive, (vectorized_bound, signed_bound): how the proxy upper bound for thinning is constructed
Thinning (proposal → accept/reject) at a glance
The core loop in src/SamplingLoopInplace.jl works like this:
- Build a proxy upper bound
BoundBoxviaupper_bound_func(x, v, horizon)(ifgrid_size == 0, a constant bound is used) - Sample a proposed event time
tpand the corresponding bound valueλ̄vianext_event(boundbox, Exp(1)) - Evaluate the true rate
λ(tp) = rate(x, v, tp)and accept with probability (ar = λ(tp)/λ̄) - If accepted: advance with
flowbytp, then applyvelocity_jumpto form the next event state - If rejected: accumulate exponential waiting time and re-propose within the same
BoundBox(optionally shrinkinghorizonwhenadaptive=true) - If the proposal crosses the horizon: advance to the horizon via
flowand continue (with mild horizon adaptation if enabled)
Sticky samplers branch to src/StickySamplingLoop.jl, where axis-crossings and thawing are treated as additional “events” (via is_active).
Implementation notes for each sampler (7)
Zig-Zag (ZigZag) — src/Samplers/ZigZagSamplers.jl
- flow: linear motion
x(t) = x + v t(typically with component-wise velocities (v_i \in {\pm 1})) - rate:
- Global:
sum(max.(0, ∇U(x_t) .* v_t)) - Vectorized:
max.(0, ∇U(x_t) .* v_t)(per-coordinate rates) - With
signed_bound=true,signed_rate_vect = ∇U(x_t) .* v_tis used for bound construction (note: signed bound is not compatible withvectorized_bound=falsehere, so it is auto-disabled)
- Global:
- jump (
velocity_jump): sample an index from probabilities proportional tomax.(0, ∇U(x) .* v)and flip one componentv[m] *= -1 - note: with
grid_size > 0andvectorized_bound=true, bounds are typically built viaupper_bound_grid_vect.
Speed Up Zig-Zag (SpeedUpZigZag) — src/Samplers/SpeedUpZigZagSamplers.jl
- intent: a “sped-up” ZigZag variant via a position-dependent time/velocity transformation (implemented via a nonlinear flow)
- flow: closed-form nonlinear update in
flow(x, v, t)(not straight-line) - rate: same ZigZag structure but using an effective gradient
∇U_effective(x) = speed(x) * ∇U(x) - ∇speed(x)speed(x) = sqrt(1 + x⋅x),∇speed(x) = x / speed(x)
- jump: same as ZigZag (flip a single coordinate chosen by the rates)
Bouncy Particle Sampler (BPS) — src/Samplers/BouncyParticleSamplers.jl
- flow:
x(t)=x+v t - rate:
max(0, ∇U(x_t)⋅v_t) + refresh_rate- This implementation does not use vectorized bounds (it forces
vectorized_bound=false).
- This implementation does not use vectorized bounds (it forces
- jump:
- With probability
bounce_prob = bounce_rate/(bounce_rate+refresh_rate): reflect - Otherwise: refresh (draw new
vviarandn!; ifGaussian_velocity=false, normalize to unit length) - Reflection is implemented as
v - 2 * (v⋅g)/||g||^2 * gwithg = ∇U(x).
- With probability
Forward Event Chain Monte Carlo (ForwardECMC) — src/Samplers/ForwardEventChainMonteCarlo.jl
- flow:
x(t)=x+v t - rate:
max(0, ∇U(x_t)⋅v_t)(a signed variant is also provided) - jump: relative to the gradient direction (n = ∇U(x)/||∇U(x)||)
- decompose
v = v_parallel + v_orthogonaland sample a new radial/parallel component magnitudeρ - with probability
mix_p, refresh the orthogonal component (orthogonal switch ifswitch=true, full refresh otherwise) - options include
ran_p(random angle),positive(avoid backtracking),speed_factor(speed scaling),normal(alternativeρgeneration)
- decompose
- note: requires
dim >= 3(validated in the constructor).
Boomerang (Boomerang) — src/Samplers/BoomerangSamplers.jl
- flow: harmonic-oscillator rotation of
(x, v)by angletx(t) = x cos t + v sin t,v(t) = -x sin t + v cos t
- rate:
max(0, ∇U_eff(x_t)⋅v_t) + refresh_ratewhere∇U_eff(x) = ∇U(x) - x - jump:
- Like BPS: reflect or refresh
- Refresh draws
v ~ N(0, I)(this implementation does not normalize).
Sticky Zig-Zag (StickyZigZag) — src/Samplers/StickyZigZagSamplers.jl + src/StickySamplingLoop.jl
- extension point:
StickyPDMP <: AbstractPDMPadds anis_activemask (coordinates can be active or frozen/sticky). - flow / rate / jump: same functional form as ZigZag, but the loop passes a masked velocity (inactive coordinates treated as
v_i = 0) via_active_velocity. - extra events (Sticky loop):
- Axis crossing: if an active coordinate crosses 0, the loop advances to that axis and sets
is_active[i]=false(stick/freeze) - Thawing: sample a thaw time using the total thaw rate
sum(κ[i])over inactive coordinates, then reactivate one coordinate proportional toκ
- Axis crossing: if an active coordinate crosses 0, the loop advances to that axis and sets
- parameter:
κ::Vector{Float64}controls thawing rates (often tied to prior inclusion probabilities).
Randomized Hamiltonian Monte Carlo (RHMC) — src/Samplers/RandomizedHamiltonianMonteCarlo.jl
- flow: approximate Hamiltonian dynamics
ẋ=v, v̇=-∇U(x)using velocity-Verlet with step sizestep_size - rate: constant Poisson refresh clock only, i.e.
rate = refresh_rate- therefore
init_state(::RHMC, ...)builds a cheap 2-pointBoundBoxspecialized for constant rates
- therefore
- jump: Horowitz (partial) momentum refresh
v ← cos(phi) v + sin(phi) ξ, withξ ~ N(0, I)
- note: if
mean_durationis provided, it is converted torefresh_rate = 1/mean_duration.
About AD constructors
Each sampler has an *AD constructor (e.g. ZigZagAD, BPSAD, ForwardECMCAD, BoomerangAD, SpeedUpZigZagAD, StickyZigZagAD, RHMCAD) that builds ∇U(x) from a potential U(x) and passes it to the sampler.
Additionally, bound construction for thinning (upper_bound_grid*) may need derivatives with respect to time, so AD_backend is also consulted on the upper-bound side (see _pdmp_ad_backend in src/Samplers/AbstractPDMP.jl).