use fuel_ir::error::Result;
use rand::distr::Distribution;
use rand::rngs::StdRng;
pub fn xavier_uniform(fan_in: usize, fan_out: usize, rng: &mut StdRng) -> Result<Vec<f32>> {
if fan_in == 0 || fan_out == 0 {
return Err(fuel_ir::error::Error::Msg(format!(
"xavier_uniform: fan_in and fan_out must be >= 1, got \
fan_in={fan_in} fan_out={fan_out}",
))
.bt());
}
let denom = (fan_in + fan_out) as f32;
let a = (6.0_f32 / denom).sqrt();
uniform(-a, a, fan_in * fan_out, rng)
}
pub fn kaiming_uniform(fan_in: usize, gain: f32, rng: &mut StdRng) -> Result<Vec<f32>> {
if fan_in == 0 {
return Err(
fuel_ir::error::Error::Msg("kaiming_uniform: fan_in must be >= 1".to_string()).bt(),
);
}
if !gain.is_finite() {
return Err(fuel_ir::error::Error::Msg(format!(
"kaiming_uniform: gain must be finite, got {gain}",
))
.bt());
}
let std = gain / (fan_in as f32).sqrt();
let bound = (3.0_f32).sqrt() * std;
uniform(-bound, bound, fan_in, rng)
}
pub fn normal(mean: f32, std: f32, n: usize, rng: &mut StdRng) -> Result<Vec<f32>> {
if std < 0.0 || !std.is_finite() || !mean.is_finite() {
return Err(fuel_ir::error::Error::Msg(format!(
"normal: invalid parameters mean={mean} std={std}",
))
.bt());
}
let dist = rand_distr::Normal::new(mean, std).map_err(fuel_ir::error::Error::wrap)?;
Ok((0..n).map(|_| dist.sample(rng)).collect())
}
pub fn uniform(lo: f32, hi: f32, n: usize, rng: &mut StdRng) -> Result<Vec<f32>> {
if !lo.is_finite() || !hi.is_finite() || lo >= hi {
return Err(fuel_ir::error::Error::Msg(format!(
"uniform: require lo < hi and both finite, got lo={lo} hi={hi}",
))
.bt());
}
let dist = rand::distr::Uniform::new(lo, hi).map_err(fuel_ir::error::Error::wrap)?;
Ok((0..n).map(|_| dist.sample(rng)).collect())
}
#[cfg(test)]
mod tests {
use super::*;
use rand::SeedableRng;
fn mean_and_var(xs: &[f32]) -> (f32, f32) {
let n = xs.len() as f32;
let mean = xs.iter().sum::<f32>() / n;
let var = xs.iter().map(|x| (x - mean).powi(2)).sum::<f32>() / n;
(mean, var)
}
#[test]
fn xavier_uniform_mean_zero_variance_2_over_fan_in_plus_fan_out() {
let fan_in = 64;
let fan_out = 64;
let mut rng = StdRng::seed_from_u64(0xabad1dea);
let n_trials = 64;
let mut all = Vec::with_capacity(n_trials * fan_in * fan_out);
for _ in 0..n_trials {
all.extend(xavier_uniform(fan_in, fan_out, &mut rng).unwrap());
}
let (mean, var) = mean_and_var(&all);
let expected_var = 2.0_f32 / (fan_in + fan_out) as f32;
assert!(
mean.abs() < 5e-4,
"xavier mean {mean} not near 0 (n={})",
all.len(),
);
let rel = (var - expected_var).abs() / expected_var;
assert!(
rel < 0.02,
"xavier var {var} expected {expected_var} (rel diff {rel})",
);
for v in &all {
assert!(v.is_finite(), "xavier produced non-finite value");
}
}
#[test]
fn kaiming_uniform_distribution_check() {
let fan_in = 512;
let gain = (2.0_f32).sqrt(); let mut rng = StdRng::seed_from_u64(123);
let n_trials = 256;
let mut all = Vec::with_capacity(n_trials * fan_in);
for _ in 0..n_trials {
all.extend(kaiming_uniform(fan_in, gain, &mut rng).unwrap());
}
let (mean, var) = mean_and_var(&all);
let expected_var = gain * gain / fan_in as f32;
let std = expected_var.sqrt();
let bound = (3.0_f32).sqrt() * std;
assert!(
mean.abs() < 5e-4,
"kaiming mean {mean} not near 0 (n={})",
all.len(),
);
let rel = (var - expected_var).abs() / expected_var;
assert!(
rel < 0.02,
"kaiming var {var} expected {expected_var} (rel diff {rel})",
);
for v in &all {
assert!(
v.is_finite() && v.abs() <= bound + 1e-5,
"kaiming value {v} out of [-{bound}, {bound}]",
);
}
}
#[test]
fn normal_initializer_mean_within_tolerance() {
let mean_in = 0.7_f32;
let std_in = 0.5_f32;
let n = 1 << 16; let mut rng = StdRng::seed_from_u64(0xcafe_f00d);
let samples = normal(mean_in, std_in, n, &mut rng).unwrap();
assert_eq!(samples.len(), n);
let (m, v) = mean_and_var(&samples);
assert!(
(m - mean_in).abs() < 0.01,
"normal mean {m} not near {mean_in}",
);
let expected_var = std_in * std_in;
let rel = (v - expected_var).abs() / expected_var;
assert!(
rel < 0.04,
"normal var {v} expected {expected_var} (rel diff {rel})",
);
for x in &samples {
assert!(x.is_finite(), "normal produced non-finite sample");
}
}
#[test]
fn uniform_in_range() {
let mut rng = StdRng::seed_from_u64(99);
let lo = -1.5_f32;
let hi = 2.5_f32;
let samples = uniform(lo, hi, 4096, &mut rng).unwrap();
for &x in &samples {
assert!(x.is_finite());
assert!(x >= lo && x < hi, "uniform sample {x} out of [{lo}, {hi})");
}
}
}