// // PhiloxTests.swift // Metrika // // Author: Simon-Pierre Boucher // Contact: contact@spboucher.ai // Copyright © 2026 Simon-Pierre Boucher. All rights reserved. // import Testing import ZQGPU @Suite("Philox4x32-10 RNG") struct PhiloxTests { @Test("Random123 known-answer vectors") func knownAnswers() { // Zero counter, zero key. let zero = Philox4x32(seed: 0).block(counter: (0, 0, 0, 0)) #expect(zero.0 == 0x6627_e8d5) #expect(zero.1 == 0xe169_c58d) #expect(zero.2 == 0xbc57_ac4c) #expect(zero.3 == 0x9b00_dbd8) // All-ones counter and key (Random123 kat_vectors). let ones = Philox4x32(seed: .max).block( counter: (0xffff_ffff, 0xffff_ffff, 0xffff_ffff, 0xffff_ffff) ) #expect(ones.0 == 0x408f_276d) #expect(ones.1 == 0x41c8_3b0e) #expect(ones.2 == 0xa20b_c7c6) #expect(ones.3 == 0x6d54_51fd) } @Test("same seed produces identical streams — reproducibility blocker") func determinism() { let a = Philox4x32(seed: 42) let b = Philox4x32(seed: 42) for index in [0, 1, 2, 1_000, 1_000_000] as [UInt64] { #expect(a.word(at: index) == b.word(at: index)) #expect(a.uniform(at: index) == b.uniform(at: index)) } } @Test("different seeds diverge") func seedsDiffer() { let a = Philox4x32(seed: 42) let b = Philox4x32(seed: 43) let differing = (0..<64).count { a.word(at: UInt64($0)) != b.word(at: UInt64($0)) } #expect(differing > 60) } @Test("uniforms are in [0, 1) and pass a coarse mean check") func uniformRange() { let generator = Philox4x32(seed: 7) var sum = 0.0 let n = 100_000 for i in 0..= 0 && u < 1) sum += u } let mean = sum / Double(n) #expect(abs(mean - 0.5) < 0.005, "mean \(mean) too far from 0.5") } @Test("bounded integers stay in range and hit every value") func integerBounds() { let generator = Philox4x32(seed: 11) var seen = Set() for i in 0..<10_000 { let value = generator.integer(at: UInt64(i), bound: 10) #expect(value >= 0 && value < 10) seen.insert(value) } #expect(seen.count == 10) } @Test("bootstrap replicates are independent of computation order") func resamplingAddressable() { let generator = Philox4x32(seed: 42) let early = ZQResampling.pairsBootstrapIndices( replicate: 5, sampleSize: 100, generator: generator ) // Recomputing replicate 5 in isolation gives the same draw. let again = ZQResampling.pairsBootstrapIndices( replicate: 5, sampleSize: 100, generator: Philox4x32(seed: 42) ) #expect(early == again) #expect(early.allSatisfy { (0..<100).contains($0) }) } }