use super::functions::*;
use rand::RngExt;
use std::f64::consts::PI;
pub struct MonteCarloFemSolver {
pub k_mean: f64,
pub k_std: f64,
pub load: f64,
pub samples: Vec<f64>,
}
impl MonteCarloFemSolver {
pub fn new(k_mean: f64, k_std: f64, load: f64) -> Self {
Self {
k_mean,
k_std,
load,
samples: Vec::new(),
}
}
pub fn run(&mut self, n_samples: usize) {
use rand::RngExt as _;
let mut rng = rand::rng();
self.samples.reserve(n_samples);
for _ in 0..n_samples {
let u1: f64 = rng.random_range(f64::EPSILON..1.0_f64);
let u2: f64 = rng.random_range(0.0_f64..1.0_f64);
let z = box_muller(u1, u2);
let k = (self.k_mean + self.k_std * z).max(self.k_mean * 1e-6);
self.samples.push(self.load / k);
}
}
pub fn clear(&mut self) {
self.samples.clear();
}
pub fn mean(&self) -> f64 {
if self.samples.is_empty() {
0.0
} else {
self.samples.iter().sum::<f64>() / self.samples.len() as f64
}
}
pub fn std_dev(&self) -> f64 {
let n = self.samples.len();
if n < 2 {
return 0.0;
}
let mu = self.mean();
let var = self.samples.iter().map(|x| (x - mu).powi(2)).sum::<f64>() / n as f64;
var.sqrt()
}
pub fn cov(&self) -> f64 {
let mu = self.mean().abs();
if mu < f64::EPSILON {
0.0
} else {
self.std_dev() / mu
}
}
pub fn percentile(&self, p: f64) -> f64 {
let n = self.samples.len();
if n == 0 {
return 0.0;
}
let mut sorted = self.samples.clone();
sorted.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
let idx = ((p / 100.0) * (n - 1) as f64).round() as usize;
sorted[idx.min(n - 1)]
}
pub fn failure_probability(&self, threshold: f64) -> f64 {
if self.samples.is_empty() {
return 0.0;
}
let n_fail = self.samples.iter().filter(|&&s| s > threshold).count();
n_fail as f64 / self.samples.len() as f64
}
pub fn confidence_interval_mean_95(&self) -> (f64, f64) {
let n = self.samples.len();
if n < 2 {
return (self.mean(), self.mean());
}
let mu = self.mean();
let se = self.std_dev() / (n as f64).sqrt();
(mu - 1.96 * se, mu + 1.96 * se)
}
}
pub struct KarhunenLoeveExpansion {
pub n_terms: usize,
pub eigenvalues: Vec<f64>,
pub eigenvectors: Vec<Vec<f64>>,
}
impl KarhunenLoeveExpansion {
pub fn new(n_terms: usize) -> Self {
Self {
n_terms,
eigenvalues: Vec::new(),
eigenvectors: Vec::new(),
}
}
pub fn add_mode(&mut self, eigenvalue: f64, eigenvector: Vec<f64>) {
self.eigenvalues.push(eigenvalue);
self.eigenvectors.push(eigenvector);
}
pub fn sample(&self, xi: &[f64]) -> Vec<f64> {
let n_modes = self.eigenvalues.len().min(xi.len());
if n_modes == 0 {
return Vec::new();
}
let n_nodes = self.eigenvectors[0].len();
let mut result = vec![0.0f64; n_nodes];
for ((&ev, &xi_i), eigvec) in self
.eigenvalues
.iter()
.zip(xi.iter())
.zip(self.eigenvectors.iter())
.take(n_modes)
{
let scale = ev.max(0.0).sqrt() * xi_i;
for (res_j, &e_j) in result.iter_mut().zip(eigvec.iter()) {
*res_j += scale * e_j;
}
}
result
}
pub fn total_variance(&self) -> f64 {
self.eigenvalues.iter().cloned().sum()
}
pub fn energy_ratio(&self, mode_idx: usize) -> f64 {
let total = self.total_variance();
if total < f64::EPSILON || mode_idx >= self.eigenvalues.len() {
return 0.0;
}
self.eigenvalues[mode_idx] / total
}
pub fn cumulative_energy(&self, k: usize) -> f64 {
let total = self.total_variance();
if total < f64::EPSILON {
return 0.0;
}
self.eigenvalues.iter().take(k).sum::<f64>() / total
}
pub fn from_covariance(cov: &[f64], n: usize, n_modes: usize, max_iter: usize) -> Self {
let mut kle = KarhunenLoeveExpansion::new(n_modes);
if n == 0 || n_modes == 0 {
return kle;
}
let mut residual: Vec<f64> = cov.to_vec();
for _mode in 0..n_modes {
let mut v: Vec<f64> = (0..n).map(|i| if i == 0 { 1.0 } else { 0.0 }).collect();
let mut lambda = 0.0_f64;
for _ in 0..max_iter {
let mut w = vec![0.0f64; n];
for i in 0..n {
for j in 0..n {
w[i] += residual[i * n + j] * v[j];
}
}
let rq: f64 = v.iter().zip(w.iter()).map(|(a, b)| a * b).sum();
lambda = rq;
let norm: f64 = w.iter().map(|x| x * x).sum::<f64>().sqrt();
if norm < 1e-30 {
break;
}
v = w.iter().map(|x| x / norm).collect();
}
if lambda > 0.0 {
for i in 0..n {
for j in 0..n {
residual[i * n + j] -= lambda * v[i] * v[j];
}
}
kle.add_mode(lambda, v);
}
}
kle
}
}
pub struct StochasticFEM {
pub n_nodes: usize,
pub n_random: usize,
pub mean_k: f64,
pub std_k: f64,
}
impl StochasticFEM {
pub fn new(n_nodes: usize, n_random: usize, mean_k: f64, std_k: f64) -> Self {
Self {
n_nodes,
n_random,
mean_k,
std_k,
}
}
pub fn cov(&self) -> f64 {
if self.mean_k.abs() < f64::EPSILON {
return 0.0;
}
self.std_k / self.mean_k
}
}
pub struct StochasticFemProblem {
pub nodes: Vec<f64>,
pub mean_field: Vec<f64>,
pub variance_field: Vec<f64>,
pub corr_length: f64,
pub sigma: f64,
}
impl StochasticFemProblem {
pub fn new_uniform(
n_nodes: usize,
length: f64,
mean_value: f64,
sigma: f64,
corr_length: f64,
) -> Self {
let nodes: Vec<f64> = (0..n_nodes)
.map(|i| i as f64 * length / (n_nodes.max(2) - 1) as f64)
.collect();
let mean_field = vec![mean_value; n_nodes];
let variance_field = vec![sigma * sigma; n_nodes];
Self {
nodes,
mean_field,
variance_field,
corr_length,
sigma,
}
}
pub fn n_nodes(&self) -> usize {
self.nodes.len()
}
pub fn covariance_matrix_exponential(&self) -> Vec<Vec<f64>> {
let n = self.nodes.len();
let mut c = vec![vec![0.0f64; n]; n];
for (i, c_row) in c.iter_mut().enumerate() {
for (j, c_ij) in c_row.iter_mut().enumerate() {
*c_ij = covariance_exponential(
self.nodes[i],
self.nodes[j],
self.sigma,
self.corr_length,
);
}
}
c
}
pub fn covariance_matrix_gaussian(&self) -> Vec<Vec<f64>> {
let n = self.nodes.len();
let mut c = vec![vec![0.0f64; n]; n];
for (i, c_row) in c.iter_mut().enumerate() {
for (j, c_ij) in c_row.iter_mut().enumerate() {
*c_ij =
covariance_gaussian(self.nodes[i], self.nodes[j], self.sigma, self.corr_length);
}
}
c
}
pub fn update_statistics_from_ensemble(&mut self, ensemble: &[Vec<f64>]) {
let n = self.nodes.len();
if ensemble.is_empty() {
return;
}
let m = ensemble.len() as f64;
let mut mean = vec![0.0f64; n];
for sample in ensemble.iter() {
for (i, &v) in sample.iter().enumerate().take(n) {
mean[i] += v;
}
}
for v in mean.iter_mut() {
*v /= m;
}
let mut var = vec![0.0f64; n];
for sample in ensemble.iter() {
for (i, &v) in sample.iter().enumerate().take(n) {
var[i] += (v - mean[i]).powi(2);
}
}
for v in var.iter_mut() {
*v /= m;
}
self.mean_field = mean;
self.variance_field = var;
}
pub fn cov_field(&self) -> Vec<f64> {
self.mean_field
.iter()
.zip(self.variance_field.iter())
.map(|(&mu, &var)| {
if mu.abs() < f64::EPSILON {
0.0
} else {
var.sqrt() / mu.abs()
}
})
.collect()
}
}
pub struct ReliabilityFem {
pub means: Vec<f64>,
pub std_devs: Vec<f64>,
pub lsf_coeffs: Vec<f64>,
}
impl ReliabilityFem {
pub fn new(means: Vec<f64>, std_devs: Vec<f64>, lsf_coeffs: Vec<f64>) -> Self {
Self {
means,
std_devs,
lsf_coeffs,
}
}
pub fn lsf(&self, x: &[f64]) -> f64 {
let k = self.lsf_coeffs.len().saturating_sub(1);
let mut g = if self.lsf_coeffs.is_empty() {
0.0
} else {
self.lsf_coeffs[0]
};
for (coeff, &xi) in self.lsf_coeffs[1..].iter().zip(x.iter()).take(k) {
g += coeff * xi;
}
g
}
pub fn form_beta(&self) -> f64 {
let k = self.means.len().min(self.std_devs.len());
let n_coeffs = self.lsf_coeffs.len();
let a0 = if n_coeffs == 0 {
0.0
} else {
let mut s = self.lsf_coeffs[0];
for i in 0..k.min(n_coeffs.saturating_sub(1)) {
s += self.lsf_coeffs[i + 1] * self.means[i];
}
s
};
let denom_sq: f64 = (0..k.min(n_coeffs.saturating_sub(1)))
.map(|i| (self.lsf_coeffs[i + 1] * self.std_devs[i]).powi(2))
.sum();
if denom_sq < f64::EPSILON {
return if a0 >= 0.0 {
f64::INFINITY
} else {
f64::NEG_INFINITY
};
}
a0 / denom_sq.sqrt()
}
pub fn form_pf(&self) -> f64 {
phi_cdf(-self.form_beta())
}
pub fn sorm_pf_breitung(&self, curvatures: &[f64]) -> f64 {
let beta = self.form_beta();
if !beta.is_finite() {
return if beta > 0.0 { 0.0 } else { 1.0 };
}
let correction: f64 = curvatures
.iter()
.map(|&kappa| {
let v = 1.0 + beta * kappa;
if v > 0.0 { v.powf(-0.5) } else { 0.0 }
})
.product();
phi_cdf(-beta) * correction
}
pub fn monte_carlo_pf(&self, n_samples: usize) -> f64 {
let mut rng = rand::rng();
let k = self.means.len().min(self.std_devs.len());
if n_samples == 0 || k == 0 {
return 0.0;
}
let mut n_fail = 0usize;
let mut x = vec![0.0f64; k];
for _ in 0..n_samples {
for (x_i, (mean_i, std_i)) in x
.iter_mut()
.zip(self.means.iter().zip(self.std_devs.iter()))
{
let u1: f64 = rng.random_range(f64::EPSILON..1.0_f64);
let u2: f64 = rng.random_range(0.0_f64..1.0_f64);
let z = box_muller(u1, u2);
*x_i = mean_i + std_i * z;
}
if self.lsf(&x) < 0.0 {
n_fail += 1;
}
}
n_fail as f64 / n_samples as f64
}
pub fn safety_factor(&self) -> f64 {
if self.means.len() < 2 || self.means[1].abs() < f64::EPSILON {
return 0.0;
}
self.means[0] / self.means[1]
}
pub fn importance_vector(&self) -> Vec<f64> {
let k = self.means.len().min(self.std_devs.len());
let n_coeffs = self.lsf_coeffs.len();
let scaled: Vec<f64> = (0..k.min(n_coeffs.saturating_sub(1)))
.map(|i| self.lsf_coeffs[i + 1] * self.std_devs[i])
.collect();
let norm: f64 = scaled.iter().map(|v| v * v).sum::<f64>().sqrt();
if norm < f64::EPSILON {
return vec![0.0; k];
}
scaled.iter().map(|v| v / norm).collect()
}
}
pub struct SensitivityAnalysis {
pub n_inputs: usize,
pub n_samples: usize,
}
impl SensitivityAnalysis {
pub fn new(n_inputs: usize, n_samples: usize) -> Self {
Self {
n_inputs,
n_samples,
}
}
pub fn morris_screening<F>(&self, f: &F, delta: f64) -> (Vec<f64>, Vec<f64>)
where
F: Fn(&[f64]) -> f64,
{
let mut rng = rand::rng();
let k = self.n_inputs;
let r = self.n_samples.max(1);
let mut ee: Vec<Vec<f64>> = vec![Vec::new(); k];
for _ in 0..r {
let mut x: Vec<f64> = (0..k).map(|_| rng.random_range(0.0_f64..1.0_f64)).collect();
let y0 = f(&x);
let mut perm: Vec<usize> = (0..k).collect();
for i in (1..k).rev() {
let j = rng.random_range(0usize..=i);
perm.swap(i, j);
}
for &dim in &perm {
let x_old = x[dim];
let step = if x[dim] + delta <= 1.0 { delta } else { -delta };
x[dim] += step;
let y1 = f(&x);
let eff = (y1 - y0) / step;
ee[dim].push(eff);
x[dim] = x_old + step;
}
}
let mu_star: Vec<f64> = ee
.iter()
.map(|v| {
if v.is_empty() {
0.0
} else {
v.iter().map(|e| e.abs()).sum::<f64>() / v.len() as f64
}
})
.collect();
let sigma: Vec<f64> = ee
.iter()
.map(|v| {
if v.len() < 2 {
return 0.0;
}
let mu = v.iter().sum::<f64>() / v.len() as f64;
(v.iter().map(|e| (e - mu).powi(2)).sum::<f64>() / v.len() as f64).sqrt()
})
.collect();
(mu_star, sigma)
}
pub fn saltelli_sobol<F>(&self, f: &F) -> (Vec<f64>, Vec<f64>)
where
F: Fn(&[f64]) -> f64,
{
let mut rng = rand::rng();
let k = self.n_inputs;
let n = self.n_samples.max(1);
let a_mat: Vec<Vec<f64>> = (0..n)
.map(|_| (0..k).map(|_| rng.random_range(0.0_f64..1.0_f64)).collect())
.collect();
let b_mat: Vec<Vec<f64>> = (0..n)
.map(|_| (0..k).map(|_| rng.random_range(0.0_f64..1.0_f64)).collect())
.collect();
let fa: Vec<f64> = a_mat.iter().map(|row| f(row)).collect();
let fb: Vec<f64> = b_mat.iter().map(|row| f(row)).collect();
let total_mean = (fa.iter().sum::<f64>() + fb.iter().sum::<f64>()) / (2 * n) as f64;
let total_var: f64 = fa
.iter()
.chain(fb.iter())
.map(|&y| (y - total_mean).powi(2))
.sum::<f64>()
/ (2 * n) as f64;
let mut s1 = vec![0.0f64; k];
let mut st = vec![0.0f64; k];
if total_var < f64::EPSILON {
return (s1, st);
}
for j in 0..k {
let ab: Vec<f64> = a_mat
.iter()
.zip(b_mat.iter())
.map(|(a_row, b_row)| {
let mut row = a_row.clone();
row[j] = b_row[j];
f(&row)
})
.collect();
let s1_num: f64 = fb
.iter()
.zip(ab.iter())
.zip(fa.iter())
.map(|((&yb, &yab), &ya)| yb * (yab - ya))
.sum::<f64>()
/ n as f64;
s1[j] = s1_num / total_var;
let st_num: f64 = fa
.iter()
.zip(ab.iter())
.map(|(&ya, &yab)| (ya - yab).powi(2))
.sum::<f64>()
/ (2 * n) as f64;
st[j] = st_num / total_var;
}
(s1, st)
}
pub fn linear_sobol_indices(coefficients: &[f64], std_devs: &[f64]) -> Vec<f64> {
let k = coefficients.len().min(std_devs.len());
let total_var: f64 = (0..k)
.map(|i| (coefficients[i] * std_devs[i]).powi(2))
.sum();
if total_var < f64::EPSILON {
return vec![0.0; k];
}
(0..k)
.map(|i| (coefficients[i] * std_devs[i]).powi(2) / total_var)
.collect()
}
}
pub struct PolynomialChaosExpansion {
pub coefficients: Vec<f64>,
pub max_order: usize,
}
impl PolynomialChaosExpansion {
pub fn new(coefficients: Vec<f64>) -> Self {
let max_order = coefficients.len().saturating_sub(1);
Self {
coefficients,
max_order,
}
}
pub fn evaluate(&self, xi: f64) -> f64 {
polynomial_chaos_expansion(&self.coefficients, xi, self.max_order)
}
pub fn mean(&self) -> f64 {
pce_mean(&self.coefficients)
}
pub fn variance(&self) -> f64 {
pce_variance(&self.coefficients)
}
pub fn std_dev(&self) -> f64 {
self.variance().sqrt()
}
pub fn cov(&self) -> f64 {
let mu = self.mean().abs();
if mu < f64::EPSILON {
0.0
} else {
self.std_dev() / mu
}
}
pub fn from_quadrature<F>(f: F, max_order: usize, quad_pts: usize) -> Self
where
F: Fn(f64) -> f64,
{
let (nodes, weights) = gauss_hermite_quadrature(quad_pts);
if nodes.is_empty() {
return Self::new(vec![0.0; max_order + 1]);
}
let sqrt2 = 2.0_f64.sqrt();
let mut coeffs = vec![0.0f64; max_order + 1];
let factorial: Vec<f64> = {
let mut v = vec![1.0f64; max_order + 2];
for k in 1..=max_order + 1 {
v[k] = v[k - 1] * k as f64;
}
v
};
for order in 0..=max_order {
let mut sum = 0.0;
for (xi_phys, &w) in nodes.iter().zip(weights.iter()) {
let xi_prob = xi_phys / sqrt2;
let he = hermite_polynomial(order, xi_prob);
sum += w * f(xi_prob) * he;
}
let norm = factorial[order] * PI.sqrt() / 2.0_f64.powi(order as i32);
coeffs[order] = sum / norm;
}
Self {
coefficients: coeffs,
max_order,
}
}
pub fn sobol_indices(&self) -> Vec<f64> {
let var = self.variance();
if var < f64::EPSILON {
return vec![0.0; self.max_order];
}
self.coefficients[1..].iter().map(|c| c * c / var).collect()
}
pub fn total_sobol_index(&self) -> f64 {
self.sobol_indices().iter().sum()
}
}
pub struct StochasticResponse {
pub samples: Vec<f64>,
}
impl StochasticResponse {
pub fn new(samples: Vec<f64>) -> Self {
Self { samples }
}
pub fn mean(&self) -> f64 {
if self.samples.is_empty() {
return 0.0;
}
self.samples.iter().sum::<f64>() / self.samples.len() as f64
}
pub fn variance(&self) -> f64 {
let n = self.samples.len();
if n == 0 {
return 0.0;
}
let mu = self.mean();
self.samples.iter().map(|x| (x - mu).powi(2)).sum::<f64>() / n as f64
}
pub fn percentile(&self, p: f64) -> f64 {
let n = self.samples.len();
if n == 0 {
return 0.0;
}
let mut sorted = self.samples.clone();
sorted.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
let idx = ((p / 100.0) * (n - 1) as f64).round() as usize;
sorted[idx.min(n - 1)]
}
pub fn cdf(&self, x: f64) -> f64 {
if self.samples.is_empty() {
return 0.0;
}
let count = self.samples.iter().filter(|&&s| s <= x).count();
count as f64 / self.samples.len() as f64
}
}