use core::f64::consts::PI;
pub const K0: f64 = 0.5;
pub const K1: f64 = 0.5;
pub const B_MAX_MT: f64 = 100.0;
pub const GROUND_TRUTH_NOISE_STD: f64 = 0.008;
pub const N_FEATURES: usize = 4;
pub const FEATURE_NAMES: [&str; N_FEATURES] = ["bias", "B-field", "B-field²", "B-field³"];
pub struct Rng {
state: u64,
}
impl Rng {
pub fn new(seed: u64) -> Self {
let state = seed ^ 0x6C62_272E_07BB_0142;
let state = if state == 0 {
0x6C62_272E_07BB_0142
} else {
state
};
let mut rng = Self { state };
for _ in 0..8 {
rng.next_u64();
}
rng
}
#[inline]
pub fn next_u64(&mut self) -> u64 {
self.state ^= self.state << 13;
self.state ^= self.state >> 7;
self.state ^= self.state << 17;
self.state
}
pub fn uniform(&mut self, min: f64, max: f64) -> f64 {
let bits = self.next_u64() >> 11;
let u = bits as f64 * (1.0 / 9_007_199_254_740_992.0_f64);
min + u * (max - min)
}
pub fn normal(&mut self) -> f64 {
let u1 = self.uniform(1e-15, 1.0);
let u2 = self.uniform(0.0, 2.0 * PI);
(-2.0 * u1.ln()).sqrt() * u2.cos()
}
}
pub fn hall_feature_fn(b_mt: f64) -> Vec<f64> {
let b = b_mt / B_MAX_MT;
vec![1.0, b, b * b, b * b * b]
}
pub fn hall_voltage_true(b_mt: f64) -> f64 {
K0 + K1 * (b_mt / B_MAX_MT)
}
pub fn generate_hall_samples(
n: usize,
b_min_mt: f64,
b_max_mt: f64,
noise_std: f64,
seed: u64,
) -> (Vec<f64>, Vec<f64>) {
let mut rng = Rng::new(seed);
let mut b_values = Vec::with_capacity(n);
let mut voltages = Vec::with_capacity(n);
for _ in 0..n {
let b = rng.uniform(b_min_mt, b_max_mt);
let v = hall_voltage_true(b) + noise_std * rng.normal();
b_values.push(b);
voltages.push(v);
}
(b_values, voltages)
}
pub fn build_phi(b_vals: &[f64]) -> Vec<f64> {
let n = b_vals.len();
let mut phi = Vec::with_capacity(n * N_FEATURES);
for &b in b_vals {
phi.extend_from_slice(&hall_feature_fn(b));
}
phi
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_rng_deterministic() {
let mut r1 = Rng::new(42);
let mut r2 = Rng::new(42);
for _ in 0..100 {
assert_eq!(r1.next_u64(), r2.next_u64());
}
}
#[test]
fn test_rng_different_seeds() {
let mut r1 = Rng::new(1);
let mut r2 = Rng::new(2);
let same = (0..10).all(|_| r1.next_u64() == r2.next_u64());
assert!(!same, "Different seeds produced identical output");
}
#[test]
fn test_hall_feature_fn() {
let phi = hall_feature_fn(0.0);
assert_eq!(phi.len(), N_FEATURES);
assert!((phi[0] - 1.0).abs() < 1e-14);
assert!((phi[1] - 0.0).abs() < 1e-14);
assert!((phi[2] - 0.0).abs() < 1e-14);
assert!((phi[3] - 0.0).abs() < 1e-14);
let phi100 = hall_feature_fn(100.0);
assert!((phi100[1] - 1.0).abs() < 1e-14);
assert!((phi100[2] - 1.0).abs() < 1e-14);
assert!((phi100[3] - 1.0).abs() < 1e-14);
let phi50 = hall_feature_fn(50.0);
assert!((phi50[1] - 0.5).abs() < 1e-14);
assert!((phi50[2] - 0.25).abs() < 1e-14);
assert!((phi50[3] - 0.125).abs() < 1e-14);
}
#[test]
fn test_hall_voltage_true_range() {
assert!((hall_voltage_true(0.0) - 0.5).abs() < 1e-14);
assert!((hall_voltage_true(100.0) - 1.0).abs() < 1e-14);
assert!((hall_voltage_true(50.0) - 0.75).abs() < 1e-14);
}
#[test]
fn test_generate_hall_samples_length() {
let (bs, vs) = generate_hall_samples(20, 0.0, 100.0, GROUND_TRUTH_NOISE_STD, 42);
assert_eq!(bs.len(), 20);
assert_eq!(vs.len(), 20);
}
#[test]
fn test_generate_hall_samples_deterministic() {
let (bs1, vs1) = generate_hall_samples(10, 0.0, 100.0, GROUND_TRUTH_NOISE_STD, 99);
let (bs2, vs2) = generate_hall_samples(10, 0.0, 100.0, GROUND_TRUTH_NOISE_STD, 99);
assert_eq!(bs1, bs2);
assert_eq!(vs1, vs2);
}
#[test]
fn test_generate_hall_samples_range() {
let (bs, _vs) = generate_hall_samples(100, 0.0, 100.0, GROUND_TRUTH_NOISE_STD, 7);
for b in &bs {
assert!(*b >= 0.0 && *b < 100.0, "B out of range: {b}");
}
}
#[test]
fn test_generate_hall_samples_close_to_truth() {
let (bs, vs) = generate_hall_samples(1000, 0.0, 100.0, GROUND_TRUTH_NOISE_STD, 42);
let mut max_err = 0.0_f64;
for (&b, &v) in bs.iter().zip(vs.iter()) {
let err = (v - hall_voltage_true(b)).abs();
if err > max_err {
max_err = err;
}
}
assert!(
max_err < 0.1,
"Max error too large: {max_err:.4} (expected < 0.1 for noise_std=0.008)"
);
}
#[test]
fn test_build_phi_shape() {
let bs = vec![0.0, 50.0, 100.0];
let phi = build_phi(&bs);
assert_eq!(phi.len(), 3 * N_FEATURES);
assert!((phi[N_FEATURES + 1] - 0.5).abs() < 1e-12);
}
}