1#[cfg(not(feature = "std"))]
24use alloc::string::String;
25#[cfg(not(feature = "std"))]
26use alloc::vec;
27#[cfg(not(feature = "std"))]
28use alloc::vec::Vec;
29
30use crate::Scalar;
31
32#[derive(Clone, Copy, Debug, PartialEq)]
48pub struct Uncertain<S: Scalar> {
49 pub mean: S,
51 pub variance: S,
53}
54
55impl<S: Scalar> Default for Uncertain<S> {
56 fn default() -> Self {
57 Self {
58 mean: S::ZERO,
59 variance: S::ZERO,
60 }
61 }
62}
63
64impl<S: Scalar> Uncertain<S> {
65 pub fn new(mean: S, variance: S) -> Self {
67 Self { mean, variance }
68 }
69
70 pub fn from_std(mean: S, std: S) -> Self {
72 Self {
73 mean,
74 variance: std * std,
75 }
76 }
77
78 pub fn certain(value: S) -> Self {
80 Self {
81 mean: value,
82 variance: S::ZERO,
83 }
84 }
85
86 pub fn std(&self) -> S {
88 self.variance.sqrt()
89 }
90
91 pub fn cv(&self) -> S {
93 const CV_ZERO_THRESHOLD: f64 = 1e-15;
95 if self.mean.abs() < S::from_f64(CV_ZERO_THRESHOLD) {
96 S::INFINITY
97 } else {
98 self.std() / self.mean.abs()
99 }
100 }
101
102 pub fn scale(&self, a: S) -> Self {
105 Self {
106 mean: a * self.mean,
107 variance: a * a * self.variance,
108 }
109 }
110
111 pub fn add_const(&self, c: S) -> Self {
114 Self {
115 mean: self.mean + c,
116 variance: self.variance,
117 }
118 }
119
120 pub fn add(&self, other: &Self) -> Self {
123 Self {
124 mean: self.mean + other.mean,
125 variance: self.variance + other.variance,
126 }
127 }
128
129 pub fn sub(&self, other: &Self) -> Self {
131 Self {
132 mean: self.mean - other.mean,
133 variance: self.variance + other.variance,
134 }
135 }
136
137 pub fn mul(&self, other: &Self) -> Self {
142 let mean = self.mean * other.mean;
143 let variance =
144 other.mean * other.mean * self.variance + self.mean * self.mean * other.variance;
145 Self { mean, variance }
146 }
147
148 pub fn div(&self, other: &Self) -> Self {
153 let mean = self.mean / other.mean;
154 let inv_mean2 = S::ONE / (other.mean * other.mean);
155 let variance = inv_mean2 * self.variance
156 + (self.mean * inv_mean2) * (self.mean * inv_mean2) * other.variance;
157 Self { mean, variance }
158 }
159
160 pub fn apply<F, D>(&self, f: F, df: D) -> Self
163 where
164 F: Fn(S) -> S,
165 D: Fn(S) -> S,
166 {
167 let mean = f(self.mean);
168 let deriv = df(self.mean);
169 let variance = deriv * deriv * self.variance;
170 Self { mean, variance }
171 }
172
173 pub fn square(&self) -> Self {
175 self.apply(|x| x * x, |x| S::from_f64(2.0) * x)
176 }
177
178 pub fn sqrt_unc(&self) -> Self {
180 self.apply(|x| x.sqrt(), |x| S::ONE / (S::from_f64(2.0) * x.sqrt()))
181 }
182
183 pub fn exp(&self) -> Self {
185 self.apply(|x| x.exp(), |x| x.exp())
186 }
187
188 pub fn ln(&self) -> Self {
190 self.apply(|x| x.ln(), |x| S::ONE / x)
191 }
192
193 pub fn sin(&self) -> Self {
195 self.apply(|x| x.sin(), |x| x.cos())
196 }
197
198 pub fn cos(&self) -> Self {
200 self.apply(|x| x.cos(), |x| -x.sin())
201 }
202}
203
204#[derive(Clone, Debug)]
211pub struct ParameterSensitivity<S: Scalar> {
212 pub name: String,
214 pub coefficient: S,
216 pub normalized: S,
218}
219
220#[derive(Clone, Debug)]
226pub struct ParameterSensitivityResult<S: Scalar> {
227 pub output: S,
229 pub sensitivities: Vec<ParameterSensitivity<S>>,
231}
232
233impl<S: Scalar> ParameterSensitivityResult<S> {
234 pub fn new(output: S, sensitivities: Vec<ParameterSensitivity<S>>) -> Self {
236 Self {
237 output,
238 sensitivities,
239 }
240 }
241
242 pub fn most_sensitive(&self) -> Option<&ParameterSensitivity<S>> {
244 self.sensitivities
245 .iter()
246 .max_by(|a, b| a.normalized.abs().partial_cmp(&b.normalized.abs()).unwrap())
247 }
248
249 pub fn propagate_uncertainty(&self, param_variances: &[S]) -> S {
252 assert_eq!(param_variances.len(), self.sensitivities.len());
253 let mut total_var = S::ZERO;
254 for (sens, &var) in self.sensitivities.iter().zip(param_variances.iter()) {
255 total_var += sens.coefficient * sens.coefficient * var;
256 }
257 total_var
258 }
259}
260
261pub fn compute_sensitivities<S: Scalar, F>(
270 f: F,
271 params: &[S],
272 names: &[&str],
273 h: Option<S>,
274) -> ParameterSensitivityResult<S>
275where
276 F: Fn(&[S]) -> S,
277{
278 let h = h.unwrap_or(S::EPSILON.cbrt());
279 let output = f(params);
280
281 let mut sensitivities = Vec::with_capacity(params.len());
282 let mut params_pert = params.to_vec();
283
284 for (i, name) in names.iter().enumerate() {
285 let p0 = params[i];
286 let step = h * (S::ONE + p0.abs());
287
288 params_pert[i] = p0 + step;
290 let f_plus = f(¶ms_pert);
291 params_pert[i] = p0 - step;
292 let f_minus = f(¶ms_pert);
293 params_pert[i] = p0;
294
295 let coeff = (f_plus - f_minus) / (S::from_f64(2.0) * step);
296 const NORMALIZED_ZERO_THRESHOLD: f64 = 1e-15;
297 let normalized = if output.abs() > S::from_f64(NORMALIZED_ZERO_THRESHOLD) {
298 (p0 / output) * coeff
299 } else {
300 S::ZERO
301 };
302
303 sensitivities.push(ParameterSensitivity {
304 name: name.to_string(),
305 coefficient: coeff,
306 normalized,
307 });
308 }
309
310 ParameterSensitivityResult::new(output, sensitivities)
311}
312
313#[derive(Clone, Copy, Debug, PartialEq)]
317pub struct Interval<S: Scalar> {
318 pub lower: S,
320 pub upper: S,
322}
323
324impl<S: Scalar> Interval<S> {
325 pub fn new(lower: S, upper: S) -> Self {
327 debug_assert!(lower <= upper, "Invalid interval: lower > upper");
328 Self { lower, upper }
329 }
330
331 pub fn point(x: S) -> Self {
333 Self { lower: x, upper: x }
334 }
335
336 pub fn from_center(center: S, half_width: S) -> Self {
338 Self {
339 lower: center - half_width,
340 upper: center + half_width,
341 }
342 }
343
344 pub fn width(&self) -> S {
346 self.upper - self.lower
347 }
348
349 pub fn center(&self) -> S {
351 (self.lower + self.upper) / S::from_f64(2.0)
352 }
353
354 pub fn contains(&self, x: S) -> bool {
356 x >= self.lower && x <= self.upper
357 }
358
359 pub fn add(&self, other: &Self) -> Self {
361 Self {
362 lower: self.lower + other.lower,
363 upper: self.upper + other.upper,
364 }
365 }
366
367 pub fn sub(&self, other: &Self) -> Self {
369 Self {
370 lower: self.lower - other.upper,
371 upper: self.upper - other.lower,
372 }
373 }
374
375 pub fn mul(&self, other: &Self) -> Self {
377 let products = [
378 self.lower * other.lower,
379 self.lower * other.upper,
380 self.upper * other.lower,
381 self.upper * other.upper,
382 ];
383 let lower = products.iter().fold(S::INFINITY, |a, &b| a.min(b));
384 let upper = products.iter().fold(S::NEG_INFINITY, |a, &b| a.max(b));
385 Self { lower, upper }
386 }
387
388 pub fn scale(&self, a: S) -> Self {
390 if a >= S::ZERO {
391 Self {
392 lower: a * self.lower,
393 upper: a * self.upper,
394 }
395 } else {
396 Self {
397 lower: a * self.upper,
398 upper: a * self.lower,
399 }
400 }
401 }
402
403 pub fn union(&self, other: &Self) -> Self {
405 Self {
406 lower: self.lower.min(other.lower),
407 upper: self.upper.max(other.upper),
408 }
409 }
410
411 pub fn intersection(&self, other: &Self) -> Option<Self> {
413 let lower = self.lower.max(other.lower);
414 let upper = self.upper.min(other.upper);
415 if lower <= upper {
416 Some(Self { lower, upper })
417 } else {
418 None
419 }
420 }
421}
422
423#[cfg(test)]
424mod tests {
425 use super::*;
426
427 #[test]
428 fn test_uncertain_basic() {
429 let x: Uncertain<f64> = Uncertain::from_std(10.0, 0.5);
430 assert!((x.mean - 10.0).abs() < 1e-10);
431 assert!((x.variance - 0.25).abs() < 1e-10);
432 assert!((x.std() - 0.5).abs() < 1e-10);
433 }
434
435 #[test]
436 fn test_uncertain_scale() {
437 let x: Uncertain<f64> = Uncertain::from_std(10.0, 1.0);
438 let y = x.scale(2.0);
439 assert!((y.mean - 20.0).abs() < 1e-10);
440 assert!((y.std() - 2.0).abs() < 1e-10);
441 }
442
443 #[test]
444 fn test_uncertain_add() {
445 let x: Uncertain<f64> = Uncertain::new(10.0, 1.0);
446 let y: Uncertain<f64> = Uncertain::new(5.0, 0.5);
447 let z = x.add(&y);
448 assert!((z.mean - 15.0).abs() < 1e-10);
449 assert!((z.variance - 1.5).abs() < 1e-10);
450 }
451
452 #[test]
453 fn test_uncertain_mul() {
454 let x: Uncertain<f64> = Uncertain::new(10.0, 1.0);
458 let y: Uncertain<f64> = Uncertain::new(5.0, 0.25);
459 let z = x.mul(&y);
460 assert!((z.mean - 50.0).abs() < 1e-10);
461 assert!((z.variance - 50.0).abs() < 1e-10);
462 }
463
464 #[test]
465 fn test_uncertain_exp() {
466 let x: Uncertain<f64> = Uncertain::new(0.0, 0.01);
469 let y = x.exp();
470 assert!((y.mean - 1.0).abs() < 1e-10);
471 assert!((y.variance - 0.01).abs() < 1e-10);
472 }
473
474 #[test]
475 fn test_sensitivity_analysis() {
476 let f = |p: &[f64]| p[0] * p[1];
479 let params = [3.0, 2.0];
480 let names = ["a", "b"];
481
482 let result = compute_sensitivities(f, ¶ms, &names, None);
483
484 assert!((result.output - 6.0).abs() < 1e-10);
485 assert!((result.sensitivities[0].coefficient - 2.0).abs() < 1e-5);
486 assert!((result.sensitivities[1].coefficient - 3.0).abs() < 1e-5);
487 }
488
489 #[test]
490 fn test_interval_basic() {
491 let i: Interval<f64> = Interval::new(1.0, 3.0);
492 assert!((i.width() - 2.0).abs() < 1e-10);
493 assert!((i.center() - 2.0).abs() < 1e-10);
494 assert!(i.contains(2.0));
495 assert!(!i.contains(4.0));
496 }
497
498 #[test]
499 fn test_interval_add() {
500 let a: Interval<f64> = Interval::new(1.0, 2.0);
501 let b: Interval<f64> = Interval::new(3.0, 4.0);
502 let c = a.add(&b);
503 assert!((c.lower - 4.0).abs() < 1e-10);
504 assert!((c.upper - 6.0).abs() < 1e-10);
505 }
506
507 #[test]
508 fn test_interval_mul() {
509 let a: Interval<f64> = Interval::new(-1.0, 2.0);
510 let b: Interval<f64> = Interval::new(1.0, 3.0);
511 let c = a.mul(&b);
512 assert!((c.lower + 3.0).abs() < 1e-10);
515 assert!((c.upper - 6.0).abs() < 1e-10);
516 }
517
518 #[test]
519 fn test_interval_intersection() {
520 let a: Interval<f64> = Interval::new(1.0, 4.0);
521 let b: Interval<f64> = Interval::new(2.0, 5.0);
522 let c = a.intersection(&b).unwrap();
523 assert!((c.lower - 2.0).abs() < 1e-10);
524 assert!((c.upper - 4.0).abs() < 1e-10);
525
526 let d: Interval<f64> = Interval::new(5.0, 6.0);
527 assert!(a.intersection(&d).is_none());
528 }
529
530 #[test]
535 fn test_uncertain_zero_variance() {
536 let x: Uncertain<f64> = Uncertain::certain(5.0);
537 assert!(x.variance.abs() < 1e-15);
538 assert!((x.std()).abs() < 1e-15);
539
540 let y = x.scale(2.0);
542 assert!(y.variance.abs() < 1e-15);
543
544 let z = x.add_const(10.0);
545 assert!(z.variance.abs() < 1e-15);
546 }
547
548 #[test]
549 fn test_uncertain_zero_mean() {
550 let x: Uncertain<f64> = Uncertain::new(0.0, 1.0);
551
552 assert!(x.cv().is_infinite());
554
555 let y = x.scale(100.0);
557 assert!(y.mean.abs() < 1e-15);
558 assert!((y.variance - 10000.0).abs() < 1e-10);
559 }
560
561 #[test]
562 fn test_uncertain_negative_values() {
563 let x: Uncertain<f64> = Uncertain::from_std(-10.0, 2.0);
564 assert!((x.mean + 10.0).abs() < 1e-10);
565
566 let y = x.scale(-1.0);
568 assert!((y.mean - 10.0).abs() < 1e-10);
569 assert!((y.variance - x.variance).abs() < 1e-10);
570 }
571
572 #[test]
573 fn test_uncertain_small_values() {
574 let x: Uncertain<f64> = Uncertain::from_std(1e-15, 1e-16);
575 let y: Uncertain<f64> = Uncertain::from_std(1e-15, 1e-16);
576
577 let z = x.add(&y);
578 assert!(z.mean > 0.0);
579 assert!(z.variance > 0.0);
580 }
581
582 #[test]
583 fn test_uncertain_large_values() {
584 let x: Uncertain<f64> = Uncertain::from_std(1e15, 1e14);
585 let y = x.scale(0.5);
586 assert!((y.mean - 5e14).abs() < 1e10);
587 }
588
589 #[test]
590 fn test_interval_point() {
591 let i: Interval<f64> = Interval::point(5.0);
592 assert!((i.width()).abs() < 1e-15);
593 assert!(i.contains(5.0));
594 assert!(!i.contains(5.0 + 1e-10));
595 }
596
597 #[test]
598 fn test_interval_negative() {
599 let i: Interval<f64> = Interval::new(-5.0, -2.0);
600 assert!(i.contains(-3.0));
601 assert!(!i.contains(0.0));
602 assert!((i.center() + 3.5).abs() < 1e-10);
603 }
604
605 #[test]
606 fn test_interval_scale_negative() {
607 let i: Interval<f64> = Interval::new(2.0, 4.0);
608 let j = i.scale(-2.0);
609 assert!((j.lower + 8.0).abs() < 1e-10);
611 assert!((j.upper + 4.0).abs() < 1e-10);
612 }
613
614 #[test]
615 fn test_interval_subtract() {
616 let a: Interval<f64> = Interval::new(2.0, 4.0);
617 let b: Interval<f64> = Interval::new(1.0, 3.0);
618 let c = a.sub(&b);
619 assert!((c.lower + 1.0).abs() < 1e-10);
621 assert!((c.upper - 3.0).abs() < 1e-10);
622 }
623
624 #[test]
625 fn test_interval_union() {
626 let a: Interval<f64> = Interval::new(1.0, 3.0);
627 let b: Interval<f64> = Interval::new(5.0, 7.0);
628 let c = a.union(&b);
629 assert!((c.lower - 1.0).abs() < 1e-10);
630 assert!((c.upper - 7.0).abs() < 1e-10);
631 }
632
633 #[test]
634 fn test_interval_self_intersection() {
635 let a: Interval<f64> = Interval::new(1.0, 5.0);
636 let b = a.intersection(&a).unwrap();
637 assert!((b.lower - a.lower).abs() < 1e-10);
638 assert!((b.upper - a.upper).abs() < 1e-10);
639 }
640
641 #[test]
642 fn test_uncertain_div_small_denominator() {
643 let x: Uncertain<f64> = Uncertain::new(10.0, 1.0);
644 let y: Uncertain<f64> = Uncertain::new(0.01, 0.0001);
645 let z = x.div(&y);
646 assert!(z.mean > 100.0);
648 }
649
650 #[test]
651 fn test_sensitivity_single_param() {
652 let f = |p: &[f64]| p[0] * p[0];
654 let params = [3.0];
655 let names = ["x"];
656
657 let result = compute_sensitivities(f, ¶ms, &names, None);
658 assert!((result.sensitivities[0].coefficient - 6.0).abs() < 1e-4);
660 }
661
662 #[test]
663 fn test_sensitivity_zero_output() {
664 let f = |p: &[f64]| p[0] - p[0];
666 let params = [5.0];
667 let names = ["x"];
668
669 let result = compute_sensitivities(f, ¶ms, &names, None);
670 assert!(result.output.abs() < 1e-15);
671 assert!(result.sensitivities[0].normalized.abs() < 1e-10);
673 }
674}