extern crate nalgebra as na;
extern crate num_traits;
extern crate sprs;
use na::{DMatrix, DVector, Dyn, LU};
use log::info;
use std::f64;
use std::ops::AddAssign;
use crate::numerical::BDF::BDF_utils::{OrderEnum, group_columns};
use crate::numerical::BDF::common::{
NumberOrVec, check_arguments, is_sparse, newton_tol, norm, scale_func, select_initial_step,
validate_first_step, validate_max_step, validate_tol,
};
use std::fmt::Debug;
use std::fmt::Display;
const MAX_ORDER: usize = 5;
const NEWTON_MAXITER: usize = 4;
const MIN_FACTOR: f64 = 0.2;
const MAX_FACTOR: f64 = 10.0;
const SPARSE: f64 = 0.01;
fn cumulative_product_along_columns(matrix: &DMatrix<f64>) -> DMatrix<f64> {
let (rows, cols) = matrix.shape();
let mut result = DMatrix::zeros(rows, cols);
for col in 0..cols {
let mut cumprod = 1.0;
for row in 0..rows {
cumprod *= matrix[(row, col)];
result[(row, col)] = cumprod;
}
}
result
}
fn compute_r(order: usize, factor: f64) -> DMatrix<f64> {
let mut m = DMatrix::zeros(order + 1, order + 1);
for i in 1..(order + 1) {
for j in 1..(order + 1) {
m[(i, j)] = (i as f64 - 1.0 - factor * j as f64) / i as f64;
}
}
m.row_mut(0).fill(1.0);
let result = cumulative_product_along_columns(&m);
result
}
fn change_D(D: &mut DMatrix<f64>, order: usize, factor: f64) {
let r = compute_r(order, factor);
let u = compute_r(order, 1.0);
let ru = r * u;
let temp = ru.transpose() * D.rows(0, order + 1);
D.rows_mut(0, order + 1).copy_from(&temp);
}
fn solve_bdf_system<F>(
fun: F,
t_new: f64,
y_predict: &DVector<f64>,
c: f64,
psi: &DVector<f64>,
lumatrx: LU<f64, Dyn, Dyn>,
solve_lu: &mut Box<dyn FnMut(&LU<f64, Dyn, Dyn>, &DVector<f64>) -> DVector<f64>>, scale: &DVector<f64>, tol: f64,
) -> (bool, usize, DVector<f64>, DVector<f64>)
where
F: Fn(f64, &DVector<f64>) -> DVector<f64>,
{
let mut d = DVector::zeros(y_predict.len()); let mut y = y_predict.clone();
let mut dy_norm_old: Option<f64> = None;
let mut converged = false;
let mut k_: usize = 0;
for k in 0..NEWTON_MAXITER {
let f = fun(t_new, &y);
if !f.iter().all(|&x| x.is_finite()) {
break;
}
let dy = solve_lu(&lumatrx, &(c * &f - psi - &d));
let dy_norm = norm(&(dy.component_div(scale)));
let rate: Option<f64> = if let Some(dy_norm_old) = dy_norm_old {
Some(dy_norm / dy_norm_old)
} else {
None
};
if let Some(rate) = rate {
if rate >= 1.0
|| (rate.powi((NEWTON_MAXITER - k) as i32) / (1.0 - rate)) * dy_norm > tol
{
break;
}
}
y += &dy;
d += &dy;
k_ = k;
if dy_norm == 0.0 {
converged = true;
break;
}
if let Some(rate) = rate {
if rate / (1.0 - rate) * dy_norm < tol {
converged = true;
break;
}
}
dy_norm_old = Some(dy_norm);
}
(converged, k_ + 1, y, d)
}
pub struct BDF {
fun: Box<dyn Fn(f64, &DVector<f64>) -> DVector<f64>>,
pub t: f64,
pub y: DVector<f64>,
t_bound: f64,
max_step: f64,
rtol: NumberOrVec,
atol: NumberOrVec,
vectorized: bool,
n: usize,
pub t_old: Option<f64>,
h_abs: f64,
h_abs_old: Option<f64>,
error_norm_old: Option<f64>,
newton_tol: f64,
jac_factor: Option<DVector<f64>>,
jac: Option<Box<dyn FnMut(f64, &DVector<f64>) -> DMatrix<f64>>>,
J: DMatrix<f64>,
I: DMatrix<f64>,
lu: Box<dyn FnMut(&DMatrix<f64>) -> LU<f64, Dyn, Dyn>>,
solve_lu: Box<dyn FnMut(&LU<f64, Dyn, Dyn>, &DVector<f64>) -> DVector<f64>>,
gamma: DVector<f64>,
alpha: DVector<f64>,
error_const: DVector<f64>,
D: DMatrix<f64>,
order: usize,
n_equal_steps: usize,
LU: Option<LU<f64, Dyn, Dyn>>,
nlu: usize,
nfev: usize,
njev: usize,
pub direction: f64,
}
impl Display for BDF {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self)
}
}
impl Debug for BDF {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("BDF")
.field("t", &self.t)
.field("y", &self.y)
.field("t_bound", &self.t_bound)
.field("max_step", &self.max_step)
.field("rtol", &self.rtol)
.field("atol", &self.atol)
.field("vectorized", &self.vectorized)
.field("n", &self.n)
.field("t_old", &self.t_old)
.field("h_abs", &self.h_abs)
.field("h_abs_old", &self.h_abs_old)
.field("error_norm_old", &self.error_norm_old)
.field("newton_tol", &self.newton_tol)
.field("jac_factor", &self.jac_factor)
.field("J", &self.J)
.field("I", &self.I)
.field("gamma", &self.gamma)
.field("alpha", &self.alpha)
.field("error_const", &self.error_const)
.field("D", &self.D)
.field("order", &self.order)
.field("LU", &self.LU)
.field("nlu", &self.nlu)
.field("nfev", &self.nfev)
.field("njev", &self.njev)
.field("direction", &self.direction)
.finish()
}
}
impl BDF {
pub fn new() -> Self {
BDF {
fun: Box::new(|_t, y| y.clone()),
t: 0.0,
y: DVector::zeros(1),
t_bound: 1.0,
max_step: 1e-3,
rtol: NumberOrVec::Number(1e-3),
atol: NumberOrVec::Number(1e-4),
vectorized: false,
h_abs: 0.0,
h_abs_old: None,
error_norm_old: None,
newton_tol: 0.0,
jac_factor: None,
jac: None,
J: DMatrix::zeros(0, 0),
I: DMatrix::zeros(0, 0),
lu: Box::new(|J: &DMatrix<f64>| LU::new(J.clone())),
solve_lu: Box::new(|_lu: &LU<f64, Dyn, Dyn>, dv: &DVector<f64>| {
dv.clone()
}),
gamma: DVector::zeros(0),
alpha: DVector::zeros(0),
error_const: DVector::zeros(0),
D: DMatrix::zeros(0, 0),
order: 1,
n_equal_steps: 0,
LU: None,
nlu: 0,
direction: 1.0,
nfev: 0,
njev: 0,
n: 0,
t_old: None,
}
}
pub fn set_initial(
&mut self,
fun: Box<dyn Fn(f64, &DVector<f64>) -> DVector<f64>>,
t0: f64,
y0: DVector<f64>,
t_bound: f64,
_max_step: f64,
rtol: NumberOrVec,
atol: NumberOrVec,
jac: Option<Box<dyn Fn(f64, &DVector<f64>) -> DMatrix<f64>>>,
jac_sparsity: Option<DMatrix<f64>>,
vectorized: bool,
first_step: Option<f64>,
) {
self.prelude(fun, t0, y0, t_bound, vectorized);
info!("prelude done");
let max_step = validate_max_step(self.max_step);
self.max_step = max_step.unwrap();
let (rtol, atol) = validate_tol(rtol, atol, self.n).unwrap();
self.rtol = rtol.clone();
self.atol = atol.clone();
info!("tolerance validation: done");
let f = (&self.fun)(self.t, &self.y);
let h_abs = match first_step {
None => select_initial_step(
&self.fun,
self.t,
&self.y,
self.t_bound,
self.max_step,
&f,
self.direction,
1.0,
self.rtol.clone(),
self.atol.clone(),
),
Some(first_step) => {
validate_first_step(first_step, t0, t_bound).expect("first_step must be positive")
}
};
self.h_abs = h_abs;
self.h_abs_old = None;
self.error_norm_old = None;
self.newton_tol = newton_tol(rtol);
self.jac_factor = None;
let kappa = DVector::from_vec(vec![0.0, -0.1850, -1.0 / 9.0, -0.0823, -0.0415, 0.0]);
let gamma = {
let mut g = vec![0.0];
let mut cumsum = 0.0;
for i in 1..=MAX_ORDER {
cumsum += 1.0 / (i as f64);
g.push(cumsum);
}
DVector::from_vec(g)
};
let alpha = (DVector::from(vec![1.0; MAX_ORDER + 1]) - kappa.clone()).component_mul(&gamma);
let error_const = kappa.component_mul(&gamma)
+ DVector::from_iterator(MAX_ORDER + 1, (1..=MAX_ORDER + 1).map(|i| 1.0 / i as f64));
assert_eq!(alpha.len(), MAX_ORDER + 1);
assert_eq!(gamma.len(), MAX_ORDER + 1);
assert_eq!(error_const.len(), MAX_ORDER + 1);
self.alpha = alpha;
self.error_const = error_const;
self.gamma = gamma;
let mut D = DMatrix::zeros(MAX_ORDER + 3, self.y.len());
assert!(
D.nrows() >= MAX_ORDER + 3,
"D matrix must have at least MAX_ORDER + 3 rows"
);
info!("created matrix of size: {:?} x {:?}", D.nrows(), D.ncols());
D.set_row(0, &self.y.transpose());
D.set_row(1, &(f * h_abs * self.direction).transpose());
self.D = D;
self.order = 1;
self.n_equal_steps = 0;
self.LU = None;
self.create_funct();
self.validate_jac(jac, jac_sparsity);
}
fn create_funct(&mut self) {
if is_sparse(&self.J, SPARSE) {
let lu = |A: &DMatrix<f64>| {
A.clone().lu()
};
let solve_lu = |LU: &LU<f64, Dyn, Dyn>, b: &DVector<f64>| -> DVector<f64> {
let linear_solution: DVector<f64> = LU.solve(b).unwrap();
linear_solution
};
#[allow(unused)]
fn solve_lu_f(LU: &LU<f64, Dyn, Dyn>, b: &DVector<f64>) -> DVector<f64> {
let linear_solution = LU.solve(b).unwrap();
linear_solution
}
self.lu = Box::new(lu) as Box<dyn FnMut(&DMatrix<f64>) -> LU<f64, Dyn, Dyn>>;
self.solve_lu = Box::new(solve_lu)
as Box<dyn FnMut(&LU<f64, Dyn, Dyn>, &DVector<f64>) -> DVector<f64>>;
self.I = DMatrix::identity(self.n, self.n);
} else {
let lu = |A: &DMatrix<f64>| {
A.clone().lu()
};
let solve_lu = |LU: &LU<f64, Dyn, Dyn>, b: &DVector<f64>| -> DVector<f64> {
let linear_solution: DVector<f64> = LU.solve(b).unwrap();
linear_solution
};
#[allow(dead_code)]
fn solve_lu_f(LU: &LU<f64, Dyn, Dyn>, b: &DVector<f64>) -> DVector<f64> {
let linear_solution = LU.solve(b).unwrap();
linear_solution
}
self.lu = Box::new(lu) as Box<dyn FnMut(&DMatrix<f64>) -> LU<f64, Dyn, Dyn>>;
self.solve_lu = Box::new(solve_lu)
as Box<dyn FnMut(&LU<f64, Dyn, Dyn>, &DVector<f64>) -> DVector<f64>>;
self.I = DMatrix::identity(self.n, self.n);
info!("functions creation: done");
};
}
fn prelude(
&mut self,
fun: Box<dyn Fn(f64, &DVector<f64>) -> DVector<f64>>,
t0: f64,
y0: DVector<f64>,
t_bound: f64,
vectorized: bool,
) {
let support_complex: bool = false;
self.t_old = None;
self.t = t0;
self.n = y0.len();
let (fun, y) = check_arguments(fun, (&y0).into(), support_complex).unwrap();
self.y = y;
self.t_bound = t_bound;
self.vectorized = vectorized;
self.fun = fun;
self.direction = if t_bound != t0 {
(t_bound - t0).signum()
} else {
1.0
};
self.nfev = 0;
}
fn validate_jac(
&mut self,
jac: Option<Box<dyn Fn(f64, &DVector<f64>) -> DMatrix<f64>>>,
sparsity: Option<DMatrix<f64>>,
) {
let t0 = self.t;
let y0 = self.y.clone();
let _sparsity_ = 0.0;
match jac {
Some(jac) => {
info!("analytical jacobian used");
let J = jac(t0, &y0);
self.njev += 1;
let jac_wrapped: Box<dyn FnMut(f64, &DVector<f64>) -> DMatrix<f64>> =
if is_sparse(&J, SPARSE) {
Box::new(move |t: f64, y: &DVector<f64>| -> DMatrix<f64> {
jac(t, &y)
})
} else {
Box::new(move |t: f64, y: &DVector<f64>| -> DMatrix<f64> {
jac(t, &y)
})
};
self.jac = Some(jac_wrapped);
self.J = J.clone();
}
_ => {
let _new_sparsity: Option<(DMatrix<f64>, Vec<usize>)> =
if let Some(sparsity) = sparsity {
let groups = group_columns(&sparsity, OrderEnum::None);
Some((sparsity.clone(), groups))
} else {
None
};
}
};
info!("jac validation: done");
}
pub fn _step_impl(&mut self) -> (bool, Option<&'static str>) {
let t = self.t;
let mut D = self.D.clone();
let max_step = self.max_step;
let min_step = 10.0 * f64::MIN;
let mut h_abs = if self.h_abs > max_step {
change_D(&mut D, self.order, max_step / self.h_abs);
self.n_equal_steps = 0;
max_step
} else if self.h_abs < min_step {
change_D(&mut D, self.order, min_step / self.h_abs);
self.n_equal_steps = 0;
min_step
} else {
self.h_abs
};
let order = self.order;
assert!(order <= MAX_ORDER, "Order cannot exceed MAX_ORDER");
assert!(
order < self.alpha.len(),
"Order must be within alpha bounds"
);
let alpha = &self.alpha;
let gamma = &self.gamma;
let error_const = &self.error_const;
let J = self.J.clone();
let mut LU = self.LU.clone();
let mut current_jac = self.jac.is_none();
let mut step_accepted = false;
let mut scale = DVector::zeros(self.n);
let mut n_iter = 0;
let mut y_new = DVector::zeros(self.n);
let _conv = false;
let mut t_new = 0.0;
let mut safety = 0.0;
let mut error_norm = 0.0;
let mut d = DVector::zeros(self.n);
while !step_accepted {
if h_abs < min_step {
return (false, "step size too small".into());
}
let h = h_abs * self.direction;
let t_new_ = t + h;
if self.direction * (t_new - self.t_bound) > 0.0 {
t_new = self.t_bound;
change_D(&mut D, order, (t_new - t).abs() / h_abs);
self.n_equal_steps = 0;
LU = None;
}
t_new = t_new_;
let h = t_new - t;
h_abs = h.abs();
let y_predict = D.rows(0, order + 1).row_sum().transpose();
let y_predict_abs = y_predict.abs();
let scale_ = scale_func(self.rtol.clone(), self.atol.clone(), &y_predict_abs);
let scale_: DVector<f64> = DVector::from_vec(scale_);
scale = scale_;
let psi = D.rows(1, order).transpose() * gamma.rows(1, order) / alpha[order];
let mut converged = false;
let c = h / alpha[order];
while !converged {
if LU.clone().is_none() {
let eye: DMatrix<f64> = DMatrix::identity(self.n, self.n);
assert_eq!(eye.shape(), J.shape(), "J shape is not equal to eye shape");
let x = &(eye - c * J.clone());
LU = Some((self.lu)(x));
}
let (conv, n_iter_, y_new_, d_) = solve_bdf_system(
&self.fun,
t_new,
&y_predict.clone(),
c,
&psi.clone(),
LU.clone().unwrap(),
&mut self.solve_lu,
&scale.clone(),
self.newton_tol,
);
n_iter = n_iter_;
y_new = y_new_;
d = d_;
converged = conv;
if !converged {
if current_jac {
break;
}
let _J = self.jac.as_mut().unwrap()(t_new, &y_predict);
LU = None;
current_jac = true;
}
}
if !converged {
let factor = 0.5;
h_abs *= factor;
change_D(&mut D, order, factor);
self.n_equal_steps = 0;
LU = None;
continue;
}
let safety_ = 0.9 * (2.0 * (NEWTON_MAXITER as f64) + 1.0)
/ (2.0 * (NEWTON_MAXITER as f64) + n_iter as f64);
safety = safety_;
let scale = scale_func(self.rtol.clone(), self.atol.clone(), &y_new.abs());
let scale: DVector<f64> = DVector::from_vec(scale);
let error = error_const[order] * d.clone();
let error_norm_ = norm(&(error.component_div(&scale)));
error_norm = error_norm_;
if error_norm > 1.0 {
let factor =
(safety * error_norm.powf(-1.0 / (order as f64 + 1.0))).max(MIN_FACTOR);
h_abs *= factor;
change_D(&mut D, order, factor);
self.n_equal_steps = 0;
} else {
step_accepted = true;
}
}
self.n_equal_steps += 1;
self.t = t_new;
self.y = y_new;
self.h_abs = h_abs;
self.J = J;
self.LU = LU;
let D_ = D.clone();
D.set_row(order + 2, &(d.clone().transpose() - D_.row(order + 1)));
D.set_row(order + 1, &d.transpose());
for i in (0..order + 1).rev() {
let D_ = D.clone();
D.row_mut(i).add_assign(D_.row(i + 1));
}
if self.n_equal_steps < order + 1 {
self.D = D;
return (true, None);
}
let error_m_norm = if order > 1 {
let error_m = error_const[order - 1] * D.row(order);
norm(&(error_m.transpose().component_div(&scale)))
} else {
f64::INFINITY
};
let error_p_norm = if order < MAX_ORDER {
let error_p = error_const[order + 1] * D.row(order + 2);
norm(&(error_p.transpose().component_div(&scale)))
} else {
f64::INFINITY
};
let error_norms = DVector::from_vec(vec![error_m_norm, error_norm, error_p_norm]);
let factors: Vec<f64> = error_norms
.iter()
.enumerate()
.map(|(i, x)| x.powf(-1.0 / (order as f64 + i as f64)))
.collect(); let factors: DVector<f64> = factors.into();
let argmax_index = factors.argmax().0;
let delta_order = (argmax_index as i32) - 1; let new_order = ((order as i32) + delta_order).max(1).min(MAX_ORDER as i32) as usize;
self.order = new_order;
let factor = (safety * factors.max()).min(MAX_FACTOR);
self.h_abs *= factor;
change_D(&mut D, self.order, factor);
self.n_equal_steps = 0;
self.LU = None;
self.D = D;
(true, None)
}
}