// parity_check.mbt — bit-exact parity verification helpers (v0.42.3).
//
// Mirrors the SpikingNeuralNetworks.jl side-by-side verification
// harness. Provides deterministic dumps of state (RNG, neuron
// trajectories, weight matrices) that can be compared against
// equivalent Julia runs to validate Float32 bit-exactness.
//
// Note: MoonBit doesn't expose a Float-to-bits reinterpret API, so
// we use a Float→Int quantization (multiply by 1e6, truncate) which
// is deterministic and matches what Julia can produce via
// `Float32(x * 1f6) |> Int32(x)` for the same input.
//
// Hash convention: FNV-1a 32-bit. Julia side computes the same
// FNV-1a 32-bit hash over the quantized Int array. As long as both
// sides produce the same quantized Int sequence, the hashes match.
// ── FNV-1a 32-bit hash over Int array ─────────────────────────────
///|
/// FNV-1a 32-bit hash over an Int array.
pub fn fnv1a_32(data : Array[Int]) -> Int {
let mut h : UInt = 0x811c9dc5 // FNV offset basis
for i in 0..> (j * 8)) & 0xFF
h = h ^ b
h = h * 0x01000193 // FNV prime
j = j + 1
}
}
h.to_int()
}
///|
/// Quantise a Float array to Int by scaling by 1e6 and truncating.
pub fn floats_to_int_keys(data : Array[Float]) -> Array[Int] {
let out : Array[Int] = Array::make(data.length(), 0)
for i in 0.. Int {
fnv1a_32(floats_to_int_keys(data))
}
// ── RNG parity ────────────────────────────────────────────────────
///|
/// Hash the public state of an Xoshiro RNG after seeding with
/// `seed` and advancing `n` steps. Caller must use the same seed +
/// step count on the Julia side to verify.
pub fn parity_hash_xoshiro(seed : UInt64, n : Int) -> Int {
let rng = Xoshiro::new(seed)
let samples : Array[Float] = Array::make(n, 0.0F)
for i in 0.. Int {
let rng = Xoshiro::new(seed)
let param = IFParameter::new()
let pop = IF::new(1, param, rng)
// Set tonic current to all neurons.
for i in 0.. Int {
let rng = Xoshiro::new(seed)
let m = SparseMatrixCSR::random(n_pre, n_post, mu, sigma, p, rng)
// Hash vals + colptr (rowptr is structural, not state).
let combined : Array[Float] = []
for i in 0.. (Int, Int, Int) {
let h1 = parity_hash_xoshiro(seed, 1000)
let h2 = parity_hash_if_trajectory(200, 0.125F, 1.5F, seed)
let h3 = parity_hash_sparse_matrix(50, 50, 1.0F, 0.3F, 0.5F, seed)
(h1, h2, h3)
}
///|
/// Format a parity dump as a single Int that combines all three
/// sub-hashes. Useful for cross-language comparison (Julia can
/// reproduce this with the same recipe).
pub fn parity_combined_hash(seed : UInt64) -> Int {
let (h1, h2, h3) = parity_full_dump(seed)
// Combine via FNV-1a over the 3 sub-hashes.
fnv1a_32([h1, h2, h3])
}