use std::alloc::{GlobalAlloc, Layout, System};
use std::error::Error;
use std::hint::black_box;
use std::num::NonZeroUsize;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::time::Instant;
use owalnuts::walnutpie::{
DiagonalMass, KernelTuning, MultiChainOutput, RunConfig, Target, TargetError,
TargetEvaluationAdmissionLimit, TargetEvaluationBudget, WarmupConfig,
sample_chains_with_target_budget,
};
struct CountingAllocator;
static ALLOCATIONS: AtomicUsize = AtomicUsize::new(0);
static ALLOCATED_BYTES: AtomicUsize = AtomicUsize::new(0);
unsafe impl GlobalAlloc for CountingAllocator {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
ALLOCATIONS.fetch_add(1, Ordering::Relaxed);
ALLOCATED_BYTES.fetch_add(layout.size(), Ordering::Relaxed);
unsafe { System.alloc(layout) }
}
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
unsafe { System.dealloc(ptr, layout) }
}
unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 {
ALLOCATIONS.fetch_add(1, Ordering::Relaxed);
ALLOCATED_BYTES.fetch_add(new_size, Ordering::Relaxed);
unsafe { System.realloc(ptr, layout, new_size) }
}
}
#[global_allocator]
static GLOBAL: CountingAllocator = CountingAllocator;
const FUNNEL_DIMENSION: usize = 10;
struct Funnel;
impl Target for Funnel {
fn dimension(&self) -> usize {
FUNNEL_DIMENSION
}
fn log_density_gradient(
&self,
position: &[f64],
gradient: &mut [f64],
) -> Result<f64, TargetError> {
let omega = position[0];
let inverse_variance = (-omega).exp();
if !inverse_variance.is_finite() {
return Err(TargetError::recoverable("exp(-omega) overflowed"));
}
let sum_squares: f64 = position[1..].iter().map(|x| x * x).sum();
let tail = (FUNNEL_DIMENSION - 1) as f64;
gradient[0] = -omega / 9.0 - 0.5 * tail + 0.5 * inverse_variance * sum_squares;
for (g, x) in gradient[1..].iter_mut().zip(&position[1..]) {
*g = -inverse_variance * x;
}
Ok(-omega * omega / 18.0 - 0.5 * tail * omega - 0.5 * inverse_variance * sum_squares)
}
}
struct Gaussian(usize);
impl Target for Gaussian {
fn dimension(&self) -> usize {
self.0
}
fn log_density_gradient(
&self,
position: &[f64],
gradient: &mut [f64],
) -> Result<f64, TargetError> {
let mut value = 0.0;
for (g, x) in gradient.iter_mut().zip(position) {
*g = -x;
value += x * x;
}
Ok(-0.5 * value)
}
}
const LOG_2PI: f64 = 1.837_877_066_409_345_3;
const SCHOOL_Y: [f64; 8] = [28., 8., -3., 7., -1., 1., 18., 12.];
const SCHOOL_SE: [f64; 8] = [15., 10., 16., 11., 9., 11., 10., 18.];
fn normal_log_density(x: f64, mean: f64, sd: f64) -> f64 {
-0.5 * LOG_2PI - sd.ln() - 0.5 * ((x - mean) / sd).powi(2)
}
struct EightSchools;
impl Target for EightSchools {
fn dimension(&self) -> usize {
10
}
fn log_density_gradient(&self, q: &[f64], gradient: &mut [f64]) -> Result<f64, TargetError> {
let mu = q[0];
let log_tau = q[1];
let tau = log_tau.exp();
let z = &q[2..];
let mut value = normal_log_density(mu, 0., 5.)
+ (2. / (std::f64::consts::PI * 5. * (1. + (tau / 5.).powi(2)))).ln()
+ log_tau;
gradient.fill(0.);
gradient[0] = -mu / 25.;
gradient[1] = 1. - 2. * tau * tau / (25. + tau * tau);
for j in 0..8 {
let theta = mu + tau * z[j];
let residual = SCHOOL_Y[j] - theta;
let likelihood_gradient = residual / SCHOOL_SE[j].powi(2);
value += normal_log_density(SCHOOL_Y[j], theta, SCHOOL_SE[j])
+ normal_log_density(z[j], 0., 1.);
gradient[0] += likelihood_gradient;
gradient[1] += likelihood_gradient * tau * z[j];
gradient[j + 2] = -z[j] + likelihood_gradient * tau;
}
if value.is_finite() && gradient.iter().all(|x| x.is_finite()) {
Ok(value)
} else {
Err(TargetError::new("nonfinite target evaluation"))
}
}
}
fn nz(value: usize) -> NonZeroUsize {
NonZeroUsize::new(value).expect("nonzero")
}
fn fnv(hash: &mut u64, value: u64) {
*hash ^= value;
*hash = hash.wrapping_mul(0x100_0000_01b3);
}
fn fingerprint(output: &MultiChainOutput) -> u64 {
let mut hash = 0xcbf2_9ce4_8422_2325;
for chain in output.chains() {
for value in chain.samples() {
fnv(&mut hash, value.to_bits());
}
}
hash
}
fn target_calls(output: &MultiChainOutput) -> usize {
output
.chains()
.iter()
.map(|chain| chain.telemetry().total().target_calls_total())
.sum()
}
fn target_cost_ns<T: Target>(target: &T, position: &[f64]) -> f64 {
let mut gradient = vec![0.0; target.dimension()];
let iterations = 2_000_000usize;
let start = Instant::now();
for _ in 0..iterations {
let value = target
.log_density_gradient(black_box(position), &mut gradient)
.expect("finite");
black_box(value);
black_box(&gradient);
}
start.elapsed().as_nanos() as f64 / iterations as f64
}
struct Report {
name: &'static str,
wall_ms: f64,
calls: usize,
target_ns: f64,
overhead_ns: f64,
allocations_per_call: f64,
bytes_per_call: f64,
fingerprint: u64,
}
fn run<T: Target>(
name: &'static str,
target: &T,
starts: &[Vec<f64>],
config: &RunConfig,
probe: &[f64],
repeat: usize,
) -> Result<Report, Box<dyn Error>> {
let mass = DiagonalMass::identity(nz(target.dimension()));
let target_ns = target_cost_ns(target, probe);
let mut best_wall = f64::INFINITY;
let mut best = None;
for _ in 0..repeat {
let allocations_before = ALLOCATIONS.load(Ordering::Relaxed);
let bytes_before = ALLOCATED_BYTES.load(Ordering::Relaxed);
let start = Instant::now();
let worst = config.worst_case_target_evaluations(nz(starts.len()))?;
let output = sample_chains_with_target_budget(
target,
starts,
&mass,
config,
nz(1),
TargetEvaluationAdmissionLimit::new(nz(worst)),
&TargetEvaluationBudget::new(nz(worst)),
)?;
let wall = start.elapsed();
let allocations = ALLOCATIONS.load(Ordering::Relaxed) - allocations_before;
let bytes = ALLOCATED_BYTES.load(Ordering::Relaxed) - bytes_before;
let wall_ms = wall.as_secs_f64() * 1e3;
if wall_ms < best_wall {
best_wall = wall_ms;
let calls = target_calls(&output);
best = Some(Report {
name,
wall_ms,
calls,
target_ns,
overhead_ns: (wall.as_nanos() as f64 - target_ns * calls as f64) / calls as f64,
allocations_per_call: allocations as f64 / calls as f64,
bytes_per_call: bytes as f64 / calls as f64,
fingerprint: fingerprint(&output),
});
}
}
Ok(best.expect("at least one repeat"))
}
fn main() -> Result<(), Box<dyn Error>> {
let mut repeat = 3usize;
let mut args = std::env::args().skip(1);
while let Some(arg) = args.next() {
if arg == "--repeat" {
repeat = args.next().ok_or("--repeat needs a value")?.parse()?;
}
}
let mut reports = Vec::new();
{
let tuning = KernelTuning::new(0.36, nz(10), nz(1), nz(8), 0.21)?;
let config = RunConfig::new(0, nz(1_000), 0x5eed_f0f0).with_tuning(tuning);
let starts: Vec<Vec<f64>> = [-3.0, -1.0, 1.0, 3.0]
.into_iter()
.map(|omega| {
let mut q = vec![0.0; FUNNEL_DIMENSION];
q[0] = omega;
q
})
.collect();
let mut probe = vec![0.3; FUNNEL_DIMENSION];
probe[0] = -1.0;
reports.push(run(
"funnel-10d",
&Funnel,
&starts,
&config,
&probe,
repeat,
)?);
}
{
let dimension = 100;
let tuning = KernelTuning::new(0.3, nz(8), nz(1), nz(4), 1.0)?;
let config = RunConfig::new(0, nz(1_000), 0x5eed_0100).with_tuning(tuning);
let starts: Vec<Vec<f64>> = (0..4)
.map(|chain| vec![0.5 * (chain as f64 - 1.5); dimension])
.collect();
let probe = vec![0.7; dimension];
reports.push(run(
"gaussian-100d",
&Gaussian(dimension),
&starts,
&config,
&probe,
repeat,
)?);
}
{
let tuning =
KernelTuning::new(0.3, nz(8), nz(1), nz(8), 1.0)?.with_divergence_threshold(1000.0)?;
let warmup = WarmupConfig::new(0.95)?.with_mass_adaptation(true);
let config = RunConfig::new(1_000, nz(1_000), 0x5eed_0008)
.with_tuning(tuning)
.with_warmup(warmup);
let starts: Vec<Vec<f64>> = [-2., -1., 0., 1.]
.into_iter()
.map(|log_tau| {
let mut q = vec![0.0; 10];
q[1] = log_tau;
q
})
.collect();
let probe = vec![0.2; 10];
reports.push(run(
"eight-schools",
&EightSchools,
&starts,
&config,
&probe,
repeat,
)?);
}
println!(
"{:<14} {:>10} {:>10} {:>10} {:>12} {:>10} {:>10} fingerprint",
"workload", "wall ms", "calls", "target ns", "overhead ns", "alloc/cl", "bytes/cl",
);
for report in &reports {
println!(
"{:<14} {:>10.1} {:>10} {:>10.1} {:>12.1} {:>10.2} {:>10.1} {:016x}",
report.name,
report.wall_ms,
report.calls,
report.target_ns,
report.overhead_ns,
report.allocations_per_call,
report.bytes_per_call,
report.fingerprint
);
}
Ok(())
}