use crate::rng::Pcg;
#[inline]
pub fn p_up(field: f64, beta: f64) -> f64 {
1.0 / (1.0 + (-2.0 * beta * field).exp())
}
#[inline]
pub fn draw(field: f64, beta: f64, rng: &mut Pcg) -> i8 {
if rng.f64() < p_up(field, beta) {
1
} else {
-1
}
}
#[inline]
pub fn score_dh(field: f64, beta: f64, s_new: i8) -> f64 {
let s = s_new as f64;
let arg = 2.0 * beta * field * s;
2.0 * beta * s / (1.0 + arg.exp())
}
#[inline]
pub fn delta_e(field: f64, s_i: i8) -> f64 {
2.0 * field * s_i as f64
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn zero_field_is_a_fair_coin() {
assert_eq!(p_up(0.0, 1.0), 0.5);
assert_eq!(p_up(0.0, 100.0), 0.5);
}
#[test]
fn beta_is_a_parameter_not_a_weight() {
for &(f, b) in &[(0.3, 2.0), (-1.2, 0.5), (2.5, 0.25)] {
let via_beta = p_up(f, b);
let via_weights = p_up(f * b, 1.0);
assert!((via_beta - via_weights).abs() < 1e-15, "f={f} beta={b}");
}
}
#[test]
fn colder_is_more_decided() {
let mut last = p_up(0.7, 0.1);
for &b in &[0.5, 1.0, 2.0, 8.0] {
let p = p_up(0.7, b);
assert!(p > last, "beta {b} should be more decided than the last");
last = p;
}
assert!(p_up(0.7, 1e3) > 0.999999);
}
#[test]
fn saturates_without_overflow() {
for &(f, b) in &[(1e6, 1.0), (-1e6, 1.0), (1.0, 1e6), (1.0, -1e6)] {
let p = p_up(f, b);
assert!(p.is_finite() && (0.0..=1.0).contains(&p), "f={f} beta={b} -> {p}");
}
}
#[test]
fn score_matches_a_numerical_derivative() {
let (beta, h) = (0.8, 1e-6);
for &f in &[-1.5, -0.3, 0.0, 0.4, 2.2] {
for &s in &[1i8, -1] {
let lp = |x: f64| {
let p = p_up(x, beta);
(if s > 0 { p } else { 1.0 - p }).ln()
};
let numeric = (lp(f + h) - lp(f - h)) / (2.0 * h);
let analytic = score_dh(f, beta, s);
assert!(
(numeric - analytic).abs() < 1e-6,
"f={f} s={s}: numeric {numeric} vs analytic {analytic}"
);
}
}
}
#[test]
fn draw_reproduces_the_probability() {
let mut rng = Pcg::new(42, 1);
let (f, beta) = (0.35, 1.3);
let n = 400_000;
let ups = (0..n).filter(|_| draw(f, beta, &mut rng) > 0).count();
let got = ups as f64 / n as f64;
let want = p_up(f, beta);
let tol = 3.0 * (want * (1.0 - want) / n as f64).sqrt();
assert!((got - want).abs() < tol, "got {got}, want {want}, tol {tol}");
}
#[test]
fn delta_e_agrees_with_the_acceptance_ratio() {
let (f, beta) = (0.6, 0.9);
let p = p_up(f, beta);
let ratio = p / (1.0 - p);
let de = delta_e(f, -1); assert!((ratio - (-beta * de).exp()).abs() < 1e-12);
}
}