use crate::device::z1_grid;
use crate::gibbs::Sampler;
use crate::graph::Graph;
use crate::ising::{lattice2d, onsager_m};
use crate::ledger::{Ledger, Z1_SPICE};
pub struct Sim {
graph: Box<Graph>,
sampler_state: Vec<i8>,
beta: f64,
seed: u64,
sweeps_done: u64,
ledger: Ledger,
}
impl Sim {
fn new(graph: Graph, beta: f64, seed: u64) -> *mut Sim {
let g = Box::new(graph);
let sampler = Sampler::new(&g, beta, seed);
Box::into_raw(Box::new(Sim { sampler_state: sampler.s.clone(), graph: g, beta, seed, sweeps_done: 0, ledger: Ledger::default() }))
}
}
#[no_mangle]
pub extern "C" fn ft_ising2d_new(l: u32, j: f64, beta: f64, seed: u64) -> *mut Sim {
Sim::new(lattice2d(l as usize, j), beta, seed)
}
#[no_mangle]
pub extern "C" fn ft_z1_new(w: u32, h: u32, j: f64, hb: f64, beta: f64, seed: u64) -> *mut Sim {
Sim::new(z1_grid(w as usize, h as usize, j, hb), beta, seed)
}
#[no_mangle]
pub extern "C" fn ft_sweep(sim: *mut Sim, n: u32) -> u64 {
let Some(s) = (unsafe { sim.as_mut() }) else { return 0 };
let mut smp = Sampler::new(&s.graph, s.beta, s.seed ^ s.sweeps_done.wrapping_mul(0x9E3779B97F4A7C15));
smp.s.copy_from_slice(&s.sampler_state);
for _ in 0..n {
smp.sweep(Some(&mut s.ledger));
}
s.sampler_state.copy_from_slice(&smp.s);
s.sweeps_done += n as u64;
s.sweeps_done
}
#[no_mangle]
pub extern "C" fn ft_set_beta(sim: *mut Sim, beta: f64) {
if let Some(s) = unsafe { sim.as_mut() } {
s.beta = beta;
}
}
#[no_mangle]
pub extern "C" fn ft_len(sim: *const Sim) -> u32 {
unsafe { sim.as_ref() }.map_or(0, |s| s.graph.n as u32)
}
#[no_mangle]
pub extern "C" fn ft_spins(sim: *const Sim) -> *const i8 {
unsafe { sim.as_ref() }.map_or(std::ptr::null(), |s| s.sampler_state.as_ptr())
}
#[no_mangle]
pub extern "C" fn ft_magnetization(sim: *const Sim) -> f64 {
unsafe { sim.as_ref() }.map_or(0.0, |s| {
s.sampler_state.iter().map(|&v| v as i64).sum::<i64>() as f64 / s.graph.n as f64
})
}
#[no_mangle]
pub extern "C" fn ft_energy(sim: *const Sim) -> f64 {
unsafe { sim.as_ref() }.map_or(0.0, |s| s.graph.energy(&s.sampler_state))
}
#[no_mangle]
pub extern "C" fn ft_ledger_joules_z1(sim: *const Sim) -> f64 {
unsafe { sim.as_ref() }.map_or(0.0, |s| s.ledger.joules(&Z1_SPICE))
}
#[no_mangle]
pub extern "C" fn ft_onsager(beta: f64) -> f64 {
onsager_m(beta)
}
#[no_mangle]
pub extern "C" fn ft_free(sim: *mut Sim) {
if !sim.is_null() {
drop(unsafe { Box::from_raw(sim) });
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn ffi_roundtrip_matches_onsager() {
let sim = ft_ising2d_new(32, 1.0, 0.6, 42);
assert_eq!(ft_len(sim), 1024);
ft_sweep(sim, 2000);
let mut acc = 0.0;
let reads = 200;
for _ in 0..reads {
ft_sweep(sim, 10);
acc += ft_magnetization(sim).abs();
}
let m = acc / reads as f64;
let exact = ft_onsager(0.6);
assert!((m - exact).abs() < 0.02, "FFI |M| {m} vs Onsager {exact}");
assert!(ft_ledger_joules_z1(sim) > 0.0);
assert!(!ft_spins(sim).is_null());
ft_free(sim);
}
}
use crate::graph::GraphBuilder;
use crate::tempering::{anneal, geometric_ladder};
#[no_mangle]
pub extern "C" fn ft_builder_new(n: u32) -> *mut GraphBuilder {
if n == 0 {
return core::ptr::null_mut();
}
Box::into_raw(Box::new(GraphBuilder::new(n as usize)))
}
#[no_mangle]
pub extern "C" fn ft_builder_couple(b: *mut GraphBuilder, i: u32, j: u32, w: f64) -> u32 {
let Some(b) = (unsafe { b.as_mut() }) else { return 0 };
if i == j || !w.is_finite() || i as usize >= b.n() || j as usize >= b.n() {
return 0;
}
b.couple(i as usize, j as usize, w);
1
}
#[no_mangle]
pub extern "C" fn ft_builder_bias(b: *mut GraphBuilder, i: u32, h: f64) -> u32 {
let Some(bb) = (unsafe { b.as_mut() }) else { return 0 };
if !h.is_finite() || i as usize >= bb.n() {
return 0;
}
bb.bias(i as usize, h);
1
}
#[no_mangle]
pub extern "C" fn ft_builder_build(b: *mut GraphBuilder, beta: f64, seed: u64) -> *mut Sim {
if b.is_null() {
return core::ptr::null_mut();
}
let b = unsafe { Box::from_raw(b) };
Sim::new(b.build(), beta, seed)
}
#[no_mangle]
pub extern "C" fn ft_builder_free(b: *mut GraphBuilder) {
if !b.is_null() {
drop(unsafe { Box::from_raw(b) });
}
}
#[no_mangle]
pub extern "C" fn ft_anneal(
sim: *mut Sim,
beta_min: f64,
beta_max: f64,
stages: u32,
sweeps_per_stage: u32,
) -> f64 {
let Some(s) = (unsafe { sim.as_mut() }) else { return f64::NAN };
if !(beta_min > 0.0 && beta_max > beta_min) || stages < 2 || sweeps_per_stage == 0 {
return f64::NAN;
}
let ladder = geometric_ladder(beta_min, beta_max, stages as usize);
let schedule: Vec<(f64, usize)> =
ladder.iter().map(|&b| (b, sweeps_per_stage as usize)).collect();
let seed = s.seed ^ s.sweeps_done.wrapping_mul(0x9E37_79B9_7F4A_7C15);
let (best, e) = anneal(&s.graph, &schedule, seed, Some(&mut s.ledger));
s.sampler_state.copy_from_slice(&best);
s.sweeps_done += (stages as u64) * (sweeps_per_stage as u64);
s.beta = beta_max;
e
}
#[no_mangle]
pub extern "C" fn ft_nodes(sim: *const Sim) -> u32 {
match unsafe { sim.as_ref() } {
Some(s) => s.graph.n as u32,
None => 0,
}
}
#[no_mangle]
pub extern "C" fn ft_ledger_updates(sim: *const Sim) -> u64 {
match unsafe { sim.as_ref() } {
Some(s) => s.ledger.samples,
None => 0,
}
}
#[cfg(test)]
mod builder_tests {
use super::*;
#[test]
fn builds_and_samples_an_arbitrary_graph() {
let b = ft_builder_new(4);
assert!(!b.is_null());
assert_eq!(ft_builder_couple(b, 0, 1, 1.0), 1);
assert_eq!(ft_builder_couple(b, 1, 2, 1.0), 1);
assert_eq!(ft_builder_bias(b, 0, 0.5), 1);
let sim = ft_builder_build(b, 1.0, 7);
assert_eq!(ft_nodes(sim), 4);
ft_sweep(sim, 50);
assert!(ft_energy(sim).is_finite());
assert!(ft_ledger_updates(sim) >= 200);
ft_free(sim);
}
#[test]
fn rejects_bad_edges_without_crashing() {
let b = ft_builder_new(3);
assert_eq!(ft_builder_couple(b, 0, 9, 1.0), 0, "out of range");
assert_eq!(ft_builder_couple(b, 1, 1, 1.0), 0, "self coupling");
assert_eq!(ft_builder_couple(b, 0, 1, f64::NAN), 0, "non-finite");
assert_eq!(ft_builder_bias(b, 7, 1.0), 0, "out of range");
ft_builder_free(b);
assert_eq!(ft_builder_couple(core::ptr::null_mut(), 0, 1, 1.0), 0);
assert_eq!(ft_nodes(core::ptr::null()), 0);
assert!(ft_anneal(core::ptr::null_mut(), 0.1, 1.0, 4, 4).is_nan());
}
#[test]
fn anneal_finds_the_frustrated_optimum() {
let b = ft_builder_new(5);
for i in 0..5u32 {
ft_builder_couple(b, i, (i + 1) % 5, -1.0);
}
let sim = ft_builder_build(b, 0.1, 1);
let e = ft_anneal(sim, 0.05, 6.0, 40, 30);
assert_eq!(e, -3.0, "frustrated 5-cycle optimum");
assert_eq!(ft_energy(sim), -3.0, "sim must hold the best state");
ft_free(sim);
}
}