Skip to main content

Uncertain

Struct Uncertain 

Source
pub struct Uncertain<S>
where S: Scalar,
{ pub mean: S, pub variance: S, }
Expand description

An uncertain value with mean and variance.

§Example

use numra_core::uncertainty::Uncertain;

// A measurement of 10.0 ± 0.5 (std dev)
let x: Uncertain<f64> = Uncertain::new(10.0, 0.25); // variance = 0.5²

// Scale by 2: mean scales, variance scales by 4
let y = x.scale(2.0);
assert!((y.mean - 20.0).abs() < 1e-10);
assert!((y.variance - 1.0).abs() < 1e-10);

Fields§

§mean: S

Mean value

§variance: S

Variance (σ²)

Implementations§

Source§

impl<S> Uncertain<S>
where S: Scalar,

Source

pub fn new(mean: S, variance: S) -> Uncertain<S>

Create a new uncertain value.

Source

pub fn from_std(mean: S, std: S) -> Uncertain<S>

Create from mean and standard deviation.

Examples found in repository?
examples/lorenz.rs (line 46)
39fn main() {
40    println!("=== Lorenz Attractor with Uncertainty ===\n");
41
42    // Standard Lorenz parameters
43    let params = LorenzParams::default();
44
45    // Initial conditions (with uncertainty)
46    let x0 = Uncertain::from_std(1.0, 0.1); // x = 1.0 ± 0.1
47    let y0 = Uncertain::from_std(1.0, 0.1); // y = 1.0 ± 0.1
48    let z0 = Uncertain::from_std(1.0, 0.1); // z = 1.0 ± 0.1
49
50    println!("Initial conditions:");
51    println!("  x₀ = {:.3} ± {:.3}", x0.mean, x0.std());
52    println!("  y₀ = {:.3} ± {:.3}", y0.mean, y0.std());
53    println!("  z₀ = {:.3} ± {:.3}", z0.mean, z0.std());
54    println!();
55
56    // Create the ODE problem
57    let problem = OdeProblem::new(
58        |_t, y: &[f64], dydt: &mut [f64]| {
59            let (x, y_val, z) = (y[0], y[1], y[2]);
60            let sigma = 10.0;
61            let rho = 28.0;
62            let beta = 8.0 / 3.0;
63
64            dydt[0] = sigma * (y_val - x);
65            dydt[1] = x * (rho - z) - y_val;
66            dydt[2] = x * y_val - beta * z;
67        },
68        0.0,
69        20.0,
70        vec![x0.mean, y0.mean, z0.mean],
71    );
72
73    // Solve with default options
74    let options = SolverOptions::default().rtol(1e-8).atol(1e-10);
75
76    println!("Solving Lorenz system from t=0 to t=20...");
77    let result = DoPri5::solve(&problem, 0.0, 20.0, &[x0.mean, y0.mean, z0.mean], &options)
78        .expect("Solver failed");
79
80    // Get solution statistics
81    println!("  Steps accepted: {}", result.stats.n_accept);
82    println!("  Steps rejected: {}", result.stats.n_reject);
83    println!("  Function evals: {}", result.stats.n_eval);
84    println!();
85
86    // Final state
87    let y_final = result.y_final().expect("No final state");
88    println!("Final state at t=20:");
89    println!("  x = {:.6}", y_final[0]);
90    println!("  y = {:.6}", y_final[1]);
91    println!("  z = {:.6}", y_final[2]);
92    println!();
93
94    // Trajectory summary
95    println!("Trajectory has {} points", result.t.len());
96    println!();
97
98    // === Sensitivity Analysis ===
99    println!("=== Sensitivity Analysis ===\n");
100
101    // Compute sensitivity of final z-coordinate with respect to parameters
102    let solve_for_z = |p: &[f64]| -> f64 {
103        let problem = OdeProblem::new(
104            move |_t, y: &[f64], dydt: &mut [f64]| {
105                let (x, y_val, z) = (y[0], y[1], y[2]);
106                dydt[0] = p[0] * (y_val - x); // σ
107                dydt[1] = x * (p[1] - z) - y_val; // ρ
108                dydt[2] = x * y_val - p[2] * z; // β
109            },
110            0.0,
111            5.0, // Shorter time for sensitivity (chaos makes long-time sensitivity ill-defined)
112            vec![1.0, 1.0, 1.0],
113        );
114
115        let options = SolverOptions::default().rtol(1e-6);
116        let result = DoPri5::solve(&problem, 0.0, 5.0, &[1.0, 1.0, 1.0], &options)
117            .expect("Solver failed in sensitivity");
118        result.y_final().unwrap()[2] // Return final z
119    };
120
121    let nominal_params = [params.sigma, params.rho, params.beta];
122    let param_names = ["σ", "ρ", "β"];
123
124    let sens_result = compute_sensitivities(solve_for_z, &nominal_params, &param_names, None);
125
126    println!("Sensitivity of z(t=5) to parameters:");
127    for s in &sens_result.sensitivities {
128        println!(
129            "  ∂z/∂{}: {:.4} (normalized: {:.4})",
130            s.name, s.coefficient, s.normalized
131        );
132    }
133    println!();
134
135    // Find most sensitive parameter
136    if let Some(most_sens) = sens_result.most_sensitive() {
137        println!(
138            "Most sensitive parameter: {} (|normalized| = {:.4})",
139            most_sens.name,
140            most_sens.normalized.abs()
141        );
142    }
143    println!();
144
145    // === Uncertainty Propagation ===
146    println!("=== Uncertainty Propagation ===\n");
147
148    // Assume 5% uncertainty on each parameter
149    let param_std = [0.5, 1.4, 0.133]; // 5% of nominal
150    let param_vars: Vec<f64> = param_std.iter().map(|s| s * s).collect();
151
152    let total_var = sens_result.propagate_uncertainty(&param_vars);
153    let total_std = total_var.sqrt();
154
155    println!("Parameter uncertainties:");
156    println!("  σ = {:.2} ± {:.3}", params.sigma, param_std[0]);
157    println!("  ρ = {:.2} ± {:.3}", params.rho, param_std[1]);
158    println!("  β = {:.4} ± {:.4}", params.beta, param_std[2]);
159    println!();
160
161    println!("Propagated uncertainty in z(t=5):");
162    println!("  z = {:.4} ± {:.4}", sens_result.output, total_std);
163    println!();
164
165    // Individual contributions
166    println!("Uncertainty contributions:");
167    for (i, s) in sens_result.sensitivities.iter().enumerate() {
168        let contribution = (s.coefficient * param_std[i]).abs();
169        let pct = 100.0 * contribution * contribution / total_var;
170        println!("  {}: {:.2}%", s.name, pct);
171    }
172    println!();
173
174    // === Ensemble of trajectories ===
175    println!("=== Ensemble with Perturbed Initial Conditions ===\n");
176
177    let n_samples = 5;
178    println!("Running {} trajectories with perturbed ICs...", n_samples);
179
180    let perturbations = [
181        [1.0, 1.0, 1.0],
182        [1.1, 1.0, 1.0],
183        [1.0, 1.1, 1.0],
184        [0.9, 1.0, 1.0],
185        [1.0, 0.9, 1.0],
186    ];
187
188    for ic in perturbations.iter() {
189        let result = DoPri5::solve(&problem, 0.0, 10.0, ic, &options).expect("Solver failed");
190        let y_end = result.y_final().unwrap();
191        println!(
192            "  IC [{:.1}, {:.1}, {:.1}] → z(10) = {:.4}",
193            ic[0], ic[1], ic[2], y_end[2]
194        );
195    }
196    println!();
197
198    println!("Note: The Lorenz system is chaotic - small perturbations in ICs");
199    println!("lead to exponentially diverging trajectories over time.");
200    println!();
201
202    println!("=== Done ===");
203}
Source

pub fn certain(value: S) -> Uncertain<S>

Create a certain value (zero variance).

Source

pub fn std(&self) -> S

Standard deviation.

Examples found in repository?
examples/lorenz.rs (line 51)
39fn main() {
40    println!("=== Lorenz Attractor with Uncertainty ===\n");
41
42    // Standard Lorenz parameters
43    let params = LorenzParams::default();
44
45    // Initial conditions (with uncertainty)
46    let x0 = Uncertain::from_std(1.0, 0.1); // x = 1.0 ± 0.1
47    let y0 = Uncertain::from_std(1.0, 0.1); // y = 1.0 ± 0.1
48    let z0 = Uncertain::from_std(1.0, 0.1); // z = 1.0 ± 0.1
49
50    println!("Initial conditions:");
51    println!("  x₀ = {:.3} ± {:.3}", x0.mean, x0.std());
52    println!("  y₀ = {:.3} ± {:.3}", y0.mean, y0.std());
53    println!("  z₀ = {:.3} ± {:.3}", z0.mean, z0.std());
54    println!();
55
56    // Create the ODE problem
57    let problem = OdeProblem::new(
58        |_t, y: &[f64], dydt: &mut [f64]| {
59            let (x, y_val, z) = (y[0], y[1], y[2]);
60            let sigma = 10.0;
61            let rho = 28.0;
62            let beta = 8.0 / 3.0;
63
64            dydt[0] = sigma * (y_val - x);
65            dydt[1] = x * (rho - z) - y_val;
66            dydt[2] = x * y_val - beta * z;
67        },
68        0.0,
69        20.0,
70        vec![x0.mean, y0.mean, z0.mean],
71    );
72
73    // Solve with default options
74    let options = SolverOptions::default().rtol(1e-8).atol(1e-10);
75
76    println!("Solving Lorenz system from t=0 to t=20...");
77    let result = DoPri5::solve(&problem, 0.0, 20.0, &[x0.mean, y0.mean, z0.mean], &options)
78        .expect("Solver failed");
79
80    // Get solution statistics
81    println!("  Steps accepted: {}", result.stats.n_accept);
82    println!("  Steps rejected: {}", result.stats.n_reject);
83    println!("  Function evals: {}", result.stats.n_eval);
84    println!();
85
86    // Final state
87    let y_final = result.y_final().expect("No final state");
88    println!("Final state at t=20:");
89    println!("  x = {:.6}", y_final[0]);
90    println!("  y = {:.6}", y_final[1]);
91    println!("  z = {:.6}", y_final[2]);
92    println!();
93
94    // Trajectory summary
95    println!("Trajectory has {} points", result.t.len());
96    println!();
97
98    // === Sensitivity Analysis ===
99    println!("=== Sensitivity Analysis ===\n");
100
101    // Compute sensitivity of final z-coordinate with respect to parameters
102    let solve_for_z = |p: &[f64]| -> f64 {
103        let problem = OdeProblem::new(
104            move |_t, y: &[f64], dydt: &mut [f64]| {
105                let (x, y_val, z) = (y[0], y[1], y[2]);
106                dydt[0] = p[0] * (y_val - x); // σ
107                dydt[1] = x * (p[1] - z) - y_val; // ρ
108                dydt[2] = x * y_val - p[2] * z; // β
109            },
110            0.0,
111            5.0, // Shorter time for sensitivity (chaos makes long-time sensitivity ill-defined)
112            vec![1.0, 1.0, 1.0],
113        );
114
115        let options = SolverOptions::default().rtol(1e-6);
116        let result = DoPri5::solve(&problem, 0.0, 5.0, &[1.0, 1.0, 1.0], &options)
117            .expect("Solver failed in sensitivity");
118        result.y_final().unwrap()[2] // Return final z
119    };
120
121    let nominal_params = [params.sigma, params.rho, params.beta];
122    let param_names = ["σ", "ρ", "β"];
123
124    let sens_result = compute_sensitivities(solve_for_z, &nominal_params, &param_names, None);
125
126    println!("Sensitivity of z(t=5) to parameters:");
127    for s in &sens_result.sensitivities {
128        println!(
129            "  ∂z/∂{}: {:.4} (normalized: {:.4})",
130            s.name, s.coefficient, s.normalized
131        );
132    }
133    println!();
134
135    // Find most sensitive parameter
136    if let Some(most_sens) = sens_result.most_sensitive() {
137        println!(
138            "Most sensitive parameter: {} (|normalized| = {:.4})",
139            most_sens.name,
140            most_sens.normalized.abs()
141        );
142    }
143    println!();
144
145    // === Uncertainty Propagation ===
146    println!("=== Uncertainty Propagation ===\n");
147
148    // Assume 5% uncertainty on each parameter
149    let param_std = [0.5, 1.4, 0.133]; // 5% of nominal
150    let param_vars: Vec<f64> = param_std.iter().map(|s| s * s).collect();
151
152    let total_var = sens_result.propagate_uncertainty(&param_vars);
153    let total_std = total_var.sqrt();
154
155    println!("Parameter uncertainties:");
156    println!("  σ = {:.2} ± {:.3}", params.sigma, param_std[0]);
157    println!("  ρ = {:.2} ± {:.3}", params.rho, param_std[1]);
158    println!("  β = {:.4} ± {:.4}", params.beta, param_std[2]);
159    println!();
160
161    println!("Propagated uncertainty in z(t=5):");
162    println!("  z = {:.4} ± {:.4}", sens_result.output, total_std);
163    println!();
164
165    // Individual contributions
166    println!("Uncertainty contributions:");
167    for (i, s) in sens_result.sensitivities.iter().enumerate() {
168        let contribution = (s.coefficient * param_std[i]).abs();
169        let pct = 100.0 * contribution * contribution / total_var;
170        println!("  {}: {:.2}%", s.name, pct);
171    }
172    println!();
173
174    // === Ensemble of trajectories ===
175    println!("=== Ensemble with Perturbed Initial Conditions ===\n");
176
177    let n_samples = 5;
178    println!("Running {} trajectories with perturbed ICs...", n_samples);
179
180    let perturbations = [
181        [1.0, 1.0, 1.0],
182        [1.1, 1.0, 1.0],
183        [1.0, 1.1, 1.0],
184        [0.9, 1.0, 1.0],
185        [1.0, 0.9, 1.0],
186    ];
187
188    for ic in perturbations.iter() {
189        let result = DoPri5::solve(&problem, 0.0, 10.0, ic, &options).expect("Solver failed");
190        let y_end = result.y_final().unwrap();
191        println!(
192            "  IC [{:.1}, {:.1}, {:.1}] → z(10) = {:.4}",
193            ic[0], ic[1], ic[2], y_end[2]
194        );
195    }
196    println!();
197
198    println!("Note: The Lorenz system is chaotic - small perturbations in ICs");
199    println!("lead to exponentially diverging trajectories over time.");
200    println!();
201
202    println!("=== Done ===");
203}
Source

pub fn cv(&self) -> S

Coefficient of variation (std/mean).

Source

pub fn scale(&self, a: S) -> Uncertain<S>

Scale by a constant: y = a*x. Variance scales by a².

Source

pub fn add_const(&self, c: S) -> Uncertain<S>

Add a constant: y = x + c. Variance unchanged.

Source

pub fn add(&self, other: &Uncertain<S>) -> Uncertain<S>

Add another uncertain value (assuming independence). y = x1 + x2, Var(y) = Var(x1) + Var(x2).

Source

pub fn sub(&self, other: &Uncertain<S>) -> Uncertain<S>

Subtract another uncertain value (assuming independence).

Source

pub fn mul(&self, other: &Uncertain<S>) -> Uncertain<S>

Multiply by another uncertain value (first-order approximation). For y = x1 * x2: E[y] ≈ E[x1] * E[x2] Var(y) ≈ E[x2]² * Var(x1) + E[x1]² * Var(x2)

Source

pub fn div(&self, other: &Uncertain<S>) -> Uncertain<S>

Divide by another uncertain value (first-order approximation). For y = x1 / x2: E[y] ≈ E[x1] / E[x2] Var(y) ≈ (1/E[x2]²) * Var(x1) + (E[x1]/E[x2]²)² * Var(x2)

Source

pub fn apply<F, D>(&self, f: F, df: D) -> Uncertain<S>
where F: Fn(S) -> S, D: Fn(S) -> S,

Apply a function with known derivative. For y = f(x): Var(y) ≈ (f’(x))² * Var(x).

Source

pub fn square(&self) -> Uncertain<S>

Square: y = x².

Source

pub fn sqrt_unc(&self) -> Uncertain<S>

Square root: y = √x.

Source

pub fn exp(&self) -> Uncertain<S>

Exponential: y = e^x.

Source

pub fn ln(&self) -> Uncertain<S>

Natural log: y = ln(x).

Source

pub fn sin(&self) -> Uncertain<S>

Sine: y = sin(x).

Source

pub fn cos(&self) -> Uncertain<S>

Cosine: y = cos(x).

Trait Implementations§

Source§

impl<S> Clone for Uncertain<S>
where S: Clone + Scalar,

Source§

fn clone(&self) -> Uncertain<S>

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl<S> Debug for Uncertain<S>
where S: Debug + Scalar,

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more
Source§

impl<S> Default for Uncertain<S>
where S: Scalar,

Source§

fn default() -> Uncertain<S>

Returns the “default value” for a type. Read more
Source§

impl<S> PartialEq for Uncertain<S>
where S: PartialEq + Scalar,

Source§

fn eq(&self, other: &Uncertain<S>) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 (const: unstable) · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, and should not be overridden without very good reason.
Source§

impl<S> Copy for Uncertain<S>
where S: Copy + Scalar,

Source§

impl<S> StructuralPartialEq for Uncertain<S>
where S: Scalar,

Auto Trait Implementations§

§

impl<S> Freeze for Uncertain<S>
where S: Freeze,

§

impl<S> RefUnwindSafe for Uncertain<S>
where S: RefUnwindSafe,

§

impl<S> Send for Uncertain<S>

§

impl<S> Sync for Uncertain<S>

§

impl<S> Unpin for Uncertain<S>
where S: Unpin,

§

impl<S> UnsafeUnpin for Uncertain<S>
where S: UnsafeUnpin,

§

impl<S> UnwindSafe for Uncertain<S>
where S: UnwindSafe,

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> if into_left is true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> if into_left(&self) returns true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

impl<T> Pointable for T

Source§

const ALIGN: usize

The alignment of pointer.
Source§

type Init = T

The type for initializers.
Source§

unsafe fn init(init: <T as Pointable>::Init) -> usize

Initializes a with the given initializer. Read more
Source§

unsafe fn deref<'a>(ptr: usize) -> &'a T

Dereferences the given pointer. Read more
Source§

unsafe fn deref_mut<'a>(ptr: usize) -> &'a mut T

Mutably dereferences the given pointer. Read more
Source§

unsafe fn drop(ptr: usize)

Drops the object pointed to by the given pointer. Read more
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V

Source§

impl<T> Boilerplate for T
where T: Copy + Send + Sync + Debug + PartialEq + 'static,