Compiled numerical blocks for foerster inference: log densities and their gradients from raster — compiled to JVM bytecode, and by raster's backends to WASM and GPU kernels — inside probabilistic programs whose addresses, traces, proposals and acceptance foerster owns.
spindel worlds, forks, savepoints, world scopes, settlement
↑
foerster inference: sites, traces, SMC / particle MCMC / MCMC / BBVI,
HMC-within-Gibbs, the block contract (foerster.block),
portable distributions (foerster.dist)
↑
foerster-raster block capabilities from raster: a deftm log density and its
gradient by raster's reverse-mode AD
foerster defines what a block is and runs HMC on it without knowing where its numbers come from; this library supplies them from raster. It is a separate library because raster is a JVM-only compiler with a large dependency tree, while foerster is small and runs on the JVM and in JavaScript — and so that the block contract stays open to other backends (hand-written Clojure, other AD systems).
A block is a group of latent variables sampled at one site, instead of one site per variable. HMC moves all of them jointly along the gradient, which mixes where single-site Metropolis–Hastings does not (many correlated coordinates), and the density can be compiled numerical code.
A block is a description (data: the latents' names, shapes and support,
and what its density covers) plus capabilities (functions over a flat
double[] θ): :log-density and :value+grad, and optionally :sample.
In a model it is an ordinary choice site:
(sample (block/block-dist b inputs) :id :mu :init [0.0 0.0])
The density should be the complete conditional — the latents' priors and every observation that depends on them. foerster keeps HMC exact even when it is not (it accepts on the full trace's log joint), so a missing factor costs mixing, not correctness. The full contract is doc/contract.md.
defdensity writes a block as the body of its log density: the latents
(scalars, in θ order, optionally with a support), the data arguments with
their raster types, and a deftm body. It defines the compiled density and
the block, with the gradient from raster's reverse mode; a constrained
latent is seen in its natural coordinates and foerster samples its
transform:
(defdensity mean-scale [mu [sigma :positive]]
[ys :- (Array double), cnt :- Long]
(loop [i 0 acc (n/+ (dist/logpdf (dist/->Normal 0.0 10.0) mu)
(dist/logpdf (dist/->Normal 0.0 2.0) sigma))]
(if (< i cnt)
(recur (inc i) (n/+ acc (dist/logpdf (dist/->Normal mu sigma) (ra/aget ys i))))
acc)))
(infer/infer (spin (sample (block/block-dist mean-scale {:ys ys :cnt n}) :id :th :init [0.0 0.0]))
{:method :nuts :iterations 1000 :chains 4 :burn 500})
A raster log density is a deftm over typed arguments; raster-block says
how θ and the site's inputs become those arguments, and which argument slots
hold θ. Gradients are taken with respect to those slots only (raster's
:wrt), so the data stays constant.
(require '[org.replikativ.foerster-raster.block :as rb]
'[org.replikativ.foerster.block :as block]
'[org.replikativ.foerster.core :as infer]
'[org.replikativ.foerster.kernel :as k]
'[org.replikativ.foerster.effects :refer [sample]]
'[org.replikativ.spindel.spin.cps :refer [spin]]
'[raster.core :refer [deftm]]
'[raster.numeric :as n]
'[raster.arrays :as ra]
'[raster.sci.distributions :as dist])
;; μ ∈ R², μ_j ~ N(0, s0²), y_ij ~ N(μ_j, s²), observations interleaved
(deftm gauss-lp [m0 :- Double, m1 :- Double, ys :- (Array double),
cnt :- Long, s0 :- Double, s :- Double] :- Double
(loop [i 0 acc (n/+ (dist/logpdf (dist/->Normal 0.0 s0) m0)
(dist/logpdf (dist/->Normal 0.0 s0) m1))]
(if (< i cnt)
(recur (inc i)
(n/+ acc (n/+ (dist/logpdf (dist/->Normal m0 s) (ra/aget ys (* 2 i)))
(dist/logpdf (dist/->Normal m1 s) (ra/aget ys (inc (* 2 i)))))))
acc)))
(def gauss
(rb/raster-block {:block/id :gauss
:block/latents [{:name :mu :shape [2] :support :real}]
:block/target :complete-conditional}
#'gauss-lp
{:args (fn [^doubles th {:keys [ys cnt s0 s]}]
[(aget th 0) (aget th 1) ys (long cnt) (double s0) (double s)])
:theta [0 1]}))
;; the site's inputs: the data and the fixed scales
(def inputs {:ys (double-array ys) :cnt (quot (count ys) 2) :s0 3.0 :s 1.0})
;; HMC-within-Gibbs, two chains
(infer/kernel-infer (spin (sample (block/block-dist gauss inputs) :id :mu :init [0.0 0.0]))
(k/hmc-kernel 1500 {:step-size 0.1 :steps 8 :samples :all :burn 100})
2)
The tests check this block against a pure-Clojure reference block, finite differences, and the analytic posterior under HMC.
What the block tests and raster's AD regression tests cover, as of raster 0.2.1141:
loop/recur) and par/reduce over observations, with
constructed densities ((dist/logpdf (dist/->Normal mu s) y)) in the body;let-bound;ra/aget, aget), which stay
constant under :wrt.For a shape it cannot differentiate, raster declines with an error (e.g.
"No AD template for …") rather than return a gradient; the tests here also
compare every block's gradient with finite differences. Report such a shape
to raster with a minimal deftm.
org.replikativ/foerster-raster {:mvn/version "LATEST"} ; see the Clojars badge
raster's compiler needs JVM options, collected in the :jvm alias:
clojure -M:jvm:test # the tests
clojure -M:jvm:repl # a REPL
JDK 21 or later (--add-modules=jdk.incubator.vector,
--enable-native-access=ALL-UNNAMED).
test/…/logistic_test.clj).:batch), on CPU and GPU.foerster.dist sites into a compiled block without
rewriting the model (foerster.dist and raster's distributions compute
the same densities: test/…/dist_conformance_test.clj).Copyright © 2026 Christian Weilbach. Apache License 2.0.
Can you improve this documentation?Edit on GitHub
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |