use std::f64::consts::PI;
use crate::rng::TinyRng;
pub fn sigmoid(x: f64) -> f64 {
1. / (1. + f64::exp(-x))
}
pub fn sigmoid_grad(x: f64) -> f64 {
sigmoid(x) * (1.0 - sigmoid(x))
}
pub fn sphere(x: &[f64]) -> f64 {
x.iter().map(|x| x * x).sum()
}
pub fn sphere_grad(x: &[f64]) -> Vec<f64> {
x.iter().map(|x| 2.0 * x).collect()
}
pub fn abs(x: &[f64]) -> f64 {
x.iter().map(|x| x.abs()).sum()
}
pub fn abs_grad(x: &[f64]) -> Vec<f64> {
x.iter().map(|x| x.signum()).collect()
}
pub fn rosenbrock(x: &[f64], (a, b): (f64, f64)) -> f64 {
assert!(x.len() % 2 == 0, "Rosenbrock fn requires even dimensions");
let n = x.len() / 2;
(0..n)
.map(|i| {
let xi = x[2 * i];
let yi = x[2 * i + 1];
(xi - a).powi(2) + b * (yi - xi.powi(2)).powi(2)
})
.sum()
}
pub fn rosenbrock_grad(x: &[f64], (a, b): (f64, f64)) -> Vec<f64> {
assert!(x.len() % 2 == 0, "Rosenbrock grad requires even dimensions");
let n = x.len() / 2;
(0..n)
.flat_map(move |i| {
let xi = x[2 * i];
let yi = x[2 * i + 1];
vec![
2.0 * (xi - a) - 4.0 * b * xi * (yi - xi.powi(2)),
2.0 * b * (yi - xi.powi(2)),
]
})
.collect()
}
pub fn uniform_noise(x: f64) -> f64 {
let seed = x.to_bits();
let random_u64 = TinyRng::new(seed).next_u64();
let random_f64 = (random_u64 >> 11) as f64 * (1.0 / (1u64 << 53) as f64); random_f64 * 2.0 - 1.0 }
pub fn gaussian_noise(x: f64) -> f64 {
let u1: f64 = uniform_noise(x);
let u2: f64 = uniform_noise(x + 1.0);
(-(u1 * u1).ln() / 2.0).sqrt() * (2.0 * PI * u2).cos()
}
#[cfg(test)]
mod tests {
use crate::{assert_approx_eq, math::central_difference_gradient};
use super::*;
#[test]
fn test_sigmoid() {
assert!((sigmoid(0.0) - 0.5).abs() < 1e-6);
}
#[test]
fn test_rosenbrock_grad() {
let x = [1.0, 2.0, 3.0, 4.0, 5.5, 6.5];
let (a, b) = (1.0, 100.0);
let grad = rosenbrock_grad(&x, (a, b));
let grad_fd = central_difference_gradient(&x, |x| rosenbrock(x, (1.0, 100.0)));
assert_approx_eq!(grad.as_slice(), grad_fd.as_slice(), 1e-4);
}
#[test]
fn test_noise() {
let x = 42.0;
let noise1 = uniform_noise(x);
let noise2 = uniform_noise(x);
assert_approx_eq!(noise1, noise2, 1e-10);
const N: u32 = 100_000;
let mean = (0..N).map(|x| uniform_noise(x as f64)).sum::<f64>() / N as f64;
assert_approx_eq!(mean, 0.0, 0.001);
}
#[test]
#[ignore]
fn write_noise_file() {
std::fs::write(
"/tmp/stepwise-random-numbers.csv",
(0..100_000)
.map(|x| {
format!(
"{u} {g}\n",
u = uniform_noise(x as f64),
g = gaussian_noise(x as f64)
)
})
.collect::<Vec<String>>()
.concat(),
)
.expect("Failed to write to file /tmp/stepwise-random-numbers.csv");
}
}