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>(
269 f: F,
270 params: &[S],
271 names: &[&str],
272 h: Option<S>,
273) -> ParameterSensitivityResult<S>
274where
275 F: Fn(&[S]) -> S,
276{
277 const DEFAULT_SENSITIVITY_EPS: f64 = 1e-7;
279 let h = h.unwrap_or(S::from_f64(DEFAULT_SENSITIVITY_EPS));
280 let output = f(params);
281
282 let mut sensitivities = Vec::with_capacity(params.len());
283 let mut params_pert = params.to_vec();
284
285 for (i, name) in names.iter().enumerate() {
286 let p0 = params[i];
287 let step = h * (S::ONE + p0.abs());
288
289 params_pert[i] = p0 + step;
291 let f_plus = f(¶ms_pert);
292 params_pert[i] = p0 - step;
293 let f_minus = f(¶ms_pert);
294 params_pert[i] = p0;
295
296 let coeff = (f_plus - f_minus) / (S::from_f64(2.0) * step);
297 const NORMALIZED_ZERO_THRESHOLD: f64 = 1e-15;
298 let normalized = if output.abs() > S::from_f64(NORMALIZED_ZERO_THRESHOLD) {
299 (p0 / output) * coeff
300 } else {
301 S::ZERO
302 };
303
304 sensitivities.push(ParameterSensitivity {
305 name: name.to_string(),
306 coefficient: coeff,
307 normalized,
308 });
309 }
310
311 ParameterSensitivityResult::new(output, sensitivities)
312}
313
314#[derive(Clone, Copy, Debug, PartialEq)]
318pub struct Interval<S: Scalar> {
319 pub lower: S,
321 pub upper: S,
323}
324
325impl<S: Scalar> Interval<S> {
326 pub fn new(lower: S, upper: S) -> Self {
328 debug_assert!(lower <= upper, "Invalid interval: lower > upper");
329 Self { lower, upper }
330 }
331
332 pub fn point(x: S) -> Self {
334 Self { lower: x, upper: x }
335 }
336
337 pub fn from_center(center: S, half_width: S) -> Self {
339 Self {
340 lower: center - half_width,
341 upper: center + half_width,
342 }
343 }
344
345 pub fn width(&self) -> S {
347 self.upper - self.lower
348 }
349
350 pub fn center(&self) -> S {
352 (self.lower + self.upper) / S::from_f64(2.0)
353 }
354
355 pub fn contains(&self, x: S) -> bool {
357 x >= self.lower && x <= self.upper
358 }
359
360 pub fn add(&self, other: &Self) -> Self {
362 Self {
363 lower: self.lower + other.lower,
364 upper: self.upper + other.upper,
365 }
366 }
367
368 pub fn sub(&self, other: &Self) -> Self {
370 Self {
371 lower: self.lower - other.upper,
372 upper: self.upper - other.lower,
373 }
374 }
375
376 pub fn mul(&self, other: &Self) -> Self {
378 let products = [
379 self.lower * other.lower,
380 self.lower * other.upper,
381 self.upper * other.lower,
382 self.upper * other.upper,
383 ];
384 let lower = products.iter().fold(S::INFINITY, |a, &b| a.min(b));
385 let upper = products.iter().fold(S::NEG_INFINITY, |a, &b| a.max(b));
386 Self { lower, upper }
387 }
388
389 pub fn scale(&self, a: S) -> Self {
391 if a >= S::ZERO {
392 Self {
393 lower: a * self.lower,
394 upper: a * self.upper,
395 }
396 } else {
397 Self {
398 lower: a * self.upper,
399 upper: a * self.lower,
400 }
401 }
402 }
403
404 pub fn union(&self, other: &Self) -> Self {
406 Self {
407 lower: self.lower.min(other.lower),
408 upper: self.upper.max(other.upper),
409 }
410 }
411
412 pub fn intersection(&self, other: &Self) -> Option<Self> {
414 let lower = self.lower.max(other.lower);
415 let upper = self.upper.min(other.upper);
416 if lower <= upper {
417 Some(Self { lower, upper })
418 } else {
419 None
420 }
421 }
422}
423
424#[cfg(test)]
425mod tests {
426 use super::*;
427
428 #[test]
429 fn test_uncertain_basic() {
430 let x: Uncertain<f64> = Uncertain::from_std(10.0, 0.5);
431 assert!((x.mean - 10.0).abs() < 1e-10);
432 assert!((x.variance - 0.25).abs() < 1e-10);
433 assert!((x.std() - 0.5).abs() < 1e-10);
434 }
435
436 #[test]
437 fn test_uncertain_scale() {
438 let x: Uncertain<f64> = Uncertain::from_std(10.0, 1.0);
439 let y = x.scale(2.0);
440 assert!((y.mean - 20.0).abs() < 1e-10);
441 assert!((y.std() - 2.0).abs() < 1e-10);
442 }
443
444 #[test]
445 fn test_uncertain_add() {
446 let x: Uncertain<f64> = Uncertain::new(10.0, 1.0);
447 let y: Uncertain<f64> = Uncertain::new(5.0, 0.5);
448 let z = x.add(&y);
449 assert!((z.mean - 15.0).abs() < 1e-10);
450 assert!((z.variance - 1.5).abs() < 1e-10);
451 }
452
453 #[test]
454 fn test_uncertain_mul() {
455 let x: Uncertain<f64> = Uncertain::new(10.0, 1.0);
459 let y: Uncertain<f64> = Uncertain::new(5.0, 0.25);
460 let z = x.mul(&y);
461 assert!((z.mean - 50.0).abs() < 1e-10);
462 assert!((z.variance - 50.0).abs() < 1e-10);
463 }
464
465 #[test]
466 fn test_uncertain_exp() {
467 let x: Uncertain<f64> = Uncertain::new(0.0, 0.01);
470 let y = x.exp();
471 assert!((y.mean - 1.0).abs() < 1e-10);
472 assert!((y.variance - 0.01).abs() < 1e-10);
473 }
474
475 #[test]
476 fn test_sensitivity_analysis() {
477 let f = |p: &[f64]| p[0] * p[1];
480 let params = [3.0, 2.0];
481 let names = ["a", "b"];
482
483 let result = compute_sensitivities(f, ¶ms, &names, None);
484
485 assert!((result.output - 6.0).abs() < 1e-10);
486 assert!((result.sensitivities[0].coefficient - 2.0).abs() < 1e-5);
487 assert!((result.sensitivities[1].coefficient - 3.0).abs() < 1e-5);
488 }
489
490 #[test]
491 fn test_interval_basic() {
492 let i: Interval<f64> = Interval::new(1.0, 3.0);
493 assert!((i.width() - 2.0).abs() < 1e-10);
494 assert!((i.center() - 2.0).abs() < 1e-10);
495 assert!(i.contains(2.0));
496 assert!(!i.contains(4.0));
497 }
498
499 #[test]
500 fn test_interval_add() {
501 let a: Interval<f64> = Interval::new(1.0, 2.0);
502 let b: Interval<f64> = Interval::new(3.0, 4.0);
503 let c = a.add(&b);
504 assert!((c.lower - 4.0).abs() < 1e-10);
505 assert!((c.upper - 6.0).abs() < 1e-10);
506 }
507
508 #[test]
509 fn test_interval_mul() {
510 let a: Interval<f64> = Interval::new(-1.0, 2.0);
511 let b: Interval<f64> = Interval::new(1.0, 3.0);
512 let c = a.mul(&b);
513 assert!((c.lower + 3.0).abs() < 1e-10);
516 assert!((c.upper - 6.0).abs() < 1e-10);
517 }
518
519 #[test]
520 fn test_interval_intersection() {
521 let a: Interval<f64> = Interval::new(1.0, 4.0);
522 let b: Interval<f64> = Interval::new(2.0, 5.0);
523 let c = a.intersection(&b).unwrap();
524 assert!((c.lower - 2.0).abs() < 1e-10);
525 assert!((c.upper - 4.0).abs() < 1e-10);
526
527 let d: Interval<f64> = Interval::new(5.0, 6.0);
528 assert!(a.intersection(&d).is_none());
529 }
530
531 #[test]
536 fn test_uncertain_zero_variance() {
537 let x: Uncertain<f64> = Uncertain::certain(5.0);
538 assert!(x.variance.abs() < 1e-15);
539 assert!((x.std()).abs() < 1e-15);
540
541 let y = x.scale(2.0);
543 assert!(y.variance.abs() < 1e-15);
544
545 let z = x.add_const(10.0);
546 assert!(z.variance.abs() < 1e-15);
547 }
548
549 #[test]
550 fn test_uncertain_zero_mean() {
551 let x: Uncertain<f64> = Uncertain::new(0.0, 1.0);
552
553 assert!(x.cv().is_infinite());
555
556 let y = x.scale(100.0);
558 assert!(y.mean.abs() < 1e-15);
559 assert!((y.variance - 10000.0).abs() < 1e-10);
560 }
561
562 #[test]
563 fn test_uncertain_negative_values() {
564 let x: Uncertain<f64> = Uncertain::from_std(-10.0, 2.0);
565 assert!((x.mean + 10.0).abs() < 1e-10);
566
567 let y = x.scale(-1.0);
569 assert!((y.mean - 10.0).abs() < 1e-10);
570 assert!((y.variance - x.variance).abs() < 1e-10);
571 }
572
573 #[test]
574 fn test_uncertain_small_values() {
575 let x: Uncertain<f64> = Uncertain::from_std(1e-15, 1e-16);
576 let y: Uncertain<f64> = Uncertain::from_std(1e-15, 1e-16);
577
578 let z = x.add(&y);
579 assert!(z.mean > 0.0);
580 assert!(z.variance > 0.0);
581 }
582
583 #[test]
584 fn test_uncertain_large_values() {
585 let x: Uncertain<f64> = Uncertain::from_std(1e15, 1e14);
586 let y = x.scale(0.5);
587 assert!((y.mean - 5e14).abs() < 1e10);
588 }
589
590 #[test]
591 fn test_interval_point() {
592 let i: Interval<f64> = Interval::point(5.0);
593 assert!((i.width()).abs() < 1e-15);
594 assert!(i.contains(5.0));
595 assert!(!i.contains(5.0 + 1e-10));
596 }
597
598 #[test]
599 fn test_interval_negative() {
600 let i: Interval<f64> = Interval::new(-5.0, -2.0);
601 assert!(i.contains(-3.0));
602 assert!(!i.contains(0.0));
603 assert!((i.center() + 3.5).abs() < 1e-10);
604 }
605
606 #[test]
607 fn test_interval_scale_negative() {
608 let i: Interval<f64> = Interval::new(2.0, 4.0);
609 let j = i.scale(-2.0);
610 assert!((j.lower + 8.0).abs() < 1e-10);
612 assert!((j.upper + 4.0).abs() < 1e-10);
613 }
614
615 #[test]
616 fn test_interval_subtract() {
617 let a: Interval<f64> = Interval::new(2.0, 4.0);
618 let b: Interval<f64> = Interval::new(1.0, 3.0);
619 let c = a.sub(&b);
620 assert!((c.lower + 1.0).abs() < 1e-10);
622 assert!((c.upper - 3.0).abs() < 1e-10);
623 }
624
625 #[test]
626 fn test_interval_union() {
627 let a: Interval<f64> = Interval::new(1.0, 3.0);
628 let b: Interval<f64> = Interval::new(5.0, 7.0);
629 let c = a.union(&b);
630 assert!((c.lower - 1.0).abs() < 1e-10);
631 assert!((c.upper - 7.0).abs() < 1e-10);
632 }
633
634 #[test]
635 fn test_interval_self_intersection() {
636 let a: Interval<f64> = Interval::new(1.0, 5.0);
637 let b = a.intersection(&a).unwrap();
638 assert!((b.lower - a.lower).abs() < 1e-10);
639 assert!((b.upper - a.upper).abs() < 1e-10);
640 }
641
642 #[test]
643 fn test_uncertain_div_small_denominator() {
644 let x: Uncertain<f64> = Uncertain::new(10.0, 1.0);
645 let y: Uncertain<f64> = Uncertain::new(0.01, 0.0001);
646 let z = x.div(&y);
647 assert!(z.mean > 100.0);
649 }
650
651 #[test]
652 fn test_sensitivity_single_param() {
653 let f = |p: &[f64]| p[0] * p[0];
655 let params = [3.0];
656 let names = ["x"];
657
658 let result = compute_sensitivities(f, ¶ms, &names, None);
659 assert!((result.sensitivities[0].coefficient - 6.0).abs() < 1e-4);
661 }
662
663 #[test]
664 fn test_sensitivity_zero_output() {
665 let f = |p: &[f64]| p[0] - p[0];
667 let params = [5.0];
668 let names = ["x"];
669
670 let result = compute_sensitivities(f, ¶ms, &names, None);
671 assert!(result.output.abs() < 1e-15);
672 assert!(result.sensitivities[0].normalized.abs() < 1e-10);
674 }
675}