#[inline]
pub fn clamp(v: f32, lo: f32, hi: f32) -> f32 {
v.max(lo).min(hi)
}
#[inline]
pub fn clamp_slice_inplace(slice: &mut [f32], lo: f32, hi: f32) {
for x in slice.iter_mut() {
*x = x.max(lo).min(hi);
}
}
#[inline]
pub fn sigmoid(x: f32) -> f32 {
if x >= 0.0 {
1.0 / (1.0 + (-x).exp())
} else {
let e = x.exp();
e / (1.0 + e)
}
}
pub fn sigmoid_slice(xs: &[f32]) -> Vec<f32> {
xs.iter().map(|&x| sigmoid(x)).collect()
}
pub fn softmax(logits: &[f32]) -> Vec<f32> {
assert!(!logits.is_empty(), "softmax requires at least one element");
let max = logits.iter().cloned().fold(f32::NEG_INFINITY, f32::max);
let mut out: Vec<f32> = logits.iter().map(|&x| (x - max).exp()).collect();
let sum: f32 = out.iter().sum();
for x in out.iter_mut() {
*x /= sum;
}
out
}
pub fn log_softmax(logits: &[f32]) -> Vec<f32> {
assert!(
!logits.is_empty(),
"log_softmax requires at least one element"
);
let max = logits.iter().cloned().fold(f32::NEG_INFINITY, f32::max);
let sum_exp: f32 = logits.iter().map(|&x| (x - max).exp()).sum();
let log_sum = max + sum_exp.ln();
logits.iter().map(|&x| x - log_sum).collect()
}
#[inline]
pub fn lerp(a: f32, b: f32, t: f32) -> f32 {
a + t * (b - a)
}
#[inline]
pub fn next_power_of_two(n: usize) -> usize {
if n == 0 {
1
} else {
n.next_power_of_two()
}
}
pub fn bytes_to_human_readable(bytes: usize) -> String {
const KIB: usize = 1024;
const MIB: usize = 1024 * KIB;
const GIB: usize = 1024 * MIB;
const TIB: usize = 1024 * GIB;
if bytes == 0 {
"0 B".to_owned()
} else if bytes < KIB {
format!("{bytes} B")
} else if bytes < MIB {
format!("{:.2} KiB", bytes as f64 / KIB as f64)
} else if bytes < GIB {
format!("{:.2} MiB", bytes as f64 / MIB as f64)
} else if bytes < TIB {
format!("{:.2} GiB", bytes as f64 / GIB as f64)
} else {
format!("{:.2} TiB", bytes as f64 / TIB as f64)
}
}
#[inline]
pub fn num_elements(shape: &[usize]) -> usize {
shape
.iter()
.product::<usize>()
.max(if shape.is_empty() { 1 } else { 0 })
}
pub fn shape_to_strides(shape: &[usize]) -> Vec<usize> {
let n = shape.len();
if n == 0 {
return vec![];
}
let mut strides = vec![1usize; n];
for i in (0..n - 1).rev() {
strides[i] = strides[i + 1] * shape[i + 1];
}
strides
}
#[inline]
pub fn flat_index(indices: &[usize], strides: &[usize]) -> usize {
debug_assert_eq!(
indices.len(),
strides.len(),
"indices and strides must have same length"
);
indices.iter().zip(strides.iter()).map(|(i, s)| i * s).sum()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_clamp_in_range() {
assert_eq!(clamp(2.0, 1.0, 3.0), 2.0);
}
#[test]
fn test_clamp_below() {
assert_eq!(clamp(0.0, 1.0, 3.0), 1.0);
}
#[test]
fn test_clamp_above() {
assert_eq!(clamp(5.0, 1.0, 3.0), 3.0);
}
#[test]
fn test_clamp_slice() {
let mut v = vec![-2.0f32, 0.5, 2.5];
clamp_slice_inplace(&mut v, 0.0, 1.0);
assert_eq!(v, vec![0.0, 0.5, 1.0]);
}
#[test]
fn test_sigmoid_zero() {
assert!((sigmoid(0.0) - 0.5).abs() < 1e-6);
}
#[test]
fn test_sigmoid_positive_saturation() {
assert!(sigmoid(20.0) > 0.999);
}
#[test]
fn test_sigmoid_negative_saturation() {
assert!(sigmoid(-20.0) < 0.001);
}
#[test]
fn test_softmax_sum_to_one() {
let p = softmax(&[1.0, 2.0, 3.0]);
let s: f32 = p.iter().sum();
assert!((s - 1.0).abs() < 1e-5);
}
#[test]
fn test_softmax_monotone() {
let p = softmax(&[1.0, 2.0, 3.0]);
assert!(p[2] > p[1] && p[1] > p[0]);
}
#[test]
fn test_log_softmax_non_positive() {
let ls = log_softmax(&[1.0, 2.0, 3.0]);
assert!(ls.iter().all(|&v| v <= 0.0));
}
#[test]
fn test_lerp_midpoint() {
assert_eq!(lerp(0.0, 10.0, 0.5), 5.0);
}
#[test]
fn test_lerp_endpoints() {
assert_eq!(lerp(0.0, 10.0, 0.0), 0.0);
assert_eq!(lerp(0.0, 10.0, 1.0), 10.0);
}
#[test]
fn test_next_pow2() {
assert_eq!(next_power_of_two(0), 1);
assert_eq!(next_power_of_two(1), 1);
assert_eq!(next_power_of_two(5), 8);
assert_eq!(next_power_of_two(8), 8);
assert_eq!(next_power_of_two(9), 16);
}
#[test]
fn test_bytes_human_readable() {
assert_eq!(bytes_to_human_readable(0), "0 B");
assert_eq!(bytes_to_human_readable(512), "512 B");
assert_eq!(bytes_to_human_readable(1024), "1.00 KiB");
assert_eq!(bytes_to_human_readable(1024 * 1024), "1.00 MiB");
}
#[test]
fn test_num_elements() {
assert_eq!(num_elements(&[2, 3, 4]), 24);
assert_eq!(num_elements(&[]), 1);
assert_eq!(num_elements(&[0, 5]), 0);
}
#[test]
fn test_shape_to_strides() {
assert_eq!(shape_to_strides(&[2, 3, 4]), vec![12, 4, 1]);
assert_eq!(shape_to_strides(&[5]), vec![1]);
assert_eq!(shape_to_strides(&[]), Vec::<usize>::new());
}
#[test]
fn test_flat_index() {
let shape = [2usize, 3, 4];
let strides = shape_to_strides(&shape);
assert_eq!(flat_index(&[1, 2, 3], &strides), 12 + 2 * 4 + 3);
assert_eq!(flat_index(&[0, 0, 0], &strides), 0);
}
}