use std::collections::HashMap;
use serde::Deserialize;
use serde::Serialize;
use crate::numerical::calculus::gradient;
use crate::numerical::elementary::eval_expr;
use crate::numerical::matrix::Matrix;
use crate::symbolic::core::Expr;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum LinearSolution {
Unique(Vec<f64>),
Parametric {
particular: Vec<f64>,
null_space_basis: Matrix<f64>,
},
NoSolution,
}
pub fn solve_linear_system(
a: &Matrix<f64>,
b: &[f64],
) -> Result<LinearSolution, String> {
let (rows, cols) = (a.rows(), a.cols());
if rows != b.len() {
return Err("Matrix and \
vector dimensions \
are incompatible.\
"
.to_string());
}
let mut augmented_data = vec![0.0; rows * (cols + 1)];
for i in 0..rows {
for j in 0..cols {
augmented_data[i * (cols + 1) + j] = *a.get(i, j);
}
augmented_data[i * (cols + 1) + cols] = b[i];
}
let mut augmented = Matrix::new(rows, cols + 1, augmented_data);
let rank = augmented.rref()?;
for i in 0..rank {
let mut pivot_col = 0;
while pivot_col < cols + 1 && augmented.get(i, pivot_col).abs() < 1e-9 {
pivot_col += 1;
}
if pivot_col == cols {
return Ok(LinearSolution::NoSolution);
}
}
if rank < cols {
let mut particular = vec![0.0; cols];
#[warn(clippy::collection_is_never_read)]
let mut _pivot_cols = Vec::new();
let mut lead = 0;
for r in 0..rank {
let mut i = lead;
while i < cols && augmented.get(r, i).abs() < 1e-9 {
i += 1;
}
if i < cols {
_pivot_cols.push(i);
particular[i] = *augmented.get(r, cols);
lead = i + 1;
}
}
let null_space = a.null_space()?;
Ok(LinearSolution::Parametric {
particular,
null_space_basis: null_space,
})
} else {
let mut solution = vec![0.0; cols];
for (i, var) in solution.iter_mut().enumerate().take(rank) {
*var = *augmented.get(i, cols);
}
Ok(LinearSolution::Unique(solution))
}
}
pub fn solve_nonlinear_system(
funcs: &[Expr],
vars: &[&str],
start_point: &[f64],
tolerance: f64,
max_iter: usize,
) -> Result<Vec<f64>, String> {
let mut x_n = start_point.to_vec();
for _ in 0..max_iter {
let mut f_at_x = Vec::new();
for func in funcs {
let mut vars_map = HashMap::new();
for (i, &var) in vars.iter().enumerate() {
vars_map.insert(var.to_string(), x_n[i]);
}
f_at_x.push(eval_expr(func, &vars_map)?);
}
let mut jacobian_rows = Vec::new();
for func in funcs {
jacobian_rows.push(gradient(func, vars, &x_n)?);
}
let jacobian = Matrix::new(funcs.len(), vars.len(), jacobian_rows.concat());
let neg_f: Vec<f64> = f_at_x.iter().map(|v| -v).collect();
let delta_x = match solve_linear_system(&jacobian, &neg_f)? {
| LinearSolution::Unique(sol) => sol,
| _ => return Err("Jacobian is singular; Newton's method failed.".to_string()),
};
for i in 0..x_n.len() {
x_n[i] += delta_x[i];
}
let norm_delta = delta_x.iter().map(|v| v * v).sum::<f64>().sqrt();
if norm_delta < tolerance {
return Ok(x_n);
}
}
Err("Newton's method did not \
converge."
.to_string())
}