1#[inline]
12pub fn eml(x: f64, y: f64) -> f64 {
13 x.exp() - y.ln()
14}
15
16#[inline]
22pub fn eml_safe(x: f64, y: f64) -> f64 {
23 let ex = x.clamp(-20.0, 20.0).exp();
24 let ly = if y > 0.0 {
25 y.ln()
26 } else {
27 f64::MIN_POSITIVE.ln()
28 };
29 ex - ly
30}
31
32#[inline]
36pub fn softmax3(a: f64, b: f64, c: f64) -> (f64, f64, f64) {
37 let max = a.max(b).max(c);
38 let ea = (a - max).exp();
39 let eb = (b - max).exp();
40 let ec = (c - max).exp();
41 let sum = ea + eb + ec;
42 (ea / sum, eb / sum, ec / sum)
43}
44
45pub(crate) fn random_params(state: &mut u64, count: usize) -> Vec<f64> {
50 let mut params = vec![0.0f64; count];
51 for p in params.iter_mut() {
52 *state = state
53 .wrapping_mul(6364136223846793005)
54 .wrapping_add(1442695040888963407);
55 *p = (*state >> 33) as f64 / (u32::MAX as f64 / 2.0) - 1.0;
56 }
57 params
58}
59
60#[cfg(test)]
61mod tests {
62 use super::*;
63
64 #[test]
65 fn eml_identity() {
66 let result = eml(0.0, 1.0);
68 assert!(
69 (result - 1.0).abs() < 1e-12,
70 "eml(0, 1) should be 1.0, got {result}"
71 );
72 }
73
74 #[test]
75 fn eml_exp_only() {
76 let result = eml(1.0, 1.0);
78 assert!(
79 (result - std::f64::consts::E).abs() < 1e-12,
80 "eml(1, 1) should be e, got {result}"
81 );
82 }
83
84 #[test]
85 fn eml_ln_only() {
86 let result = eml(0.0, std::f64::consts::E);
88 assert!(
89 result.abs() < 1e-12,
90 "eml(0, e) should be 0.0, got {result}"
91 );
92 }
93
94 #[test]
95 fn eml_safe_does_not_panic() {
96 let _ = eml_safe(100.0, 0.0);
97 let _ = eml_safe(-100.0, -5.0);
98 let _ = eml_safe(0.0, f64::MIN_POSITIVE);
99 let _ = eml_safe(f64::NAN, 1.0);
100 }
101
102 #[test]
103 fn eml_safe_clamps_large_exp() {
104 let result = eml_safe(100.0, 1.0);
105 assert!(result.is_finite(), "eml_safe(100, 1) should be finite");
107 let expected = 20.0_f64.exp(); assert!(
109 (result - expected).abs() < 1e-6,
110 "eml_safe(100, 1) should be exp(20), got {result}"
111 );
112 }
113
114 #[test]
115 fn softmax3_sums_to_one() {
116 let (a, b, c) = softmax3(1.0, 2.0, 3.0);
117 let sum = a + b + c;
118 assert!(
119 (sum - 1.0).abs() < 1e-12,
120 "softmax3 should sum to 1.0, got {sum}"
121 );
122 }
123
124 #[test]
125 fn softmax3_equal_inputs() {
126 let (a, b, c) = softmax3(0.0, 0.0, 0.0);
127 assert!((a - 1.0 / 3.0).abs() < 1e-12);
128 assert!((b - 1.0 / 3.0).abs() < 1e-12);
129 assert!((c - 1.0 / 3.0).abs() < 1e-12);
130 }
131
132 #[test]
133 fn softmax3_dominated_input() {
134 let (a, _b, _c) = softmax3(100.0, 0.0, 0.0);
135 assert!(a > 0.99, "dominant input should get nearly all weight");
136 }
137
138 #[test]
139 fn random_params_deterministic() {
140 let mut s1 = 42u64;
141 let mut s2 = 42u64;
142 let p1 = random_params(&mut s1, 10);
143 let p2 = random_params(&mut s2, 10);
144 assert_eq!(p1, p2, "same seed should produce same params");
145 }
146
147 #[test]
148 fn random_params_in_range() {
149 let mut s = 0xDEAD_BEEF_u64;
150 let params = random_params(&mut s, 100);
151 for &p in ¶ms {
152 assert!(
153 p >= -1.0 && p <= 1.0,
154 "param {p} out of [-1, 1] range"
155 );
156 }
157 }
158}