ferrotherm-gpu 0.1.0

Native GPU sampling for ferrotherm: the same WGSL sweep the browser runs, on Vulkan, Metal or DX12. Kept out of the core crate so `ferrotherm` stays std-only with zero dependencies.
Documentation

Native GPU sampling: the same chromatic sweep the browser runs, on Vulkan, Metal or DX12.

Why this is a separate crate

ferrotherm is std-only with zero dependencies, and that is load-bearing rather than decorative: it is what lets the same source compile to wasm32-unknown-unknown and to a microcontroller. A GPU backend needs a driver stack. So it lives out here beside silicon, serve and cloud, each of which exists for exactly the same reason.

Why it does not have its own shader

The WGSL comes from [ferrotherm::wgsl::sweep_shader] — the same string the browser fetches through ft_shader. A second copy would be a second implementation of the update rule, and the two would drift the first time one was tuned. The core crate already pins the sigmoid with a test (the_shader_states_the_same_update_as_the_kernel); binding that same text here means a native run and a browser run cannot disagree about the arithmetic, only about the hardware.

What it does not promise

Not bit-identical to the CPU sampler. The shader's RNG is a counter-based hash of (step, node), chosen so a lane needs no state and the result does not depend on the order lanes happen to execute in. The CPU sampler draws from its own stream. Both sample the same distribution; neither reproduces the other's individual flips, and a test that asserted they did would be asserting something false.

What they DO agree on is physics, and that is what [Gpu::sweep]'s tests check: the same magnetisation at the same temperature, and a planted instance solved to its known optimum.

use ferrotherm::{ising::lattice2d, wgsl::GpuModel};
# fn main() -> Result<(), String> {
let g = lattice2d(8, 1.0);
let m = GpuModel::from_graph(&g);
let mut spins = vec![1i8; 64];

let gpu = ferrotherm_gpu::Gpu::new().ok_or("no adapter")?;
gpu.sweep(&m, &mut spins, 0.44, 100)?;
# Ok(()) }