use std::env;
#[derive(Debug, Clone, Copy)]
pub struct SigmoidFn {
pub midpoint: f32,
pub steepness: f32,
}
impl SigmoidFn {
pub fn evaluate(self, x: f32) -> f32 {
let exp_arg = -self.steepness * (x - self.midpoint);
1.0 / (1.0 + exp_arg.exp())
}
}
impl Default for SigmoidFn {
fn default() -> Self {
Self {
midpoint: 0.5,
steepness: 8.0,
}
}
}
pub(crate) fn get_sigmoid_config(
env_prefix: &str,
default_midpoint: f32,
default_steepness: f32,
) -> SigmoidFn {
let midpoint_env = format!("{env_prefix}_MIDPOINT");
let steepness_env = format!("{env_prefix}_STEEPNESS");
let midpoint = env::var(&midpoint_env)
.ok()
.and_then(|s| s.parse::<f32>().ok())
.unwrap_or(default_midpoint);
let steepness = env::var(&steepness_env)
.ok()
.and_then(|s| s.parse::<f32>().ok())
.unwrap_or(default_steepness);
SigmoidFn {
midpoint,
steepness,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_sigmoid_basic() {
let f = SigmoidFn {
midpoint: 0.5,
steepness: 10.0,
};
let result = f.evaluate(0.5);
assert!((result - 0.5).abs() < 0.01);
let low_result = f.evaluate(0.0);
let high_result = f.evaluate(1.0);
assert!(low_result < 0.5);
assert!(high_result > 0.5);
assert!((0.0..=1.0).contains(&low_result));
assert!((0.0..=1.0).contains(&high_result));
}
#[test]
fn test_sigmoid_monotonic() {
let f = SigmoidFn {
midpoint: 0.5,
steepness: 10.0,
};
let values = [0.0, 0.2, 0.4, 0.6, 0.8, 1.0];
let results: Vec<f32> = values.iter().map(|&x| f.evaluate(x)).collect();
for i in 1..results.len() {
assert!(
results[i] > results[i - 1],
"Sigmoid should be monotonically increasing"
);
}
}
#[test]
fn test_sigmoid_steepness() {
let steep = SigmoidFn {
midpoint: 0.5,
steepness: 50.0,
};
let gentle = SigmoidFn {
midpoint: 0.5,
steepness: 2.0,
};
let steep_diff = steep.evaluate(0.8) - steep.evaluate(0.2);
let gentle_diff = gentle.evaluate(0.8) - gentle.evaluate(0.2);
assert!(
steep_diff > gentle_diff,
"Steeper function should have larger difference"
);
}
#[test]
fn test_get_sigmoid_config() {
let config = get_sigmoid_config("NONEXISTENT_ENV", 0.7, 15.0);
assert!((config.midpoint - 0.7).abs() < 0.01);
assert!((config.steepness - 15.0).abs() < 0.01);
}
#[test]
fn test_sigmoid_edge_cases() {
let f = SigmoidFn {
midpoint: 0.5,
steepness: 10.0,
};
let very_low = f.evaluate(-1.0);
let very_high = f.evaluate(2.0);
assert!((0.0..=1.0).contains(&very_low));
assert!((0.0..=1.0).contains(&very_high));
assert!(very_low < very_high);
}
}