Part 4: Advanced MCMC Methods — From Random Walks to Hamiltonian Flow
When Physics Meets Inference | Inferential Thinking Module 5 | ASTR 596
“In mathematics you don’t understand things. You just get used to them.”
— John von Neumann“Nature uses only the longest threads to weave her patterns, so that each small piece of her fabric reveals the organization of the entire tapestry.”
— Richard Feynman
Learning Outcomes¶
By the end of Part 4, you will be able to:
Diagnose when random walk Metropolis-Hastings fails (strongly correlated posteriors) using visual and quantitative metrics
Explain why gradients provide directional information that makes sampling exponentially more efficient
Derive Hamiltonian Monte Carlo from Hamilton’s equations and recognize it as N-body simulation in parameter space
Implement HMC by adapting your Project 2 leapfrog integrator to parameter space
Compute gradients via finite differences and understand why automatic differentiation is superior
Interpret HMC diagnostics (energy conservation, divergences, acceptance rates)
Understand how NUTS adaptively tunes HMC to eliminate manual parameter tuning
Appreciate why JAX (autodiff + JIT) revolutionized gradient-based inference
Prerequisites: Part 3 (Metropolis-Hastings, detailed balance, diagnostics), Module 3 Part 2 (Hamiltonian dynamics, phase space), Project 2 (leapfrog integrator), calculus (partial derivatives, chain rule, optimization)
Roadmap: Where We’re Going¶
This part builds on everything you’ve learned about MCMC, physics, and computation. Here’s the journey:
figure-md - Unknown Directive
figure-md - Unknown Directive:align: center
```mermaid
flowchart TD
A[Metropolis-Hastings<br/>struggles with correlations] --> B[Random walks<br/>are inefficient]
B --> C[Need directional<br/>information]
C --> E[Use Gradients!<br/>∇ log p]
E --> F[Hamiltonian<br/>dynamics]
F --> G[HMC<br/>Leapfrog integrator]
G --> H[NUTS<br/>Auto-tuning]
H --> I[JAX +<br/>NumPyro]
classDef problem fill:#BF616A,stroke:#2E3440,stroke-width:2px,color:#fff
classDef insight fill:#A3BE8C,stroke:#2E3440,stroke-width:2px,color:#fff
classDef solution fill:#5E81AC,stroke:#2E3440,stroke-width:2px,color:#fff
classDef tools fill:#EBCB8B,stroke:#2E3440,stroke-width:2px,color:#2E3440
class A problem
class E insight
class G solution
class I tools
```The narrative arc: We’ll start by diagnosing when Metropolis-Hastings struggles (correlated posteriors), discover why gradients are powerful (they point uphill!), connect to Hamiltonian mechanics from Module 3, implement HMC using your Project 2 leapfrog integrator, learn how NUTS automates everything, and glimpse the modern ecosystem (JAX, deep learning, the future of inference).
This isn’t just a better MCMC algorithm. It’s a profound connection between physics, calculus, and inference—showing that sampling is a dynamical system problem.
Part 1: The MCMC Revolution and Its Limits¶
Why MCMC Changed Everything¶
In Part 3, you learned Metropolis-Hastings—one of the most important algorithms in scientific computing. Let’s appreciate its impact:
The MCMC revolution (1953-present):
1953: Metropolis et al. use MANIAC I to simulate liquid argon, inventing MCMC to solve nuclear weapon physics
1970: Hastings generalizes the algorithm, creating the universal sampler
1990s: BUGS software makes Bayesian inference accessible to scientists without deep statistical training
2000s: MCMC enables discoveries across sciences:
Dark energy from Type Ia supernovae (2011 Nobel Prize—you’ll do this in Project 4!)
Exoplanet detection via radial velocity (thousands of planets discovered)
Genomics and personalized medicine (GWAS studies)
Climate model uncertainty quantification
Today: Billions of MCMC samples generated daily in research, industry, and medicine
Why it’s revolutionary: MCMC turned Bayesian inference from a theoretical framework into a practical tool. Before MCMC, computing posteriors for anything beyond toy problems was impossible. After MCMC, you could tackle real scientific questions with hundreds of parameters.
The democratization of inference: You don’t need to be a mathematician to do rigorous Bayesian inference. Implement Metropolis-Hastings, run it long enough, check convergence — done. This is computational thinking at its finest.
The Problem: When Random Walks Get Lost¶
Metropolis-Hastings is universal — it can sample from any distribution. But universal doesn’t mean efficient. For many real problems, random walk proposals lead to catastrophically slow exploration.
Let me show you where it breaks down.
Consider a 2D posterior with strong correlation — a twisted Gaussian. This isn’t a pathological edge case; it’s what real posteriors often look like when parameters trade off (think vs in cosmology, or planet mass vs orbital inclination in exoplanet fitting).
The Twisted Gaussian: Parameters with posterior:
where is the correlation coefficient.
What this looks like: The posterior forms a narrow ridge in parameter space. High probability along , but very narrow perpendicular to the ridge. Moving along the ridge is easy (parameters change together). Moving perpendicular is hard (tightly constrained).
Why random walk Metropolis-Hastings struggles:
Isotropic proposals: Standard M-H uses — a circular Gaussian centered on current position. Proposals are equally likely in all directions.
The step-size dilemma:
Small : Proposals along the ridge are accepted (good!) but make tiny progress. Proposals perpendicular are also small, so you never explore the width. → Slow mixing along ridge, never explore perpendicular.
Large : Proposals along the ridge make big jumps (good!) but proposals perpendicular step out of the ridge and get rejected (bad!). → Most proposals rejected, acceptance rate collapses.
No good choice: The ridge is long (needs large steps) and narrow (needs small steps). Isotropic proposals can’t do both simultaneously.
The consequence: Effective Sample Size (ESS) is terrible. Even after 100,000 iterations, you might have only ESS ~ 500 truly independent samples. You’ve wasted 99.5% of your computation!
Quantitative diagnosis: Let’s look at what random walk MCMC produces for the twisted Gaussian with :
Acceptance rate: ~60% (seems okay, but misleading)
Autocorrelation length: steps (high!)
ESS for 10,000 samples: ESS ~ 50 (terrible!)
Time to convergence: R-hat < 1.1 requires ~50,000 samples per chain
Compare this to HMC (spoiler alert):
Acceptance rate: ~90%
Autocorrelation length: steps
ESS for 10,000 samples: ESS ~ 3,000 (60× improvement!)
Time to convergence: R-hat < 1.1 with ~5,000 samples per chain
Same problem, two orders of magnitude difference in efficiency. This is why gradient-based methods matter.
figure-md - Unknown Directive
figure-md - Unknown Directive:align: center <img src="figures/04-mod5-part4-fig1-mh-vs-hmc.png" alt="Comprehensive comparison of Metropolis-Hastings vs HMC" width="95%"> **Figure 1: Metropolis-Hastings vs Hamiltonian Monte Carlo on a Strongly Correlated Posterior.** *Top left:* Target distribution (2D Gaussian with correlation ρ=0.95) showing the narrow ridge structure that challenges random walk samplers. *Top right:* M-H produces scattered samples with low acceptance (44.8%), requiring 10,000 iterations to poorly cover the posterior. *Bottom left:* HMC follows smooth trajectories along the ridge with high acceptance (97.5%), needing only 1,000 samples for superior coverage. *Bottom right:* Marginal distributions show HMC (blue) accurately recovers the true distribution (dashed black) while M-H (red) shows poor coverage despite 10× more samples. HMC achieves ~60× better effective sample size for the same computational cost.
figure-md - Unknown Directive
figure-md - Unknown Directive:align: center <img src="figures/04-fig2_trace_plots_comparison.png" alt="MCMC trace plots comparison" width="95%"> **Figure 2: Trace Plot Diagnostic — The "Hairy Caterpillar" Test.** *Left:* M-H trace plots show slow, correlated wandering with visible trends and poor mixing—this is what *bad* MCMC looks like. The chain gets stuck in regions and slowly drifts. *Right:* HMC trace plots show rapid, noisy exploration around the stationary distribution—this "hairy caterpillar" appearance indicates good mixing. Each parameter explores its full range quickly and independently. The dramatic difference in mixing is why HMC needs far fewer iterations to achieve convergence.
figure-md - Unknown Directive
figure-md - Unknown Directive:align: center <img src="figures/04-fig3_autocorrelation_comparison.png" alt="Autocorrelation function comparison" width="95%"> **Figure 3: Autocorrelation Quantifies Sampling Efficiency.** *Left:* M-H autocorrelation decays slowly (τ ≈ 158 steps), meaning samples remain correlated for hundreds of iterations. This high autocorrelation time means you need ~17× more samples to get independent draws. *Right:* HMC autocorrelation drops to zero almost immediately (τ ≈ 9 steps), indicating samples are nearly independent after just a few iterations. This is the quantitative proof that HMC is dramatically more efficient—the autocorrelation time is the "price per independent sample" and HMC's price is 17× lower.
Part 2: Gradients — Why Direction Matters (🔴 Essential)¶
The Profound Insight: Optimization and Sampling Are Related¶
Before we dive into HMC, let’s revisit a concept from Calculus I that turns out to be shockingly important: gradients and optimization.
Flashback to Calculus I: How do you find the minimum of a function ?
Compute the derivative:
Set it to zero:
Solve for (critical point)
Check second derivative: → local minimum
For multivariable functions :
Compute the gradient:
Set it to zero:
Solve for (critical point)
The gradient is the multivariable generalization of the derivative. It points in the direction of steepest ascent — the direction where increases most rapidly.
Physical intuition: If is a landscape’s elevation, is the slope vector at your location. It points uphill. The magnitude tells you how steep the slope is.
Why Gradient Descent Is a Big Freaking Deal¶
Here’s the problem: For complicated functions, solving analytically is impossible. Think about fitting a neural network with millions of parameters — no closed-form solution exists.
But here’s the trick: Even if you can’t solve , you can follow the gradient downhill to find a minimum. This is gradient descent:
where is the step size (learning rate).
Algorithm:
Start at some (initial guess)
Compute gradient:
Take a step downhill:
Repeat until (converged to a minimum)
Why this is profound:
It works for any smooth function (don’t need to solve equations analytically)
It scales to high dimensions (works for millions of parameters)
It only requires gradients (not second derivatives, not Hessians)
This is how we train neural networks! Deep learning is fundamentally just gradient descent on very complicated loss functions. Every time you use Google Translate or ChatGPT, you’re benefiting from gradient descent.
From Optimization to Sampling: The Key Idea¶
Optimization: Find (the single best parameter value)
Sampling: Generate samples (explore the whole distribution)
Why sampling is harder: Optimization needs one point (the peak). Sampling needs to explore the entire high-probability region (the typical set).
But here’s the connection: The gradient tells you:
Magnitude: How far you are from the peak (large gradient = far from peak)
Direction: Which way to move to increase probability
Metropolis-Hastings ignores this information! It proposes randomly, not using the landscape’s structure.
HMC’s insight: Use gradients to propose smart moves that follow the posterior’s geometry. Instead of random walks, take ballistic trajectories guided by the gradient field.
Preview: HMC treats the negative log-posterior as a potential energy and simulates dynamics in this landscape. Gradients become forces. This is where physics meets inference.
Computing Gradients: Two Approaches¶
To use gradients in HMC, we need to compute for our posterior. Two main approaches:
Approach 1: Finite Differences (What You Already Know)¶
Recall from earlier in the semester: We approximated derivatives numerically using finite differences:
where is the unit vector in the -th direction and to 10-7 is a small step.
For a -dimensional parameter vector, compute all partial derivatives:
def grad_log_posterior_fd(theta, log_post_fn, h=1e-6):
"""Finite difference gradient of log-posterior."""
d = len(theta)
grad = np.zeros(d)
for i in range(d):
theta_plus = theta.copy()
theta_minus = theta.copy()
theta_plus[i] += h
theta_minus[i] -= h
grad[i] = (log_post_fn(theta_plus) - log_post_fn(theta_minus)) / (2*h)
return gradCost: function evaluations per gradient (one forward, one backward for each parameter).
Accuracy: Finite differences introduce truncation error:
Too small → roundoff error dominates (subtracting nearly equal numbers)
Too large → truncation error dominates (linear approximation breaks down)
Sweet spot: for double precision
Approach 2: Automatic Differentiation (The Modern Way)¶
Here’s the magic: What if you could compute exact gradients (up to machine precision) with the same cost as one function evaluation?
This is what automatic differentiation (autodiff) does. Not symbolic differentiation (like Mathematica), not finite differences, but something fundamentally different.
The idea: Every computer program is just a sequence of elementary operations (+, -, ×, ÷, exp, log, sin, etc.). Each operation has a known derivative. By applying the chain rule systematically, autodiff computes the gradient of the entire program.
Example: Compute gradient of
Forward pass (compute function value):
v1 = θ₁ × θ₂
v2 = exp(v1)
v3 = sin(θ₁)
f = v2 + v3Backward pass (compute gradient via chain rule):
∂f/∂v3 = 1
∂f/∂v2 = 1
∂f/∂v1 = ∂f/∂v2 × exp(v1) = exp(v1)
∂f/∂θ₂ = ∂f/∂v1 × θ₁ = θ₁ exp(θ₁θ₂)
∂f/∂θ₁ = ∂f/∂v1 × θ₂ + ∂f/∂v3 × cos(θ₁) = θ₂ exp(θ₁θ₂) + cos(θ₁)Result: Exact gradient in approximately the same time as computing itself!
In JAX, this looks like:
import jax
import jax.numpy as jnp
def log_posterior(theta):
# Your likelihood and prior computations here
return log_like + log_prior
# Get gradient function automatically
grad_log_posterior = jax.grad(log_posterior)
# Use it
theta = jnp.array([1.0, 2.0])
gradient = grad_log_posterior(theta) # Exact gradient, fast!That’s it. JAX (and PyTorch, TensorFlow) automatically computes gradients for you. No finite differences, no hand-coded derivatives.
Why is autodiff transformative?
Speed: Gradient computation is no longer the bottleneck. For parameters, autodiff is ~1000× faster than finite differences.
Accuracy: No numerical error from finite differences. Gradients are exact (up to floating-point precision).
Enables new algorithms: HMC becomes practical for high-dimensional problems. Neural networks with billions of parameters become trainable.
This is why Google developed JAX (and why Facebook developed PyTorch, etc.): Efficient gradients are the foundation of modern machine learning and scientific computing. Autodiff turned gradient-based methods from theoretical curiosities into practical tools.
What This Means for MCMC¶
Random walk Metropolis-Hastings: Proposes blindly, uses only (the posterior value).
Gradient-based MCMC (HMC): Proposes using (the posterior gradient).
Information gain: A gradient tells you pieces of information (one per parameter dimension). Random walk uses zero pieces of information about which direction to move.
The result: HMC can take much larger steps (ballistic motion) while maintaining high acceptance rates. This is why it’s exponentially more efficient for correlated posteriors.
Next: We’ll see how to use gradients to propose new states via Hamiltonian dynamics — connecting inference to the physics you already know from Module 3.
Part 3: Hamiltonian Monte Carlo — Physics as Inference Engine (🔴 Essential)¶
The Profound Connection: Sampling Is a Dynamical System Problem¶
Here’s a question that seems philosophical but turns out to be deeply practical:
If we want to sample from a distribution , what physical system has that distribution as its equilibrium state?
This is the key insight behind Hamiltonian Monte Carlo. Instead of hand-crafting proposals, let physics do it for you.
The answer: A system with Hamiltonian:
where:
are the parameters we want to sample (like positions in physics)
are auxiliary momentum variables (like velocities in physics)
is the “potential energy” (negative log-posterior)
is the “kinetic energy” (momentum term)
is a “mass matrix” (typically identity: )
Why this works: The joint distribution over is:
Notice that this factors:
where:
(our target posterior!)
(just a Gaussian)
The magic: If we sample from the joint distribution , then the marginal distribution of is exactly !
Strategy:
Introduce auxiliary momentum variables
Sample from via Hamiltonian dynamics
Throw away the momenta , keep the positions
→ Samples from !
Why introduce momentum at all? Because Hamiltonian dynamics lets us efficiently explore the joint space , and marginalizing over gives us samples from our target .
Hamilton’s Equations in Parameter Space¶
In classical mechanics, Hamilton’s equations describe how positions and momenta evolve:
For HMC, replace and compute the derivatives:
Look at the second equation carefully: The force on momentum is the gradient of the log-posterior! This is how gradients enter HMC — as forces driving the dynamics.
Physical interpretation:
Potential energy : Regions of high posterior probability → low potential → valleys
Momenta : Give parameters “inertia” to coast through low-probability regions
Dynamics: Ballistic motion that follows the posterior’s geometry
Compare to N-body:
N-body potential:
N-body force:
HMC potential:
HMC force:
Same structure, different interpretation!
The Leapfrog Integrator (Again!)¶
To simulate Hamiltonian dynamics, we need to discretize Hamilton’s equations. You already did this in Project 2!
The leapfrog integrator for N-body:
# Half-step velocity update
v = v + (dt/2) * acceleration(r)
# Full-step position update
r = r + dt * v
# Half-step velocity update
v = v + (dt/2) * acceleration(r)The leapfrog integrator for HMC:
# Half-step momentum update
p = p + (epsilon/2) * grad_log_posterior(theta)
# Full-step position update
theta = theta + epsilon * p # Assuming M = I
# Half-step momentum update
p = p + (epsilon/2) * grad_log_posterior(theta)It’s the same algorithm! Just different variable names:
| N-body (Project 2) | HMC (Now) |
|---|---|
| Position | Parameters |
| Velocity | Momentum |
| Acceleration | Gradient |
| Timestep | Step size |
Why leapfrog? Recall from Project 2:
Symplectic: Preserves phase space volume (Liouville’s theorem)
Time-reversible: Can run dynamics forward then backward to return to start
Energy-conserving: Hamiltonian is conserved to error
These properties make leapfrog ideal for HMC:
Symplectic → satisfies detailed balance (reversibility condition)
Energy-conserving → acceptance rate stays high (small )
The Complete HMC Algorithm¶
Now we can write down the full algorithm. Each HMC iteration has two phases:
Phase 1: Hamiltonian Dynamics (generate proposal)
Sample fresh momentum:
Store current state:
Simulate dynamics for leapfrog steps with step size :
Result:
Negate momentum (for detailed balance):
Phase 2: Metropolis Acceptance (accept or reject proposal)
Compute Hamiltonian change:
Accept with probability:
If accepted:
If rejected:
Key insight: If the leapfrog integration were perfect (), we’d have → → 100% acceptance! But finite introduces discretization error, so .
Trade-off: Smaller → better energy conservation → higher acceptance, but need more leapfrog steps to explore. Larger → fewer steps needed but lower acceptance. Sweet spot: -90%.
Why HMC Works: Energy Conservation and Acceptance¶
The acceptance probability depends on :
If is small, acceptance is high. If is large, acceptance is low.
What determines ? Discretization error in the leapfrog integrator. Recall from Project 2:
Leapfrog conserves energy exactly for harmonic oscillators
For general potentials, energy error is
Accumulated error over steps:
Design goals:
Keep small enough that (high acceptance)
Make large enough that trajectory explores far (low autocorrelation)
Balance: should be ~1 autocorrelation length in parameter space
Typical values:
Acceptance rate: 60-90% (much higher than random walk M-H’s ~23% optimal!)
Autocorrelation: -10 steps (vs ~100s for random walk)
ESS: (vs for random walk)
Why is HMC so much better? Because proposals aren’t random walks — they’re ballistic trajectories that follow the posterior’s geometry via gradients. You’re surfing the ridge instead of stumbling around it.
Part 4: HMC in Action — The Twisted Gaussian (🔴 Essential)¶
Seeing Is Believing: Visual Comparison¶
Let’s apply HMC to the twisted Gaussian from Part 1 and compare it to random walk Metropolis-Hastings. This will make the efficiency gain visceral.
Problem setup: 2D posterior with strong correlation ()
Target: Generate 2,000 samples from this posterior.
Methods compared:
Random walk Metropolis-Hastings (from Part 3)
Hamiltonian Monte Carlo (what we just learned)
Results: Trace Plots¶
Random Walk M-H:
Proposal: (isotropic Gaussian)
Acceptance rate: 55%
Trace plots show high autocorrelation (slow mixing)
Takes ~300 steps to move one autocorrelation length
HMC:
Step size:
Trajectory length: steps
Acceptance rate: 87%
Trace plots show rapid mixing (ballistic exploration)
Takes ~5 steps to move one autocorrelation length
Visual result: HMC trace plots look like white noise (good!), while M-H traces show clear structure (bad!).
[FIGURE: Side-by-side trace plots for and , M-H vs HMC]
Results: Trajectory Visualization¶
Random walk M-H trajectories: Look like drunk particles bouncing around. Lots of rejected proposals perpendicular to ridge. Slow progress along ridge.
HMC trajectories: Smooth arcs following the ridge. Momentum carries parameters along high-probability regions. Far fewer rejected proposals.
[FIGURE: 2D posterior with overlaid trajectories showing M-H (random walk) vs HMC (ballistic)]
Results: Autocorrelation Functions¶
Random Walk M-H: ACF decays slowly, steps
HMC: ACF decays rapidly, steps
ESS for 2,000 samples:
M-H: ESS ≈ 8 (you wasted 99.6% of your samples!)
HMC: ESS ≈ 500 (25% efficiency, 60× better!)
[FIGURE: ACF plots for both methods]
Results: Corner Plot¶
Corner plots show the 2D posterior as a contour map.
Random Walk M-H: Even after 10,000 samples, posterior looks patchy (under-explored)
HMC: After 2,000 samples, posterior is smooth and well-resolved
[FIGURE: Corner plots comparing M-H (10k samples) vs HMC (2k samples)]
The conclusion: HMC is orders of magnitude more efficient for correlated posteriors. This isn’t theoretical — it’s a practical game-changer.
Tuning HMC: Step Size and Trajectory Length¶
HMC has two hyperparameters to tune: (step size) and (trajectory length). Let’s understand how to set them.
Step Size : The Energy Conservation Trade-off¶
Too small ():
✅ Perfect energy conservation → → acceptance rate ~100%
❌ Tiny steps → need huge to explore → computationally expensive
Too large ():
❌ Poor energy conservation → → acceptance rate ~0%
❌ All proposals rejected → no exploration!
Sweet spot ( tuned):
✅ Small enough that → acceptance 60-90%
✅ Large enough that moderate steps explore well
Rule of thumb: Tune to achieve 60-90% acceptance during warmup
Adaptive tuning (used by NUTS, Stan, PyMC): During warmup, adjust via dual averaging:
If acceptance too high → increase (take bigger steps)
If acceptance too low → decrease (take smaller steps)
Converges to optimal automatically
Trajectory Length : How Far to Simulate¶
Too short ():
Proposals very close to current state (like small M-H proposal)
High autocorrelation → slow mixing
Too long ():
Trajectory curves back toward starting point (U-turn!)
Wasted computation (revisiting already-sampled regions)
Sweet spot ( tuned):
Trajectory travels approximately one autocorrelation length in parameter space
Roughly:
Rule of thumb: Start with -50, adjust based on ACF
The U-turn problem: For very long trajectories, Hamiltonian motion eventually curves back. This is inefficient — we want to stop right before the U-turn. This is what NUTS automates!
figure-md - Unknown Directive
figure-md - Unknown Directive:align: center <img src="figures/04-mod5-part4-fig2-parameter-grid.png" alt="HMC parameter tuning grid" width="90%"> **Figure 4: HMC Parameter Tuning Landscape.** Grid search over step size (ε) and trajectory length (L) reveals the efficiency landscape. Gray shading shows Effective Sample Size (ESS)—darker is better. Black contours mark ESS values, while blue dashed lines show acceptance rate contours. The green thick contour marks the optimal region (ESS > 80% of maximum). The sweet spot lies around ε ≈ 0.1-0.2 and L ≈ 20-40, achieving both high acceptance (65-80%) and maximum ESS. This figure demonstrates why tuning matters: poorly chosen parameters can reduce efficiency by 10×. Modern samplers (NUTS, Stan) automatically find this sweet spot during warmup.
HMC Diagnostics: What to Check¶
Beyond the standard MCMC diagnostics (trace plots, R-hat, ACF), HMC has specific diagnostics:
1. Energy Conservation¶
Plot over iterations:
plt.plot(delta_H_history)
plt.axhline(0, color='red', linestyle='--', label='Perfect conservation')
plt.ylabel('ΔH per trajectory')
plt.xlabel('Iteration')
plt.title('Energy Conservation Check')What to look for:
should be small (< 1 for most samples)
No systematic drift
Occasional large spikes are okay (will be rejected)
If is consistently large: Step size is too big. Decrease it.
2. Acceptance Rate¶
Target: 60-90% acceptance
Too high (>95%): Step size too small, not exploring efficiently
Too low (<50%): Step size too large, energy conservation poor
Just right (60-90%): Step size well-tuned
3. Divergences¶
Divergence: When is extremely large (>> 1), indicating numerical instability. Often occurs in regions of high posterior curvature.
What to do:
Decrease step size
Use better mass matrix (adapt to posterior covariance)
Reparameterize model (remove pathological geometry)
Divergences are warnings: Your sampler is struggling in some regions of parameter space. Don’t ignore them!
4. Standard MCMC Diagnostics¶
Don’t forget the diagnostics from Part 3! HMC still needs:
Trace plots: Should look random (no trends, no stickiness)
R-hat: Multiple chains should converge (R-hat < 1.1)
Effective sample size: Should be substantial (ESS/N > 0.1 is good for HMC)
Corner plots: Check for multimodality or unexpected correlations
Part 5: The No-U-Turn Sampler (NUTS) (🟡 Important)¶
The Problem: Fixed Trajectory Length Is Suboptimal¶
HMC requires choosing (number of leapfrog steps). But the optimal depends on where you are in parameter space!
In tight regions (high curvature, small typical set):
Short trajectories () explore efficiently
Long trajectories () waste computation (dynamics curve back quickly)
In broad regions (low curvature, large typical set):
Long trajectories () needed to explore far
Short trajectories () have high autocorrelation (barely move)
The dilemma: There’s no single that’s optimal everywhere. You need adaptive trajectory length.
This is what the No-U-Turn Sampler (NUTS) solves.
The No-U-Turn Criterion: When to Stop¶
Key insight: Keep simulating Hamiltonian dynamics until the trajectory starts to double back (“U-turn”). Then stop.
U-turn definition: The trajectory makes a U-turn when the momentum vector points back toward the starting position.
Mathematically: Let be the initial state and be the current state after leapfrog steps.
U-turn occurs when:
Intuition: The displacement vector points from start to current position. Momentum points in the current velocity direction. If their dot product is negative, you’re moving back toward the start → U-turn!
Visual analogy: Imagine a ball rolling on a curved surface:
Starts at bottom of valley, rolls uphill (momentum carries it)
Reaches maximum height, momentarily stops
Starts rolling back down (U-turn!)
→ Stop simulating right before the U-turn (you’ve explored as far as possible in this direction)
figure-md - Unknown Directive
figure-md - Unknown Directive:align: center <img src="figures/04-mod5-part4-fig3-uturn-criterion.png" alt="NUTS U-turn detection criterion" width="95%"> **Figure 5: NUTS U-turn Detection — Knowing When to Stop.** Three snapshots of a single HMC trajectory evolving through parameter space. *Left:* Before U-turn (step 13)—displacement from start (black arrow) and momentum (yellow arrow) point in similar directions (angle 34°), dot product is positive, no U-turn detected. *Middle:* U-turn detected (step 27)—angle reaches 91°, dot product becomes negative (-0.04), NUTS would stop here. *Right:* After U-turn (step 47)—trajectory has doubled back (angle 133°), continuing wastes computation. The criterion $(θ_t - θ_0) · p_t < 0$ provides a simple, effective test for when the trajectory stops being productive. This automatic stopping is what makes NUTS "no-U-turn"—it explores as far as useful, then stops before wasting compute on redundant sampling.
The NUTS Algorithm (Conceptual Overview)¶
NUTS adaptively grows the trajectory until a U-turn is detected. Here’s the high-level idea:
NUTS procedure:
Initialize: Sample momentum , set trajectory to current state
Build trajectory recursively:
Simulate forward in time (positive direction): Run leapfrog to extend trajectory
Simulate backward in time (negative direction): Run leapfrog in reverse
Double trajectory length each iteration:
Stop when U-turn detected
Select proposal: Randomly choose a state from the trajectory (weighted by posterior probability)
Metropolis acceptance: Accept/reject based on average over trajectory
Key features:
No need to specify : Algorithm automatically determines trajectory length
Explores adaptively: Short trajectories in tight regions, long trajectories in broad regions
Efficient: Stops right before wasting computation on U-turns
The result: Fully automatic HMC with minimal tuning. This is why NUTS is the default in Stan and PyMC.
NUTS Advantages Over Vanilla HMC¶
| Aspect | HMC (fixed ) | NUTS (adaptive) |
|---|---|---|
| User tuning | Must choose manually | Automatic adaptation |
| Efficiency | Suboptimal in many regions | Near-optimal everywhere |
| Robustness | Sensitive to choice | Works across diverse posteriors |
| Complexity | Simple algorithm | More complex (recursive doubling) |
When to use NUTS:
Production inference (Stan, PyMC, NumPyro)
Complex posteriors where optimal varies
When you want “it just works” behavior
When vanilla HMC is okay:
Educational purposes (understanding the algorithm)
Simple posteriors where good is obvious
Implementing from scratch (NUTS is more complex)
For Project 4: Start with vanilla HMC (easier to understand and debug). Extensions could explore NUTS via NumPyro.
The Complete Picture: Comparing All Three Methods¶
Now that you’ve seen the full progression from random walks to adaptive Hamiltonian flow, let’s compare all three methods side-by-side:
| Aspect | Metropolis-Hastings | HMC (vanilla) | NUTS |
|---|---|---|---|
| Core idea | Random walk proposals | Hamiltonian dynamics | Adaptive HMC |
| Uses gradients? | ❌ No | ✅ Yes | ✅ Yes |
| Proposal mechanism | Leapfrog integration | Adaptive leapfrog with doubling | |
| Tuning parameters | Step size | Step size , trajectory length | Step size (auto-tuned) |
| Typical acceptance | 23-50% | 60-90% | 65-95% |
| Autocorrelation | High ( 100-1000) | Low ( 3-10) | Very low ( 2-5) |
| ESS efficiency | ~1-5% of samples | ~10-30% of samples | ~20-50% of samples |
| Cost per iteration | Very cheap (1 eval) | Moderate ( gradient evals) | Moderate-expensive (adaptive ) |
| Best for | Simple, uncorrelated posteriors | Correlated posteriors (d < 100) | General purpose, production |
| Worst for | Strongly correlated, high-d | When gradients unavailable | Low-d (overhead not worth it) |
| Requires derivatives? | ❌ No | ✅ Yes (finite diff or autodiff) | ✅ Yes (autodiff recommended) |
| Robustness | Works everywhere (slowly) | Sensitive to , | Very robust |
| Implementation complexity | Simple (~20 lines) | Moderate (~50 lines) | Complex (~200 lines) |
| When posteriors multi-modal | Can work (eventually) | Can get stuck | Can get stuck |
| Scales to high-d? | ❌ No (d > 20 struggles) | ⚠️ Moderate (d ~ 100) | ✅ Yes (d ~ 1000s with autodiff) |
| Professional tools | emcee (astronomy) | Custom implementations | Stan, PyMC, NumPyro |
Key insights from the comparison:
There’s no free lunch: HMC/NUTS require gradients and careful energy conservation, but reward you with exponentially better efficiency for hard problems.
Cost vs efficiency trade-off:
M-H: Cheap iterations, but need many → Total cost high for correlated posteriors
HMC/NUTS: Expensive iterations (gradient evals), but need far fewer → Total cost lower
When M-H is actually fine: If your posterior is simple (uncorrelated, low-dimensional), M-H works perfectly well. Don’t over-engineer!
Why NUTS won: By eliminating manual tuning of , NUTS made gradient-based MCMC accessible to non-experts. This is why it’s the default in Stan and PyMC.
The autodiff revolution: NUTS + JAX autodiff scales to thousands of parameters. This combination powers modern Bayesian deep learning and inference in complex simulators.
Practical decision tree:
Is your posterior correlated or high-dimensional?
│
├─ No (simple, uncorrelated, d < 10)
│ └─→ Use Metropolis-Hastings
│ └─→ Simple, fast, good enough
│
└─ Yes (correlated or d > 10)
│
├─ Can you compute gradients?
│ │
│ ├─ No (black-box likelihood, discrete parameters)
│ │ └─→ Use ensemble MCMC (emcee)
│ │ └─→ No gradients, but uses multiple walkers
│ │
│ └─ Yes
│ │
│ ├─ Learning/prototyping?
│ │ └─→ Implement vanilla HMC
│ │ └─→ Understand the algorithm deeply
│ │
│ └─ Production/research?
│ └─→ Use NUTS (Stan/PyMC/NumPyro)
│ └─→ Automatic tuning, battle-tested
│
└─ Is posterior multimodal?
└─→ All methods struggle!
└─→ Try: Tempered MCMC, nested sampling, or VIThe bottom line: For Project 4 (cosmology with correlated and ), HMC will dramatically outperform M-H. You’ll see this firsthand!
Beyond NUTS: The Broader MCMC Landscape¶
NUTS is state-of-the-art for general-purpose MCMC, but it’s not the end of the story. Here’s a glimpse of other advanced methods:
Riemannian Manifold HMC: Adapts the mass matrix locally based on posterior curvature (Hessian). Especially powerful for posteriors with highly varying scales.
Ensemble MCMC (affine-invariant, emcee): Multiple walkers explore together. No gradients needed! Good for multimodal posteriors. Used widely in astrophysics.
Variational Inference (VI): Approximate posterior by optimization (not sampling). Much faster than MCMC but less accurate. Trade-off: speed vs exactness.
Neural network-assisted MCMC: Learn proposal distributions via deep learning. State-of-the-art for extremely high dimensions (thousands of parameters).
When to use what:
Simple posteriors (d < 10): Random walk M-H works fine
Correlated posteriors (d = 10-100): HMC or NUTS
High dimensions (d = 100-1000): HMC with JAX on GPU
Very high dimensions (d > 1000): VI or hybrid methods
Multimodal posteriors: Ensemble methods or tempered MCMC
Real-time inference: VI or approximate methods
The frontier: Combining MCMC with machine learning (learning optimal proposals, amortized inference, simulation-based inference). This is where Module 6 leads!
Part 6: JAX and the Future of Gradient-Based Inference (🟡 Important)¶
Why JAX Matters¶
You’ve seen that HMC needs gradients. Computing gradients efficiently is the bottleneck for scaling to high dimensions. This is where JAX transforms the game.
JAX is Google’s library for high-performance numerical computing. It brings three superpowers:
Automatic Differentiation (Autodiff): Compute exact gradients for any Python function via
jax.grad()Just-In-Time (JIT) Compilation: Compile Python to optimized machine code via
jax.jit()Hardware Acceleration: Same code runs on CPU, GPU, TPU with no changes
Why Google developed JAX: Modern machine learning requires efficient gradients for training neural networks with billions of parameters. JAX enables this at scale.
Why you should care: JAX makes gradient-based inference (HMC, VI, ML) practical for high-dimensional problems. In Module 6, you’ll use JAX for physics-informed neural networks and differentiable simulations.
JAX Autodiff: A Quick Tour¶
The power of autodiff: Recall from Part 2 that automatic differentiation computes exact gradients with the cost of one function evaluation (not like finite differences).
In JAX, getting gradients is trivial:
import jax
import jax.numpy as jnp
# Define a function
def f(x):
return jnp.sum(x**2)
# Get its gradient (automatic!)
grad_f = jax.grad(f)
# Use it
x = jnp.array([1.0, 2.0, 3.0])
gradient = grad_f(x) # Returns [2.0, 4.0, 6.0]That’s it. JAX computes automatically by tracing the computation graph and applying the chain rule.
For HMC, this means:
# Your log-posterior function
def log_posterior(theta):
log_like = # ... your likelihood ...
log_prior = # ... your prior ...
return log_like + log_prior
# Get gradient function (one line!)
grad_log_posterior = jax.grad(log_posterior)
# Use in HMC
theta = jnp.array([0.3, 0.7]) # Current parameters
grad = grad_log_posterior(theta) # Gradient for HMCSpeed comparison (d = 100 parameters):
Finite differences: ~200 likelihood evaluations per gradient → slow
JAX autodiff: ~1 likelihood evaluation per gradient → 200× faster!
For high-dimensional problems, this is the difference between hours and minutes.
JAX JIT: Make Code Fast¶
Just-In-Time compilation: JAX can compile Python functions to optimized machine code (like C++ or Fortran) automatically.
Example:
@jax.jit # Compile this function!
def leapfrog_step(theta, p, epsilon, grad_fn):
p = p + (epsilon/2) * grad_fn(theta)
theta = theta + epsilon * p
p = p + (epsilon/2) * grad_fn(theta)
return theta, p
# First call: JAX compiles the function (takes a moment)
theta, p = leapfrog_step(theta, p, epsilon, grad_log_post)
# Subsequent calls: Run compiled code (10-100× faster!)
for _ in range(1000):
theta, p = leapfrog_step(theta, p, epsilon, grad_log_post)Speedup: 10-100× for numerical code (loops, array operations). The more complex your code, the bigger the speedup.
GPUs for free: Change jax.jit to run on GPU? Nothing! JAX handles device placement automatically. Your HMC code runs on GPU without modification.
The JAX Ecosystem for Inference¶
NumPyro: Probabilistic programming in JAX. Uses NUTS under the hood.
import numpyro
from numpyro.infer import MCMC, NUTS
def model(x, y):
# Define your Bayesian model
slope = numpyro.sample('slope', dist.Normal(0, 10))
intercept = numpyro.sample('intercept', dist.Normal(0, 10))
sigma = numpyro.sample('sigma', dist.HalfNormal(1))
mu = intercept + slope * x
numpyro.sample('y', dist.Normal(mu, sigma), obs=y)
# Run NUTS (fully automatic!)
nuts_kernel = NUTS(model)
mcmc = MCMC(nuts_kernel, num_warmup=1000, num_samples=2000)
mcmc.run(jax.random.PRNGKey(0), x=x_data, y=y_data)
# Get results
samples = mcmc.get_samples()BlackJAX: Lower-level MCMC library (like your from-scratch implementation, but production-ready). Modular design—mix and match samplers, diagnostics, adaptation strategies.
Why learn JAX now: Module 6 uses JAX extensively for differentiable physics simulations and physics-informed neural networks. Getting comfortable with JAX syntax now pays dividends later.
The Big Picture: Where Inference Is Heading¶
The convergence of fields: Gradient-based inference connects:
Classical statistics: Bayesian inference, MCMC
Physics: Hamiltonian dynamics, conservation laws, symplectic integration
Machine learning: Autodiff, optimization, neural networks
High-performance computing: GPUs, JIT compilation, parallel sampling
Modern inference workflow:
Write model in JAX (probabilistic program)
Automatic gradients via
jax.grad()JIT-compile for speed
Run NUTS on GPU (handles tuning automatically)
Diagnose and visualize (arviz, corner.py)
This is how professionals do inference today. You’re learning the cutting edge.
Looking ahead:
Module 6 (Physics-Informed Learning): Use JAX to build differentiable simulators, train neural networks to solve PDEs, combine data and physics
Research frontiers: Simulation-based inference, neural posterior estimation, amortized MCMC
Your future: These tools will be part of your computational toolkit for any domain — astrophysics, climate science, biology, finance, ML research
The glass-box journey: You built MCMC from scratch (Part 3), understood the physics (Part 4), implemented HMC, and now see the professional tools (JAX, NumPyro). You’re not a black-box user — you’re a computational scientist who understands the foundations.
Part 7: Synthesis and Looking Forward¶
What We’ve Learned: The Journey from Random Walks to Hamiltonian Flow¶
Part 3 (Metropolis-Hastings):
Random walk proposals: Universal but sometimes inefficient
Works well for simple posteriors
Struggles with correlated, high-dimensional problems
Part 4 (HMC):
Use gradients to guide proposals
Reframe sampling as a physics problem (Hamiltonian dynamics)
Ballistic trajectories follow posterior geometry
Massive efficiency gains (10-100× fewer samples needed)
The connections:
| Concept | Module 1 | Module 3 | Module 5 (Part 3) | Module 5 (Part 4) |
|---|---|---|---|---|
| What we sample | Random variables | Microstates | Parameters (random walk) | Parameters (Hamiltonian) |
| Target distribution | Population | Boltzmann | Posterior p(θ|D) | Posterior p(θ|D) |
| How we sample | IID draws | Molecular dynamics | Markov chain (M-H) | Markov chain (HMC) |
| Key property | Law of Large Numbers | Ergodicity | Detailed balance | Hamiltonian conservation |
| Dynamics | Static | Newton’s laws | Stochastic jumps | Leapfrog integration |
| Efficiency | Perfect (independent) | Limited by collision rate | Limited by correlation | High (gradient-guided) |
The profound unity: Whether you’re:
Sampling from a distribution to estimate its mean (Module 1)
Simulating molecules exploring phase space (Module 3)
Running MCMC to quantify parameter uncertainty (Module 5 Part 3)
Using HMC to sample posteriors efficiently (Module 5 Part 4)
You’re using variations of the same mathematical structure: stochastic or deterministic processes converging to equilibrium distributions, with time averages equaling ensemble averages.
This is why physics is powerful. The same principles work everywhere — from atoms to galaxies to probability distributions.
Where This Leads: Your Path Forward¶
Immediate: Project 4
You’ll apply HMC to real cosmology data:
Measure (matter density) and (Hubble constant) from Type Ia supernovae
Implement HMC using your Project 2 leapfrog integrator
Compare efficiency: M-H vs HMC (experience the speedup yourself)
Use the same data that won the 2011 Nobel Prize for discovering dark energy
Skills you’ll use:
Bayesian model specification (likelihood + priors)
Gradient computation (finite differences or JAX)
HMC implementation (adapting your leapfrog code)
Convergence diagnostics (trace plots, R-hat, ACF, energy conservation)
Result visualization (corner plots, credible intervals)
Near future: Module 6
Physics-informed learning:
Build differentiable physics simulators in JAX
Train neural networks to solve PDEs
Combine data-driven and physics-based modeling
Learn inverse problems (infer parameters from observations)
Long term: Your research
These tools are universally applicable:
Astrophysics: Exoplanet characterization, stellar populations, cosmological inference
Climate science: Parameter estimation in Earth system models
Particle physics: Analyzing LHC collision data
Biology: Fitting dynamical models to experimental data
Machine learning: Bayesian deep learning, uncertainty quantification
The glass-box philosophy in action: You didn’t just learn to use Stan or PyMC. You:
Understood why gradients are powerful (calculus → optimization → sampling)
Derived HMC from first principles (Hamiltonian mechanics)
Implemented it yourself (can debug when things break)
Connected it to physics you already know (Project 2, Module 3)
Know when to use it (correlated posteriors, high dimensions)
Understand the tools (JAX, autodiff, professional software)
→ You’re not a black-box user. You’re a computational scientist.
Self-Assessment Rubric¶
Before moving to Project 4, assess your mastery of Part 4. Be honest — identifying gaps now makes the project more productive.
Level 1: Conceptual Understanding¶
Can you explain the ideas to someone else?
Developing: I understand that HMC is “better” than M-H but can’t articulate why
Proficient: I can explain why random walk M-H fails for correlated posteriors (isotropic proposals vs anisotropic posterior geometry) and why gradients help (they point uphill)
Advanced: I can explain the full conceptual chain: Gradients → Hamiltonian dynamics → Leapfrog integration → High acceptance rates → Efficient sampling, and justify each step with physics/math intuition
Level 2: Physics Connection¶
Can you see the unity across modules?
Developing: I see that HMC uses physics concepts but the connection feels superficial
Proficient: I recognize that HMC is literally Hamiltonian dynamics in parameter space, using the same leapfrog integrator from Project 2, just with different interpretation of variables
Advanced: I can articulate the deep analogies: (Position, momentum) ↔ (parameters, auxiliary variables); gravitational potential ↔ negative log-posterior; energy conservation ↔ high acceptance; detailed balance ↔ time-reversibility. I see this as one mathematical structure applied to different domains.
Level 3: Mathematical Foundations¶
Can you derive key results?
Developing: I can follow the derivations when reading but would struggle to reproduce them
Proficient: I can derive Hamilton’s equations for HMC from the Hamiltonian , show that the joint distribution marginalizes to , and explain why leapfrog preserves the structure needed for detailed balance
Advanced: I can derive the acceptance criterion from energy conservation, explain the role of momentum negation in detailed balance, and understand the U-turn criterion mathematically (dot product test)
Level 4: Implementation Skills¶
Can you code HMC from scratch?
Developing: I can use provided HMC code but would struggle to implement it independently
Proficient: I can implement HMC for a simple problem (like the twisted Gaussian) by adapting my Project 2 leapfrog integrator, computing gradients via finite differences, and adding the Metropolis acceptance step
Advanced: I can implement HMC for complex problems, debug issues (poor acceptance, divergences), tune hyperparameters (, ), and potentially implement a simplified NUTS or use JAX autodiff
Level 5: Diagnostic Expertise¶
Can you tell if your HMC sampler is working?
Developing: I know I should check diagnostics but I’m not sure what “good” looks like
Proficient: I can create and interpret energy conservation plots (ΔH should be small), check acceptance rates (60-90% target), compute standard diagnostics (R-hat, ACF, ESS), and recognize when step size needs adjustment
Advanced: I can diagnose subtle problems (divergences indicating stiff posterior regions, systematic energy drift indicating bad step size, U-turns indicating inefficient trajectory length), adjust sampling strategy accordingly, and compare HMC vs M-H efficiency quantitatively
Level 6: Tool Awareness¶
Do you understand the modern ecosystem?
Developing: I’ve heard of JAX/Stan/PyMC but don’t really know what they do
Proficient: I understand that JAX provides autodiff (exact gradients cheaply) and JIT compilation (speed), that NUTS is adaptive HMC (auto-tunes and ), and that Stan/PyMC/NumPyro are high-level interfaces that make inference “just work” for users
Advanced: I can use NumPyro to run NUTS on a simple problem, understand the trade-offs (finite differences vs autodiff, vanilla HMC vs NUTS, MCMC vs VI), and know when to reach for different tools based on problem characteristics
Level 7: Connections and Transfer¶
Can you see how HMC fits into the bigger picture?
Developing: I see HMC as an isolated technique for Bayesian inference
Proficient: I understand how HMC connects to Module 3 (Hamiltonian dynamics, symplectic integration) and Module 1 (CLT, ergodicity), and why gradient-based methods are central to machine learning (optimization, training neural networks)
Advanced: I can articulate how gradient-based inference connects physics, statistics, and ML; explain why autodiff revolutionized deep learning and computational science; and identify when gradient-based methods are appropriate vs when alternatives (ensemble MCMC, VI) are better
Reflection: Which levels feel solid? Which need more work? Use this to guide your Project 4 strategy and what to review.
Conceptual Checkpoints (Throughout)¶
Looking Ahead: Project 4 and the Universe¶
You now have the tools to measure the contents of the universe. Seriously.
Project 4 preview: Type Ia supernovae cosmology
Data: ~600 SNe distances vs redshifts (publicly available)
Model: from Friedmann equations (cosmological distance-redshift relation)
Inference: Use HMC to sample
Result: Measure how much matter vs dark energy the universe contains
This is real science. The same analysis won the 2011 Nobel Prize in Physics for discovering cosmic acceleration (dark energy). You’re not just practicing methods—you’re doing frontier research with public data.
What you’ll experience:
M-H struggles: Correlated posterior, slow mixing, need 500k samples
HMC succeeds: Same posterior, 20k samples sufficient, 25× faster
The “aha!” moment: When you see HMC trace plots vs M-H side-by-side and viscerally understand why gradients matter
Skills you’ll cement:
Bayesian model building (forward model + likelihood + priors)
Gradient computation (finite differences or JAX extension)
HMC implementation (adapting Project 2 leapfrog)
Convergence diagnostics (energy conservation, acceptance, R-hat, ESS)
Scientific inference (credible intervals, parameter correlations, physical interpretation)
Beyond this course: These methods generalize to any inference problem — exoplanets, stellar evolution, black hole mergers, climate models, drug discovery, economics, you name it. Gradient-based MCMC is a universal tool.
Module 6 awaits: Physics-informed neural networks, differentiable simulations, combining data and theory. JAX becomes central. The journey continues from inference to learning.
Now go forth and sample the universe. 🌌
Further Resources (Optional)¶
For deeper understanding:
Neal (2011): “MCMC using Hamiltonian dynamics” — The definitive review article, clear and comprehensive
Betancourt (2017): “A Conceptual Introduction to Hamiltonian Monte Carlo” — Excellent conceptual explanations with visualizations
Hoffman & Gelman (2014): “The No-U-Turn Sampler” — Original NUTS paper
For implementation:
JAX documentation: https://
jax .readthedocs .io — Official tutorials and examples NumPyro documentation: https://num.pyro.ai — Probabilistic programming in JAX
Stan documentation: https://mc-stan.org — Alternative to NumPyro, C++ backend
For inspiration:
MacKay: Information Theory, Inference, and Learning Algorithms — Beautiful connections between physics, information theory, and ML
Murphy: Probabilistic Machine Learning: Advanced Topics — Modern perspective on inference and learning
Historical:
Duane et al. (1987): “Hybrid Monte Carlo” — Original HMC paper from lattice QCD
Metropolis et al. (1953): “Equation of State Calculations by Fast Computing Machines” — Where it all began
Next: Project 4 — Measuring dark energy with HMC 🚀