use super::functions::*;
use rand::RngExt;
use std::f64::consts::PI;
type EvalFn = Box<dyn Fn(&[f64]) -> f64 + Send + Sync>;
#[derive(Debug, Clone)]
pub struct RandomField {
pub x_min: f64,
pub x_max: f64,
pub n_pts: usize,
pub n_terms: usize,
pub length_scale: f64,
pub variance: f64,
pub eigenvalues: Vec<f64>,
pub eigenvectors: Vec<f64>,
}
impl RandomField {
pub fn new(
x_min: f64,
x_max: f64,
n_pts: usize,
n_terms: usize,
length_scale: f64,
variance: f64,
) -> Self {
assert!(n_pts >= 2, "need at least 2 points");
let n_terms = n_terms.min(n_pts);
let dx = (x_max - x_min) / (n_pts - 1) as f64;
let xs: Vec<f64> = (0..n_pts).map(|i| x_min + i as f64 * dx).collect();
let n = n_pts;
let mut cov = vec![0.0_f64; n * n];
for i in 0..n {
for j in 0..n {
let r = (xs[i] - xs[j]).abs();
cov[i * n + j] = cov_gaussian(r, variance, length_scale) * dx;
}
}
let mut eigenvalues = vec![0.0_f64; n_terms];
let mut eigenvectors = vec![0.0_f64; n_terms * n];
let mut deflated = cov.clone();
for k in 0..n_terms {
let mut v: Vec<f64> = (0..n).map(|i| ((i + k + 1) as f64).sin()).collect();
Self::normalise(&mut v);
let mut lambda = 0.0_f64;
for _ in 0..200 {
let w = mat_vec_mul(&deflated, &v, n);
let new_lambda: f64 = v.iter().zip(w.iter()).map(|(a, b)| a * b).sum();
let mut w2 = w;
Self::normalise(&mut w2);
let diff: f64 = w2
.iter()
.zip(v.iter())
.map(|(a, b)| (a - b).powi(2))
.sum::<f64>()
.sqrt();
v = w2;
lambda = new_lambda;
if diff < 1e-10 {
break;
}
}
eigenvalues[k] = lambda.max(0.0);
for i in 0..n {
eigenvectors[k * n + i] = v[i];
}
for i in 0..n {
for j in 0..n {
deflated[i * n + j] -= lambda * v[i] * v[j];
}
}
}
Self {
x_min,
x_max,
n_pts,
n_terms,
length_scale,
variance,
eigenvalues,
eigenvectors,
}
}
pub fn sample(&self, xi: &[f64]) -> Vec<f64> {
let n = self.n_pts;
let mut field = vec![0.0_f64; n];
for (k, &xi_k) in xi.iter().enumerate().take(self.n_terms.min(xi.len())) {
let scale = self.eigenvalues[k].sqrt() * xi_k;
for (i, fi) in field.iter_mut().enumerate().take(n) {
*fi += scale * self.eigenvectors[k * n + i];
}
}
field
}
pub fn energy_fraction(&self) -> f64 {
let total = self.eigenvalues.iter().sum::<f64>();
if total < 1e-300 {
return 1.0;
}
self.eigenvalues.iter().sum::<f64>() / total
}
fn normalise(v: &mut [f64]) {
let norm: f64 = v.iter().map(|x| x * x).sum::<f64>().sqrt();
if norm > 1e-300 {
for x in v.iter_mut() {
*x /= norm;
}
}
}
pub fn covariance(&self, r: f64) -> f64 {
cov_gaussian(r, self.variance, self.length_scale)
}
pub fn spectral_density(&self, omega: f64) -> f64 {
spectral_density_gaussian(omega, self.variance, self.length_scale)
}
pub fn mean_abs_sample(&self, xi: &[f64]) -> f64 {
let s = self.sample(xi);
if s.is_empty() {
return 0.0;
}
s.iter().map(|v| v.abs()).sum::<f64>() / s.len() as f64
}
}
pub struct FormAnalysis;
impl FormAnalysis {
pub fn solve(g: &LimitState) -> FormResult {
let n = g.n_vars();
let mut u = vec![0.0_f64; n];
let h = 1e-6_f64;
let mut iters = 0usize;
for _iter in 0..200 {
iters += 1;
let x = g.u_to_x(&u);
let gval = g.evaluate(&x);
let mut grad = vec![0.0_f64; n];
for i in 0..n {
let mut xp = x.clone();
let mut xm = x.clone();
xp[i] += h * g.std_dev[i];
xm[i] -= h * g.std_dev[i];
grad[i] =
(g.evaluate(&xp) - g.evaluate(&xm)) / (2.0 * h * g.std_dev[i]) * g.std_dev[i];
}
let grad_norm_sq: f64 = grad.iter().map(|v| v * v).sum();
if grad_norm_sq < 1e-30 {
break;
}
let dot: f64 = grad.iter().zip(u.iter()).map(|(gi, ui)| gi * ui).sum();
let lambda = (dot - gval) / grad_norm_sq;
let u_new: Vec<f64> = grad.iter().map(|gi| lambda * gi).collect();
let diff: f64 = u_new
.iter()
.zip(u.iter())
.map(|(a, b)| (a - b).powi(2))
.sum::<f64>()
.sqrt();
u = u_new;
if diff < 1e-9 {
break;
}
}
let beta = u.iter().map(|v| v * v).sum::<f64>().sqrt();
let pf = phi(-beta);
let alpha: Vec<f64> = if beta > 1e-12 {
u.iter().map(|ui| -ui / beta).collect()
} else {
vec![0.0; n]
};
FormResult {
beta,
pf,
design_point: u,
alpha,
iterations: iters,
}
}
pub fn hasofer_lind_beta(u: &[f64]) -> f64 {
u.iter().map(|v| v * v).sum::<f64>().sqrt()
}
pub fn importance_vector(u: &[f64]) -> Vec<f64> {
let beta = Self::hasofer_lind_beta(u);
if beta < 1e-12 {
return vec![0.0; u.len()];
}
u.iter().map(|ui| -ui / beta).collect()
}
pub fn beta_sensitivity_to_mean(alpha: &[f64], std_dev: &[f64]) -> Vec<f64> {
alpha
.iter()
.zip(std_dev.iter())
.map(|(&ai, &si)| -ai / si.max(1e-300))
.collect()
}
pub fn is_design_point(u: &[f64], g: &LimitState) -> bool {
let x = g.u_to_x(u);
g.evaluate(&x).abs() < 1e-6
}
}
pub struct SystemReliability;
impl SystemReliability {
pub fn series_bounds(pf_components: &[f64]) -> (f64, f64) {
if pf_components.is_empty() {
return (0.0, 0.0);
}
let lower = pf_components.iter().cloned().fold(0.0_f64, f64::max);
let upper = (pf_components.iter().sum::<f64>()).min(1.0);
(lower, upper)
}
pub fn parallel_bounds(pf_components: &[f64]) -> (f64, f64) {
if pf_components.is_empty() {
return (0.0, 0.0);
}
let n = pf_components.len() as f64;
let upper = pf_components.iter().cloned().fold(f64::INFINITY, f64::min);
let lower = (pf_components.iter().sum::<f64>() - (n - 1.0)).max(0.0);
(lower, upper)
}
pub fn series_beta(pf_components: &[f64]) -> f64 {
let (_lower, upper) = Self::series_bounds(pf_components);
phi_inv(1.0 - upper)
}
pub fn parallel_beta(pf_components: &[f64]) -> f64 {
let (_lower, upper) = Self::parallel_bounds(pf_components);
phi_inv(1.0 - upper)
}
pub fn ditlevsen_lower(betas: &[f64], rho: f64) -> f64 {
if betas.is_empty() {
return 0.0;
}
let pfs: Vec<f64> = betas.iter().map(|&b| phi(-b)).collect();
let mut total = pfs[0];
for i in 1..betas.len() {
let mut inter_sum = 0.0_f64;
for j in 0..i {
inter_sum += pfs[i] * pfs[j] * (1.0 + rho).clamp(0.0, 1.0);
}
let term = (pfs[i] - inter_sum).max(0.0);
total += term;
}
total.clamp(0.0, 1.0)
}
pub fn series_mean_rate(failure_rates: &[f64]) -> f64 {
failure_rates.iter().sum()
}
pub fn series_mttf(failure_rates: &[f64]) -> f64 {
let total_rate = Self::series_mean_rate(failure_rates);
if total_rate < 1e-300 {
return f64::INFINITY;
}
1.0 / total_rate
}
}
#[derive(Debug, Clone)]
pub struct PartialCoefficients {
pub gamma_r: f64,
pub gamma_g: f64,
pub gamma_q: f64,
}
impl PartialCoefficients {
pub fn eurocode_default() -> Self {
Self {
gamma_r: 1.0 / 0.8,
gamma_g: 1.35,
gamma_q: 1.5,
}
}
pub fn new(gamma_r: f64, gamma_g: f64, gamma_q: f64) -> Self {
Self {
gamma_r,
gamma_g,
gamma_q,
}
}
}
pub struct LimitState {
pub mean: Vec<f64>,
pub std_dev: Vec<f64>,
pub(super) eval: EvalFn,
}
impl LimitState {
pub fn new(
mean: Vec<f64>,
std_dev: Vec<f64>,
eval: impl Fn(&[f64]) -> f64 + Send + Sync + 'static,
) -> Self {
Self {
mean,
std_dev,
eval: Box::new(eval),
}
}
pub fn evaluate(&self, x: &[f64]) -> f64 {
(self.eval)(x)
}
pub fn u_to_x(&self, u: &[f64]) -> Vec<f64> {
u.iter()
.zip(self.mean.iter())
.zip(self.std_dev.iter())
.map(|((ui, mi), si)| mi + si * ui)
.collect()
}
pub fn x_to_u(&self, x: &[f64]) -> Vec<f64> {
x.iter()
.zip(self.mean.iter())
.zip(self.std_dev.iter())
.map(|((xi, mi), si)| (xi - mi) / si)
.collect()
}
pub fn n_vars(&self) -> usize {
self.mean.len()
}
pub fn monte_carlo_pf(&self, n_samples: usize) -> f64 {
use rand::RngExt;
let mut rng = rand::rng();
let mut failures = 0usize;
for _ in 0..n_samples {
let x: Vec<f64> = self
.mean
.iter()
.zip(self.std_dev.iter())
.map(|(&mi, &si)| {
let u1: f64 = rng.random_range(f64::EPSILON..1.0_f64);
let u2: f64 = rng.random_range(0.0_f64..1.0_f64);
mi + si * box_muller(u1, u2)
})
.collect();
if self.evaluate(&x) <= 0.0 {
failures += 1;
}
}
failures as f64 / n_samples as f64
}
}
#[derive(Debug, Clone)]
pub struct FormResult {
pub beta: f64,
pub pf: f64,
pub design_point: Vec<f64>,
pub alpha: Vec<f64>,
pub iterations: usize,
}
pub struct SormAnalysis;
impl SormAnalysis {
pub fn solve(g: &LimitState, form: &FormResult) -> SormResult {
let n = g.n_vars();
let beta = form.beta;
let u_star = &form.design_point;
let h = 1e-5_f64;
let x_star = g.u_to_x(u_star);
let gval = g.evaluate(&x_star);
let mut hess = vec![0.0_f64; n * n];
for i in 0..n {
for j in 0..n {
let (pp, pm, mp, mm) = {
let mut xpp = x_star.clone();
let mut xpm = x_star.clone();
let mut xmp = x_star.clone();
let mut xmm = x_star.clone();
xpp[i] += h * g.std_dev[i];
xpp[j] += h * g.std_dev[j];
xpm[i] += h * g.std_dev[i];
xpm[j] -= h * g.std_dev[j];
xmp[i] -= h * g.std_dev[i];
xmp[j] += h * g.std_dev[j];
xmm[i] -= h * g.std_dev[i];
xmm[j] -= h * g.std_dev[j];
(
g.evaluate(&xpp),
g.evaluate(&xpm),
g.evaluate(&xmp),
g.evaluate(&xmm),
)
};
hess[i * n + j] = (pp - pm - mp + mm) / (4.0 * h * h * g.std_dev[i] * g.std_dev[j]);
}
}
let _ = gval;
let mut grad = vec![0.0_f64; n];
for i in 0..n {
let mut xp = x_star.clone();
let mut xm = x_star.clone();
xp[i] += h * g.std_dev[i];
xm[i] -= h * g.std_dev[i];
grad[i] = (g.evaluate(&xp) - g.evaluate(&xm)) / (2.0 * h * g.std_dev[i]) * g.std_dev[i];
}
let grad_norm = grad.iter().map(|v| v * v).sum::<f64>().sqrt().max(1e-15);
let curvatures: Vec<f64> = (0..n).map(|i| hess[i * n + i] / grad_norm).collect();
let mut correction_factor = 1.0_f64;
for &ki in &curvatures {
let denom = 1.0 + beta * ki;
if denom > 0.0 {
correction_factor /= denom.sqrt();
}
}
let pf_form = phi(-beta);
let pf_breitung = pf_form * correction_factor;
SormResult {
beta,
curvatures,
pf_breitung: pf_breitung.clamp(0.0, 1.0),
correction_factor,
}
}
pub fn hohenbichler_correction(beta: f64, curvatures: &[f64]) -> f64 {
let mu = phi_pdf(beta) / phi(-beta).max(1e-300);
let mut prod = 1.0_f64;
for &ki in curvatures {
let denom = 1.0 + mu * ki;
if denom > 0.0 {
prod /= denom.sqrt();
}
}
prod
}
pub fn tvedt_pf(beta: f64, curvatures: &[f64]) -> f64 {
let pf_form = phi(-beta);
let mut prod = 1.0_f64;
for &ki in curvatures {
let d = 1.0 + beta * ki;
if d > 0.0 {
prod /= d.sqrt();
}
}
(pf_form * prod).clamp(0.0, 1.0)
}
}
pub struct MonteCarloFEM {
pub n_samples: usize,
pub mean_k: f64,
pub std_k: f64,
}
impl MonteCarloFEM {
pub fn new(n_samples: usize, mean_k: f64, std_k: f64) -> Self {
Self {
n_samples,
mean_k,
std_k,
}
}
pub fn run<F>(&self, response_fn: F, threshold: f64) -> McFemStats
where
F: Fn(f64) -> f64,
{
let mut rng = rand::rng();
let mut samples = Vec::with_capacity(self.n_samples);
for _ in 0..self.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.mean_k + self.std_k * z).max(self.mean_k * 1e-9);
samples.push(response_fn(k));
}
Self::compute_stats(&samples, threshold, self.n_samples)
}
pub fn run_random_field(&self, rf: &RandomField, threshold: f64) -> McFemStats {
let mut rng = rand::rng();
let n_terms = rf.n_terms;
let mut samples = Vec::with_capacity(self.n_samples);
for _ in 0..self.n_samples {
let xi: Vec<f64> = (0..n_terms)
.map(|_| {
let u1: f64 = rng.random_range(f64::EPSILON..1.0_f64);
let u2: f64 = rng.random_range(0.0_f64..1.0_f64);
box_muller(u1, u2)
})
.collect();
let field = rf.sample(&xi);
let max_val = field.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
samples.push(max_val);
}
Self::compute_stats(&samples, threshold, self.n_samples)
}
pub fn run_with_load<F>(
&self,
load_fn: F,
mean_load: f64,
std_load: f64,
threshold: f64,
) -> McFemStats
where
F: Fn(f64, f64) -> f64,
{
let mut rng = rand::rng();
let mut samples = Vec::with_capacity(self.n_samples);
for _ in 0..self.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 u3: f64 = rng.random_range(f64::EPSILON..1.0_f64);
let u4: f64 = rng.random_range(0.0_f64..1.0_f64);
let zk = box_muller(u1, u2);
let zl = box_muller(u3, u4);
let k = (self.mean_k + self.std_k * zk).max(self.mean_k * 1e-9);
let load = mean_load + std_load * zl;
samples.push(load_fn(k, load));
}
Self::compute_stats(&samples, threshold, self.n_samples)
}
fn compute_stats(samples: &[f64], threshold: f64, n_samples: usize) -> McFemStats {
let n = samples.len() as f64;
let mean = if n > 0.0 {
samples.iter().sum::<f64>() / n
} else {
0.0
};
let var = if n > 0.0 {
samples.iter().map(|v| (v - mean).powi(2)).sum::<f64>() / n
} else {
0.0
};
let std_dev = var.sqrt();
let cov = if mean.abs() > 1e-300 {
std_dev / mean.abs()
} else {
0.0
};
let min = samples.iter().cloned().fold(f64::INFINITY, f64::min);
let max = samples.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
let failures = samples.iter().filter(|&&v| v > threshold).count();
let pf = failures as f64 / n_samples as f64;
McFemStats {
n_samples,
mean,
std_dev,
cov,
min,
max,
pf,
}
}
}
#[derive(Debug, Clone)]
pub struct SormResult {
pub beta: f64,
pub curvatures: Vec<f64>,
pub pf_breitung: f64,
pub correction_factor: f64,
}
#[derive(Debug, Clone)]
pub struct McFemStats {
pub n_samples: usize,
pub mean: f64,
pub std_dev: f64,
pub cov: f64,
pub min: f64,
pub max: f64,
pub pf: f64,
}
impl McFemStats {
pub fn pf_confidence_halfwidth(&self) -> f64 {
let n = self.n_samples as f64;
if n < 1.0 {
return 0.0;
}
1.96 * (self.pf * (1.0 - self.pf) / n).sqrt()
}
pub fn pf_cov(&self) -> f64 {
if self.pf < 1e-300 {
return 0.0;
}
((1.0 - self.pf) / (self.pf * self.n_samples as f64)).sqrt()
}
}
pub struct ImportanceSampling {
pub n_samples: usize,
pub proposal_std: f64,
}
impl ImportanceSampling {
pub fn new(n_samples: usize, proposal_std: f64) -> Self {
Self {
n_samples,
proposal_std,
}
}
pub fn estimate(&self, g: &LimitState, design_point: &[f64]) -> f64 {
let mut rng = rand::rng();
let n = g.n_vars();
let mut pf_sum = 0.0_f64;
for _ in 0..self.n_samples {
let u: Vec<f64> = (0..n)
.map(|i| {
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);
design_point[i] + self.proposal_std * z
})
.collect();
let x = g.u_to_x(&u);
if g.evaluate(&x) <= 0.0 {
let ln_p: f64 = -0.5 * u.iter().map(|v| v * v).sum::<f64>();
let ln_h: f64 = -0.5
* u.iter()
.zip(design_point.iter())
.map(|(ui, di)| (ui - di).powi(2))
.sum::<f64>()
/ (self.proposal_std * self.proposal_std)
- n as f64 * (2.0 * PI * self.proposal_std * self.proposal_std).ln() / 2.0
+ n as f64 * (2.0 * PI).ln() / 2.0;
let weight = (ln_p - ln_h).exp();
pf_sum += weight;
}
}
(pf_sum / self.n_samples as f64).clamp(0.0, 1.0)
}
pub fn estimator_cov(pf_estimate: f64, n_samples: usize) -> f64 {
if pf_estimate < 1e-300 {
return 0.0;
}
((1.0 - pf_estimate) / (pf_estimate * n_samples as f64)).sqrt()
}
}
#[derive(Debug, Clone)]
pub struct ResponseSurfaceMethod {
pub n_vars: usize,
pub coefficients: Vec<f64>,
pub r_squared: f64,
}
impl ResponseSurfaceMethod {
pub fn fit(g: &LimitState, h: f64) -> Self {
let n = g.n_vars();
let mean = &g.mean;
let n_pts = 2 * n + 1;
let mut x_pts: Vec<Vec<f64>> = Vec::with_capacity(n_pts);
let mut y_pts: Vec<f64> = Vec::with_capacity(n_pts);
x_pts.push(mean.clone());
y_pts.push(g.evaluate(mean));
for i in 0..n {
let mut xp = mean.clone();
let mut xm = mean.clone();
xp[i] += h * g.std_dev[i];
xm[i] -= h * g.std_dev[i];
x_pts.push(xp);
y_pts.push(g.evaluate(&x_pts[x_pts.len() - 1]));
x_pts.push(xm);
y_pts.push(g.evaluate(&x_pts[x_pts.len() - 1]));
}
let g0 = y_pts[0];
let mut a_coeffs = vec![0.0_f64; n];
let mut b_coeffs = vec![0.0_f64; n];
for i in 0..n {
let gp = y_pts[1 + 2 * i];
let gm = y_pts[2 + 2 * i];
let hi = h * g.std_dev[i];
a_coeffs[i] = (gp - gm) / (2.0 * hi);
b_coeffs[i] = (gp + gm - 2.0 * g0) / (hi * hi);
}
let mut coefficients = vec![g0];
coefficients.extend_from_slice(&a_coeffs);
coefficients.extend_from_slice(&b_coeffs);
let y_pred: Vec<f64> = x_pts
.iter()
.map(|x| {
let mut val = g0;
for i in 0..n {
let dx = x[i] - mean[i];
val += a_coeffs[i] * dx + b_coeffs[i] * dx * dx;
}
val
})
.collect();
let y_mean = y_pts.iter().sum::<f64>() / y_pts.len() as f64;
let ss_res: f64 = y_pts
.iter()
.zip(y_pred.iter())
.map(|(y, yp)| (y - yp).powi(2))
.sum();
let ss_tot: f64 = y_pts.iter().map(|y| (y - y_mean).powi(2)).sum();
let r_squared = if ss_tot > 1e-300 {
1.0 - ss_res / ss_tot
} else {
1.0
};
Self {
n_vars: n,
coefficients,
r_squared,
}
}
pub fn evaluate(&self, x: &[f64], mean: &[f64]) -> f64 {
let n = self.n_vars;
let mut val = self.coefficients[0];
for i in 0..n {
let dx = x[i] - mean[i];
val += self.coefficients[1 + i] * dx;
val += self.coefficients[1 + n + i] * dx * dx;
}
val
}
pub fn form_beta(&self, g_original: &LimitState) -> f64 {
let mean = g_original.mean.clone();
let coeffs = self.coefficients.clone();
let n = self.n_vars;
let mean_clone = mean.clone();
let approx = LimitState::new(mean.clone(), g_original.std_dev.clone(), move |x| {
let mut val = coeffs[0];
for i in 0..n {
let dx = x[i] - mean_clone[i];
val += coeffs[1 + i] * dx;
val += coeffs[1 + n + i] * dx * dx;
}
val
});
FormAnalysis::solve(&approx).beta
}
}
#[derive(Debug, Clone)]
pub struct SensitivityIndex {
pub first_order: Vec<f64>,
pub total_effect: Vec<f64>,
pub total_variance: f64,
}
impl SensitivityIndex {
pub fn compute(g: &LimitState, n: usize) -> Self {
let mut rng = rand::rng();
let k = g.n_vars();
let mut mat_a: Vec<Vec<f64>> = Vec::with_capacity(n);
let mut mat_b: Vec<Vec<f64>> = Vec::with_capacity(n);
for _ in 0..n {
let row_a: Vec<f64> = (0..k)
.map(|i| {
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);
g.mean[i] + g.std_dev[i] * z
})
.collect();
let row_b: Vec<f64> = (0..k)
.map(|i| {
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);
g.mean[i] + g.std_dev[i] * z
})
.collect();
mat_a.push(row_a);
mat_b.push(row_b);
}
let ya: Vec<f64> = mat_a.iter().map(|x| g.evaluate(x)).collect();
let yb: Vec<f64> = mat_b.iter().map(|x| g.evaluate(x)).collect();
let f0 = ya.iter().sum::<f64>() / n as f64;
let var_y = ya.iter().map(|v| (v - f0).powi(2)).sum::<f64>() / n as f64;
let mut first_order = vec![0.0_f64; k];
let mut total_effect = vec![0.0_f64; k];
if var_y > 1e-30 {
for j in 0..k {
let ab_j: Vec<Vec<f64>> = (0..n)
.map(|r| {
let mut row = mat_a[r].clone();
row[j] = mat_b[r][j];
row
})
.collect();
let y_ab_j: Vec<f64> = ab_j.iter().map(|x| g.evaluate(x)).collect();
let si: f64 = ya
.iter()
.zip(y_ab_j.iter())
.zip(yb.iter())
.map(|((a, ab), b)| b * (ab - a))
.sum::<f64>()
/ (n as f64 * var_y);
let sti: f64 = ya
.iter()
.zip(y_ab_j.iter())
.map(|(a, ab)| (a - ab).powi(2))
.sum::<f64>()
/ (2.0 * n as f64 * var_y);
first_order[j] = si.clamp(0.0, 1.0);
total_effect[j] = sti.clamp(0.0, 1.0);
}
}
Self {
first_order,
total_effect,
total_variance: var_y,
}
}
pub fn dominant_variable(&self) -> usize {
self.first_order
.iter()
.enumerate()
.max_by(|a, b| a.1.partial_cmp(b.1).unwrap_or(std::cmp::Ordering::Equal))
.map(|(i, _)| i)
.unwrap_or(0)
}
pub fn first_order_sum(&self) -> f64 {
self.first_order.iter().sum()
}
}
#[derive(Debug, Clone)]
pub struct StructuralReliability {
pub pf: f64,
pub beta: f64,
pub safety_factor: f64,
pub partial_coefficients: PartialCoefficients,
}
impl StructuralReliability {
pub fn compute(mu_r: f64, sigma_r: f64, mu_s: f64, sigma_s: f64) -> Self {
let beta_denom = (sigma_r * sigma_r + sigma_s * sigma_s).sqrt();
let beta = if beta_denom > 1e-300 {
(mu_r - mu_s) / beta_denom
} else if mu_r > mu_s {
f64::INFINITY
} else {
f64::NEG_INFINITY
};
let pf = phi(-beta);
let safety_factor = if mu_s.abs() > 1e-300 {
mu_r / mu_s
} else {
f64::INFINITY
};
Self {
pf,
beta,
safety_factor,
partial_coefficients: PartialCoefficients::eurocode_default(),
}
}
pub fn from_form(form: &FormResult, mu_r: f64, mu_s: f64) -> Self {
let safety_factor = if mu_s.abs() > 1e-300 {
mu_r / mu_s
} else {
f64::INFINITY
};
Self {
pf: form.pf,
beta: form.beta,
safety_factor,
partial_coefficients: PartialCoefficients::eurocode_default(),
}
}
pub fn design_resistance(&self, mu_r: f64) -> f64 {
mu_r / self.partial_coefficients.gamma_r
}
pub fn design_load_effect(&self, g_k: f64, q_k: f64) -> f64 {
self.partial_coefficients.gamma_g * g_k + self.partial_coefficients.gamma_q * q_k
}
pub fn target_beta_cc2() -> f64 {
3.8
}
pub fn target_beta_cc3() -> f64 {
4.3
}
pub fn meets_cc2_target(&self) -> bool {
self.beta >= Self::target_beta_cc2()
}
pub fn lognormal_beta(mu_r: f64, sigma_r: f64, mu_s: f64, sigma_s: f64) -> f64 {
let vr = (sigma_r / mu_r.abs()).max(1e-9);
let vs = (sigma_s / mu_s.abs()).max(1e-9);
let denom = (vr * vr + vs * vs).sqrt();
if denom < 1e-300 {
return f64::INFINITY;
}
(mu_r / mu_s.max(1e-300)).abs().ln() / denom
}
}