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: SMean value
variance: SVariance (σ²)
Implementations§
Source§impl<S> Uncertain<S>where
S: Scalar,
impl<S> Uncertain<S>where
S: Scalar,
Sourcepub fn from_std(mean: S, std: S) -> Uncertain<S>
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, ¶m_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(¶m_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}Sourcepub fn std(&self) -> S
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, ¶m_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(¶m_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}Sourcepub fn add(&self, other: &Uncertain<S>) -> Uncertain<S>
pub fn add(&self, other: &Uncertain<S>) -> Uncertain<S>
Add another uncertain value (assuming independence). y = x1 + x2, Var(y) = Var(x1) + Var(x2).
Sourcepub fn sub(&self, other: &Uncertain<S>) -> Uncertain<S>
pub fn sub(&self, other: &Uncertain<S>) -> Uncertain<S>
Subtract another uncertain value (assuming independence).
Sourcepub fn mul(&self, other: &Uncertain<S>) -> Uncertain<S>
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)
Sourcepub fn div(&self, other: &Uncertain<S>) -> Uncertain<S>
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)
Trait Implementations§
Source§impl<S> PartialEq for Uncertain<S>
impl<S> PartialEq for Uncertain<S>
impl<S> Copy for Uncertain<S>
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> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Mutably borrows from an owned value. Read more
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self>
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 moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
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