use crate::symbolic::symbolic_engine::Expr;
use crate::symbolic::symbolic_functions::Jacobian;
use log::info;
use nalgebra::{DMatrix, DVector, Matrix};
use std::fmt::Display;
pub struct NRE {
pub eq_system: Vec<Expr>, pub initial_guess: DVector<f64>, pub values: Vec<String>,
pub arg: String,
pub tolerance: f64, pub max_iterations: usize, pub max_error: f64, pub result: Option<DVector<f64>>, pub jacobian: Option<Vec<Vec<Expr>>>,
pub fun: Box<dyn Fn(f64, &DVector<f64>) -> DVector<f64>>,
pub jac: Option<Box<dyn FnMut(f64, &DVector<f64>) -> DMatrix<f64>>>,
pub t: f64,
pub y: DVector<f64>,
pub dt: f64,
n: usize,
pub global_timestepping: bool,
pub t_bound: Option<f64>,
}
impl Display for NRE {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"Initial guess: {:?}, tolerance: {}, max_iterations: {}, max_error: {}, result: {:?}",
self.initial_guess, self.tolerance, self.max_iterations, self.max_error, self.result
)
}
}
impl NRE {
pub fn new(
eq_system: Vec<Expr>, initial_guess: DVector<f64>, values: Vec<String>,
arg: String,
tolerance: f64, max_iterations: usize,
dt: f64,
global_timestepping: bool,
t_bound: Option<f64>,
) -> NRE {
NRE {
eq_system,
initial_guess: initial_guess.clone(),
values,
arg,
tolerance,
max_iterations,
dt,
global_timestepping,
t_bound,
result: None,
jacobian: None,
fun: Box::new(|_t, y| y.clone()),
jac: None,
t: 0.0,
y: initial_guess.clone(),
max_error: 1e-3,
n: 0,
}
}
pub fn eq_generate(&mut self) {
info!("generating equations and jacobian");
let mut jacobian_instance = Jacobian::new();
jacobian_instance.generate_IVP_ODEsolver(
self.eq_system.clone(),
self.values.clone(),
self.arg.clone(),
);
self.jacobian = Some(jacobian_instance.symbolic_jacobian);
let fun: Box<dyn Fn(f64, &DVector<f64>) -> DVector<f64>> =
jacobian_instance.lambdified_functions_IVP_DVector;
let jac = jacobian_instance.function_jacobian_IVP_DMatrix;
let jac_wrapped: Box<dyn FnMut(f64, &DVector<f64>) -> DMatrix<f64>> =
Box::new(move |t: f64, y: &DVector<f64>| -> DMatrix<f64> { jac(t, &y) });
self.fun = fun;
self.jac = Some(jac_wrapped);
self.n = self.eq_system.len();
assert_eq!(&self.eq_system.len(), &self.n);
}
pub fn set_new_step(&mut self, t: f64, y: DVector<f64>, initial_guess: DVector<f64>) {
self.t = t;
self.y = y;
self.initial_guess = initial_guess;
}
pub fn set_t(&mut self, t: f64) {
self.t = t;
}
pub fn set_initial_guess(&mut self, initial_guess: DVector<f64>) {
self.initial_guess = initial_guess;
}
pub fn iteration(&mut self) -> DVector<f64> {
assert_eq!(&self.y.is_empty(), &false, "y is empty");
let t = self.t;
let y = &self.y;
let f = (self.fun)(t, &y);
let new_j = &self.jac.as_mut().unwrap()(t, &y);
let dt = if self.global_timestepping == true {
self.dt
} else {
let t_bound = self
.t_bound
.expect("if global_timestepping = false, t_bound must be set");
let JF = new_j * &f;
let eps = self.tolerance; let norm_JF = JF.amax();
let dt_ = if norm_JF > 0.0 {
(2.0 * eps / norm_JF).sqrt()
} else {
t_bound - self.t
};
let dt = dt_.min(t_bound - self.t);
self.dt = dt; dt
};
let y_k_minus_1 = &self.initial_guess;
let new_G = y - y_k_minus_1 - dt * f;
let I = DMatrix::identity(self.n, self.n);
let J = I - dt * new_j;
let lu = J.lu();
let neg_f = -1.0 * new_G;
let delta_y = lu.solve(&neg_f).expect("The matrix should be invertible");
let new_y: DVector<f64> = y + delta_y;
new_y
}
pub fn solve(&mut self) -> Option<DVector<f64>> {
let mut y: DVector<f64> = self.initial_guess.clone();
self.y = y.clone();
let mut i = 0;
while i < self.max_iterations {
let new_y = self.iteration();
let dy = new_y.clone() - y.clone();
let error = Matrix::norm(&dy);
if error < self.tolerance {
self.result = Some(new_y.clone());
self.max_error = error;
return Some(new_y);
} else {
y = new_y.clone();
self.y = new_y;
i += 1;
}
}
None
}
pub fn get_result(&self) -> Option<DVector<f64>> {
self.result.clone()
}
}
#[cfg(test)]
mod tests {
use super::*;
use nalgebra::DVector;
#[test]
fn test_newton_raphson_solver_for_Euler() {
let eq1 = Expr::parse_expression("z+y-10.0*x");
let eq2 = Expr::parse_expression("z*y-4.0*x");
let eq_system = vec![eq1, eq2];
info!("eq_system = {:?}", eq_system);
let initial_guess = DVector::from_vec(vec![1.0, 1.0]);
let values = vec!["z".to_string(), "y".to_string()];
let arg = "x".to_string();
let tolerance = 1e-3;
let max_iterations = 50;
let h = 1e-5;
assert_eq!(&eq_system.len(), &2);
let mut nr = NRE::new(
eq_system,
initial_guess,
values,
arg,
tolerance,
max_iterations,
h,
true,
None,
);
nr.eq_generate();
assert_eq!(nr.eq_system.len(), 2);
nr.set_t(1.0);
let solution = nr.solve().unwrap();
assert_eq!(solution.len(), 2);
}
#[test]
fn test_newton_raphson_solver_for_Euler_2() {
let eq1 = Expr::parse_expression("z+y-10.0*x");
let eq2 = Expr::parse_expression("z*y-4.0*x");
let eq_system = vec![eq1, eq2];
info!("eq_system = {:?}", eq_system);
let initial_guess = DVector::from_vec(vec![1.0, 1.0]);
let values = vec!["z".to_string(), "y".to_string()];
let arg = "x".to_string();
let tolerance = 1e-3;
let max_iterations = 50;
let h = 1e-5;
assert_eq!(&eq_system.len(), &2);
let mut nr = NRE::new(
eq_system,
initial_guess,
values,
arg,
tolerance,
max_iterations,
h,
false,
Some(1.0),
);
nr.eq_generate();
assert_eq!(nr.eq_system.len(), 2);
nr.set_t(1.0);
let solution = nr.solve().unwrap();
assert_eq!(solution.len(), 2);
}
}