PyMC Comparison
This page maps PyMC and BlackJAX concepts to their Norn equivalents. Norn's design is closest to BlackJAX: both provide functional kernel APIs where the sampler state is explicit and the log-density function is passed at each step.
One-Line Sampling
PyMC:
import pymc as pm
with pm.Model():
x = pm.Normal("x", mu=0, sigma=1, shape=2)
trace = pm.sample(1000, tune=500)
Norn:
let log_prob x = Nx.mul_s (Nx.sum (Nx.square x)) (-0.5) in
let init = Nx.zeros Nx.float64 [| 2 |] in
let result = Norn.nuts ~n:1000 ~num_warmup:500 log_prob init
PyMC builds a probabilistic model and derives the log-density automatically. Norn takes the log-density function directly -- you write it yourself or build it from your model. Rune handles the gradient.
BlackJAX Kernel API
BlackJAX:
import blackjax
import jax
kernel = blackjax.nuts(log_prob, step_size=0.5)
state = kernel.init(jax.numpy.zeros(2))
for _ in range(1000):
key, subkey = jax.random.split(key)
state, info = kernel.step(subkey, state)
Norn:
let metric = Norn.unit_metric 2 in
let kernel = Norn.nuts_kernel ~step_size:0.5 ~metric () in
let state = ref (kernel.init (Nx.zeros Nx.float64 [| 2 |]) log_prob) in
for _ = 1 to 1000 do
let new_state, _info = kernel.step !state log_prob in
state := new_state
done
Both use a {init; step} pattern. The key difference: BlackJAX threads a
PRNG key explicitly, while Norn uses Nx's RNG context (Rng.run).
Adaptation
BlackJAX:
warmup = blackjax.window_adaptation(blackjax.nuts, log_prob)
state, kernel, _ = warmup.run(key, jax.numpy.zeros(2), 1000)
Norn:
(* Adaptation is built into sample/nuts/hmc *)
let result = Norn.nuts ~n:1000 ~num_warmup:500 log_prob init
(* Or use sample for control over the kernel *)
let result =
Norn.sample ~n:1000 ~num_warmup:500 log_prob init (fun ~step_size ~metric ->
Norn.nuts_kernel ~step_size ~metric ())
In BlackJAX, adaptation is a separate step that returns a tuned kernel. In
Norn, adaptation is integrated into sample -- it adapts step size and mass
matrix during warmup, then freezes them for sampling.
Samplers
| PyMC / BlackJAX | Norn | Notes |
|---|---|---|
pm.sample() (NUTS) |
Norn.nuts ~n log_prob init |
NUTS with adaptation |
blackjax.nuts(log_prob, step_size) |
Norn.nuts_kernel ~step_size ~metric () |
NUTS kernel |
blackjax.hmc(log_prob, step_size, ...) |
Norn.hmc_kernel ~step_size ~metric () |
HMC kernel |
pm.sample(step=pm.HamiltonianMC(...)) |
Norn.hmc ~n log_prob init |
HMC with adaptation |
Integrators
| BlackJAX | Norn | Notes |
|---|---|---|
blackjax.mcmc.integrators.velocity_verlet |
Norn.leapfrog |
Default, 1 grad eval/step |
blackjax.mcmc.integrators.mclachlan |
Norn.mclachlan |
2 grad evals/step |
blackjax.mcmc.integrators.yoshida |
Norn.yoshida |
3 grad evals/step |
Usage comparison:
# BlackJAX
kernel = blackjax.nuts(log_prob, step_size=0.5,
integrator=blackjax.mcmc.integrators.mclachlan)
(* Norn *)
let kernel =
Norn.nuts_kernel ~integrator:Norn.mclachlan ~step_size:0.5 ~metric ()
Metrics (Mass Matrix)
| BlackJAX | Norn | Notes |
|---|---|---|
blackjax.mcmc.metrics.default_metric(jnp.ones(d)) |
Norn.unit_metric d |
Identity |
blackjax.mcmc.metrics.default_metric(inv_mass_diag) |
Norn.diagonal_metric inv_mass_diag |
Diagonal |
| Dense metric via Cholesky | Norn.dense_metric inv_mass_matrix |
Full matrix |
Diagnostics
| PyMC / ArviZ | Norn | Notes |
|---|---|---|
az.ess(trace) |
Norn.ess samples |
Effective sample size |
az.rhat(trace) |
Norn.rhat chains |
Split R-hat |
trace.sample_stats["diverging"] |
result.stats.num_divergent |
Divergence count |
trace.sample_stats["accept"] |
result.stats.accept_rate |
Mean acceptance rate |
trace.sample_stats["step_size"] |
result.stats.step_size |
Final step size |
State and Info
BlackJAX state:
state.position # current sample
state.logdensity # log p(x)
state.logdensity_grad # grad log p(x)
Norn state:
state.position (* Nx.float64_t, shape [dim] *)
state.log_density (* float *)
state.grad_log_density (* Nx.float64_t, shape [dim] *)
BlackJAX info:
info.acceptance_rate
info.is_divergent
info.energy
info.num_integration_steps
Norn info:
info.acceptance_rate (* float in [0, 1] *)
info.is_divergent (* bool *)
info.energy (* float *)
info.num_integration_steps (* int *)
Key Differences
| Aspect | PyMC / BlackJAX | Norn |
|---|---|---|
| Language | Python / JAX | OCaml / Rune |
| Model definition | Declarative (PyMC) or functional (BlackJAX) | Functional -- write log_prob directly |
| Gradients | JAX autodiff | Rune autodiff |
| PRNG | Explicit key splitting (JAX) | Scoped via Nx.Rng.run |
| Adaptation | Separate step (BlackJAX) or automatic (PyMC) | Integrated into sample |
| Mass matrix output | Diagonal or dense | metric record with sample_momentum, kinetic_energy, scale |
| Multi-chain | Built-in (chains parameter) |
Run multiple calls, combine with rhat |
| Trace format | ArviZ InferenceData | result record with samples matrix |
| Probabilistic DSL | Yes (PyMC) | No -- bring your own log-density |