1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
//! # ferrotherm — thermodynamic sampling for Physical AI
//!
//! The IPAI @ BMI equivalent of the thermodynamic-computing software stack, in pure Rust: sparse
//! energy-based models, chromatic block-Gibbs sampling, and — first-class, not an appendix — the
//! device energy ledger that prices every sample, read, and write in joules.
//!
//! The physics is old and open: Ising (1925), Glauber dynamics (1963), Gibbs sampling
//! (Geman & Geman 1984), graph-colored parallel sweeps (standard checkerboard decomposition).
//! What a "thermodynamic sampling unit" accelerates is exactly this loop; what it charges for is
//! I/O. Both belong in the open commons, runnable on every compute fabric — CPU today, WebGPU next,
//! physics-native silicon when it exists to measure.
//!
//! Design positions, each earned from a verified source:
//! * **The ledger is first-class.** Extropic's Thermalizers appendix (arXiv:2608.01615, Table IV)
//! prices a Z1-class node at 7.09 fJ per Gibbs cycle, 1.692 pJ per read, 153.6 pJ per write —
//! a write costs ~21,700 samples. Any honest account of this hardware class is an I/O story,
//! so every `ferrotherm` simulation carries a [`ledger::Ledger`] and reports what the device
//! WOULD pay, crossings included.
//! * **Sparse and 2-colorable is the native shape.** The published Z1 topology is a planar grid
//! with odd-Manhattan couplings (degree 16, longest edge sqrt(17)), which is bipartite: one
//! full sweep = two parallel half-sweeps. [`device::z1_grid`] reproduces it.
//! * **Verification against exact physics before any claim.** The sampler must reproduce the
//! exact Boltzmann distribution on enumerable systems and the Onsager magnetization on the 2D
//! lattice before it is used for anything else. See `examples/ring_tv.rs`, `examples/onsager.rs`.
//!
//! ## Quickstart
//!
//! ```
//! use ferrotherm::{ising, gibbs::Sampler, ledger::{Ledger, Z1_SPICE}};
//!
//! // a 16x16 Ising magnet below its critical temperature
//! let g = ising::lattice2d(16, 1.0);
//! let mut led = Ledger::default();
//! let mut smp = Sampler::new(&g, 0.6, 42);
//! smp.sweeps(500, Some(&mut led));
//!
//! let m = (smp.s.iter().map(|&v| v as i64).sum::<i64>().abs() as f64) / g.n as f64;
//! assert!(m > 0.9, "ordered phase: |M| = {m}");
//! // What those sweeps WOULD cost on a Z1-class device. `joules` returns an Option because a
//! // device whose per-operation energy nobody has published has no answer here -- and borrowing
//! // another device's prices produces a figure indistinguishable from a measured one.
//! assert!(led.joules(&Z1_SPICE).unwrap() > 0.0);
//! assert_eq!(led.joules(&ferrotherm::ledger::Prices::UNSTATED), None);
//! ```
//!
//! # What "deterministic by seed" does and does not promise
//!
//! Measured across three machines -- macOS/arm64 (Apple M5 Max), Linux/x86_64 (AMD EPYC 9R14) and
//! Linux/aarch64 (Graviton3), all on rustc 1.97.1 -- running the identical program:
//!
//! | | macOS arm64 | Linux x86_64 | Linux aarch64 |
//! |---|---|---|---|
//! | compiled `.ftp` program | identical | identical | identical |
//! | CSR neighbour order | identical | identical | identical |
//! | **sampled state** | identical | identical | identical |
//! | `exp()` and the sigmoid | identical | identical | identical |
//! | energy computed from that state | `..a7b3` | `..a7b2` | `..a7b2` |
//!
//! **The answer is bit-reproducible.** The state a seed produces is the same on every platform
//! tested, which is what the promise is for: a run can be repeated and a result checked.
//!
//! **A derived float may differ by one ULP across operating systems.** The two Linux boxes agree
//! with each other across DIFFERENT architectures, and macOS disagrees with Linux on the SAME
//! architecture -- so it is not architecture. It is not libm either: `exp` and the sigmoid were
//! measured bit-identical on both. It is floating-point contraction, `w * s_i * s_j` accumulating
//! through an `fma` on one target and a separate multiply and add on another, which round
//! differently. Same values, same order, one bit apart.
//!
//! So: compare states, hashes and programs with `==`; compare energies with a tolerance. A test
//! asserting bit-equality of a derived float across platforms asserts something this crate does not
//! promise and could only deliver by disabling contraction everywhere, which costs more than the
//! property is worth.
//!
//! Scope note: binary (pbit) nodes with pairwise couplings are the sampling core. Categorical
//! and continuous nodes arrive through the program layer ([`program`]) and the thermodynamic
//! linear-algebra module ([`tla`]); the compiler ([`compile`]) targets device topologies.
/// The README's own code blocks, compiled by `cargo test`.
///
/// Its headline "Use it" snippet did not compile: `Ledger::joules` returns `Option<f64>` and the
/// snippet formatted it with `{:.2e}`, which `Option` does not implement. That is the first thing
/// anyone copies, and nothing was checking it -- a README is documentation the compiler can read,
/// so it should.
;