use crate::numerical::optimization::PPoly::{Extrapolate, PPoly};
use nalgebra::{DMatrix, DVector, LU};
const EPS: f64 = f64::EPSILON;
#[derive(Debug, Clone)]
pub struct BVPResult {
pub sol: Option<PPoly>,
pub p: Option<DVector<f64>>,
pub x: DVector<f64>,
pub y: DMatrix<f64>,
pub yp: DMatrix<f64>,
pub rms_residuals: DVector<f64>,
pub niter: usize,
pub status: i32,
pub message: String,
pub success: bool,
}
pub type ODEFunction = dyn Fn(&DVector<f64>, &DMatrix<f64>, &DVector<f64>) -> DMatrix<f64>;
pub type BCFunction = dyn Fn(&DVector<f64>, &DVector<f64>, &DVector<f64>) -> DVector<f64>;
pub type ODEJacobian = dyn Fn(
&DVector<f64>,
&DMatrix<f64>,
&DVector<f64>,
) -> (Vec<DMatrix<f64>>, Option<Vec<DMatrix<f64>>>);
pub type BCJacobian = dyn Fn(
&DVector<f64>,
&DVector<f64>,
&DVector<f64>,
) -> (DMatrix<f64>, DMatrix<f64>, Option<DMatrix<f64>>);
pub fn estimate_fun_jac(
fun: &ODEFunction,
x: &DVector<f64>,
y: &DMatrix<f64>,
p: &DVector<f64>,
f0: Option<&DMatrix<f64>>,
) -> (Vec<DMatrix<f64>>, Option<Vec<DMatrix<f64>>>) {
let (n, m) = y.shape();
let f0_computed;
let f0_ref = match f0 {
Some(f) => f,
None => {
f0_computed = fun(x, y, p);
&f0_computed
}
};
let mut df_dy = Vec::with_capacity(m);
for col in 0..m {
let mut jacobian = DMatrix::zeros(n, n);
for i in 0..n {
let mut y_perturbed = y.clone();
let h = EPS.sqrt() * (1.0 + y[(i, col)].abs());
y_perturbed[(i, col)] += h;
let x_slice = x.clone();
let f_new = fun(&x_slice, &y_perturbed, p);
for row in 0..n {
jacobian[(row, i)] = (f_new[(row, col)] - f0_ref[(row, col)]) / h;
}
}
df_dy.push(jacobian);
}
let df_dp = if p.len() == 0 {
None
} else {
let k = p.len();
let mut df_dp_vec = Vec::with_capacity(m);
for col in 0..m {
let mut param_jacobian = DMatrix::zeros(n, k);
for i in 0..k {
let mut p_perturbed = p.clone();
let h = EPS.sqrt() * (1.0 + p[i].abs());
p_perturbed[i] += h;
let f_new = fun(x, y, &p_perturbed);
for row in 0..n {
param_jacobian[(row, i)] = (f_new[(row, col)] - f0_ref[(row, col)]) / h;
}
}
df_dp_vec.push(param_jacobian);
}
Some(df_dp_vec)
};
(df_dy, df_dp)
}
pub fn estimate_bc_jac(
bc: &BCFunction,
ya: &DVector<f64>,
yb: &DVector<f64>,
p: &DVector<f64>,
bc0: Option<&DVector<f64>>,
) -> (DMatrix<f64>, DMatrix<f64>, Option<DMatrix<f64>>) {
let n = ya.len();
let k = p.len();
let bc0_computed;
let bc0_ref = match bc0 {
Some(bc_val) => bc_val,
None => {
bc0_computed = bc(ya, yb, p);
&bc0_computed
}
};
let mut dbc_dya = DMatrix::zeros(n + k, n);
for i in 0..n {
let mut ya_perturbed = ya.clone();
let h = EPS.sqrt() * (1.0 + ya[i].abs());
ya_perturbed[i] += h;
let bc_new = bc(&ya_perturbed, yb, p);
for row in 0..(n + k) {
dbc_dya[(row, i)] = (bc_new[row] - bc0_ref[row]) / h;
}
}
let mut dbc_dyb = DMatrix::zeros(n + k, n);
for i in 0..n {
let mut yb_perturbed = yb.clone();
let h = EPS.sqrt() * (1.0 + yb[i].abs());
yb_perturbed[i] += h;
let bc_new = bc(ya, &yb_perturbed, p);
for row in 0..(n + k) {
dbc_dyb[(row, i)] = (bc_new[row] - bc0_ref[row]) / h;
}
}
let dbc_dp = if k == 0 {
None
} else {
let mut dbc_dp_mat = DMatrix::zeros(n + k, k);
for i in 0..k {
let mut p_perturbed = p.clone();
let h = EPS.sqrt() * (1.0 + p[i].abs());
p_perturbed[i] += h;
let bc_new = bc(ya, yb, &p_perturbed);
for row in 0..(n + k) {
dbc_dp_mat[(row, i)] = (bc_new[row] - bc0_ref[row]) / h;
}
}
Some(dbc_dp_mat)
};
(dbc_dya, dbc_dyb, dbc_dp)
}
pub fn compute_jac_indices(n: usize, m: usize, k: usize) -> (Vec<usize>, Vec<usize>) {
let mut i_indices = Vec::new();
let mut j_indices = Vec::new();
for block in 0..(m - 1) {
for i in 0..n {
for j in 0..n {
i_indices.push(block * n + i);
j_indices.push(block * n + j);
}
}
}
for block in 0..(m - 1) {
for i in 0..n {
for j in 0..n {
i_indices.push(block * n + i);
j_indices.push((block + 1) * n + j);
}
}
}
for i in 0..(n + k) {
for j in 0..n {
i_indices.push((m - 1) * n + i);
j_indices.push(j);
}
}
for i in 0..(n + k) {
for j in 0..n {
i_indices.push((m - 1) * n + i);
j_indices.push((m - 1) * n + j);
}
}
if k > 0 {
for block in 0..(m - 1) {
for i in 0..n {
for j in 0..k {
i_indices.push(block * n + i);
j_indices.push(m * n + j);
}
}
}
for i in 0..(n + k) {
for j in 0..k {
i_indices.push((m - 1) * n + i);
j_indices.push(m * n + j);
}
}
}
(i_indices, j_indices)
}
pub fn stacked_matmul(a: &[DMatrix<f64>], b: &[DMatrix<f64>]) -> Vec<DMatrix<f64>> {
assert_eq!(a.len(), b.len());
a.iter()
.zip(b.iter())
.map(|(a_mat, b_mat)| a_mat * b_mat)
.collect()
}
pub fn collocation_fun(
fun: &ODEFunction,
y: &DMatrix<f64>,
p: &DVector<f64>,
x: &DVector<f64>,
h: &DVector<f64>,
) -> (DMatrix<f64>, DMatrix<f64>, DMatrix<f64>, DMatrix<f64>) {
let (n, m) = y.shape();
let f = fun(x, y, p);
let mut y_middle = DMatrix::zeros(n, m - 1);
for i in 0..n {
for j in 0..(m - 1) {
y_middle[(i, j)] =
0.5 * (y[(i, j + 1)] + y[(i, j)]) - 0.125 * h[j] * (f[(i, j + 1)] - f[(i, j)]);
}
}
let mut f_middle = DMatrix::zeros(n, m - 1);
for j in 0..(m - 1) {
let x_mid = x[j] + 0.5 * h[j];
let x_single = DVector::from_vec(vec![x_mid]);
let mut y_single = DMatrix::zeros(n, 1);
for i in 0..n {
y_single[(i, 0)] = y_middle[(i, j)];
}
let f_result = fun(&x_single, &y_single, p);
for i in 0..n {
f_middle[(i, j)] = f_result[(i, 0)];
}
}
let mut col_res = DMatrix::zeros(n, m - 1);
for i in 0..n {
for j in 0..(m - 1) {
col_res[(i, j)] = y[(i, j + 1)]
- y[(i, j)]
- h[j] / 6.0 * (f[(i, j)] + f[(i, j + 1)] + 4.0 * f_middle[(i, j)]);
}
}
(col_res, y_middle, f, f_middle)
}
pub fn construct_global_jac(
n: usize,
m: usize,
k: usize,
h: &DVector<f64>,
df_dy: &[DMatrix<f64>],
df_dy_middle: &[DMatrix<f64>],
df_dp: Option<&[DMatrix<f64>]>,
df_dp_middle: Option<&[DMatrix<f64>]>,
dbc_dya: &DMatrix<f64>,
dbc_dyb: &DMatrix<f64>,
dbc_dp: Option<&DMatrix<f64>>,
) -> DMatrix<f64> {
let total_size = (m - 1) * n + (n + k);
let mut jac = DMatrix::zeros(total_size, total_size);
for i in 0..(m - 1) {
let h_i = h[i];
let mut dphi_dy_0 = -DMatrix::identity(n, n);
dphi_dy_0 -= (h_i / 6.0) * (&df_dy[i] + 2.0 * &df_dy_middle[i]);
let t_diag = &df_dy_middle[i] * &df_dy[i];
dphi_dy_0 -= (h_i * h_i / 12.0) * t_diag;
let row_start = i * n;
let col_start = i * n;
for r in 0..n {
for c in 0..n {
jac[(row_start + r, col_start + c)] = dphi_dy_0[(r, c)];
}
}
let mut dphi_dy_1 = DMatrix::identity(n, n);
dphi_dy_1 -= (h_i / 6.0) * (&df_dy[i + 1] + 2.0 * &df_dy_middle[i]);
let t_off = &df_dy_middle[i] * &df_dy[i + 1];
dphi_dy_1 += (h_i * h_i / 12.0) * t_off;
let col_start_off = (i + 1) * n;
for r in 0..n {
for c in 0..n {
jac[(row_start + r, col_start_off + c)] = dphi_dy_1[(r, c)];
}
}
if let (Some(df_dp_vec), Some(df_dp_middle_vec)) = (df_dp, df_dp_middle) {
let dp_diff = &df_dp_vec[i] - &df_dp_vec[i + 1];
let t_param = &df_dy_middle[i] * dp_diff;
let mut dphi_dp =
-h_i / 6.0 * (&df_dp_vec[i] + &df_dp_vec[i + 1] + 4.0 * &df_dp_middle_vec[i]);
dphi_dp += 0.125 * h_i * t_param;
let param_col_start = n * m;
for r in 0..n {
for c in 0..k {
jac[(row_start + r, param_col_start + c)] = dphi_dp[(r, c)];
}
}
}
}
let bc_row_start = (m - 1) * n;
for r in 0..(n + k) {
for c in 0..n {
jac[(bc_row_start + r, c)] = dbc_dya[(r, c)];
}
}
let yb_col_start = (m - 1) * n;
for r in 0..(n + k) {
for c in 0..n {
jac[(bc_row_start + r, yb_col_start + c)] = dbc_dyb[(r, c)];
}
}
if let Some(dbc_dp_mat) = dbc_dp {
let param_col_start = n * m;
for r in 0..(n + k) {
for c in 0..k {
jac[(bc_row_start + r, param_col_start + c)] = dbc_dp_mat[(r, c)];
}
}
}
jac
}
pub fn solve_newton(
n: usize,
m: usize,
h: &DVector<f64>,
fun: &ODEFunction,
bc: &BCFunction,
fun_jac: Option<&ODEJacobian>,
bc_jac: Option<&BCJacobian>,
mut y: DMatrix<f64>,
mut p: DVector<f64>,
x: &DVector<f64>,
bvp_tol: f64,
bc_tol: f64,
) -> (DMatrix<f64>, DVector<f64>, bool) {
let k = p.len();
let max_iter = 8;
let max_njev = 4;
let sigma = 0.2; let tau = 0.5; let n_trial = 4;
let tol_r: DVector<f64> = h.map(|h_i| 2.0 / 3.0 * h_i * 5e-2 * bvp_tol);
let mut njev = 0;
let mut singular = false;
let mut recompute_jac = true;
let mut lu_decomp: Option<LU<f64, nalgebra::Dyn, nalgebra::Dyn>> = None;
let mut cost = 0.0;
for _iteration in 0..max_iter {
let (col_res, y_middle, f, f_middle) = collocation_fun(fun, &y, &p, x, h);
let bc_res = bc(&y.column(0).into(), &y.column(m - 1).into(), &p);
let mut res = DVector::zeros((m - 1) * n + (n + k));
for j in 0..(m - 1) {
for i in 0..n {
res[j * n + i] = col_res[(i, j)];
}
}
for i in 0..(n + k) {
res[(m - 1) * n + i] = bc_res[i];
}
if recompute_jac {
let (df_dy, df_dp) = if let Some(jac_fn) = fun_jac {
jac_fn(x, &y, &p)
} else {
estimate_fun_jac(fun, x, &y, &p, Some(&f))
};
let mut x_middle = DVector::zeros(m - 1);
for j in 0..(m - 1) {
x_middle[j] = x[j] + 0.5 * h[j];
}
let (df_dy_middle, df_dp_middle) = if let Some(jac_fn) = fun_jac {
jac_fn(&x_middle, &y_middle, &p)
} else {
estimate_fun_jac(fun, &x_middle, &y_middle, &p, Some(&f_middle))
};
let (dbc_dya, dbc_dyb, dbc_dp) = if let Some(bc_jac_fn) = bc_jac {
bc_jac_fn(&y.column(0).into(), &y.column(m - 1).into(), &p)
} else {
estimate_bc_jac(
bc,
&y.column(0).into(),
&y.column(m - 1).into(),
&p,
Some(&bc_res),
)
};
let jac_matrix = construct_global_jac(
n,
m,
k,
h,
&df_dy,
&df_dy_middle,
df_dp.as_ref().map(|v| v.as_slice()),
df_dp_middle.as_ref().map(|v| v.as_slice()),
&dbc_dya,
&dbc_dyb,
dbc_dp.as_ref(),
);
let lu = LU::new(jac_matrix);
match lu.solve(&res) {
Some(step) => {
lu_decomp = Some(lu);
cost = step.dot(&step);
}
None => {
singular = true;
break;
}
}
njev += 1;
}
if let Some(ref lu) = lu_decomp {
if let Some(step) = lu.solve(&res) {
let mut y_step = DMatrix::zeros(n, m);
for j in 0..m {
for i in 0..n {
y_step[(i, j)] = step[j * n + i];
}
}
let p_step = if k > 0 {
step.rows((m - 1) * n + (n + k) - k, k).into()
} else {
DVector::zeros(0)
};
let mut alpha = 1.0;
let mut best_y = y.clone();
let mut best_p = p.clone();
let mut best_cost = cost;
for trial in 0..=n_trial {
let y_new = &y - alpha * &y_step;
let p_new = &p - alpha * &p_step;
let (col_res_new, _y_middle_new, _f_new, _f_middle_new) =
collocation_fun(fun, &y_new, &p_new, x, h);
let bc_res_new =
bc(&y_new.column(0).into(), &y_new.column(m - 1).into(), &p_new);
let mut res_new = DVector::zeros((m - 1) * n + (n + k));
for j in 0..(m - 1) {
for i in 0..n {
res_new[j * n + i] = col_res_new[(i, j)];
}
}
for i in 0..(n + k) {
res_new[(m - 1) * n + i] = bc_res_new[i];
}
if let Some(step_new) = lu.solve(&res_new) {
let cost_new = step_new.dot(&step_new);
if cost_new < (1.0 - 2.0 * alpha * sigma) * cost {
best_y = y_new;
best_p = p_new;
best_cost = cost_new;
break;
}
}
if trial < n_trial {
alpha *= tau;
}
}
y = best_y;
p = best_p;
cost = best_cost;
let (col_res_final, _, _f_final, f_middle_final) =
collocation_fun(fun, &y, &p, x, h);
let bc_res_final = bc(&y.column(0).into(), &y.column(m - 1).into(), &p);
let mut converged = true;
for j in 0..(m - 1) {
for i in 0..n {
if col_res_final[(i, j)].abs()
>= tol_r[j] * (1.0 + f_middle_final[(i, j)].abs())
{
converged = false;
break;
}
}
if !converged {
break;
}
}
for i in 0..(n + k) {
if bc_res_final[i].abs() >= bc_tol {
converged = false;
break;
}
}
if converged {
break;
}
recompute_jac = if alpha == 1.0 {
false } else {
true };
if njev >= max_njev {
break;
}
} else {
singular = true;
break;
}
} else {
singular = true;
break;
}
}
(y, p, singular)
}
pub fn create_spline(
y: &DMatrix<f64>,
yp: &DMatrix<f64>,
x: &DVector<f64>,
h: &DVector<f64>,
) -> PPoly {
let (n, m) = y.shape();
let mut c = vec![vec![vec![0.0; n]; m - 1]; 4];
for j in 0..(m - 1) {
for i in 0..n {
let slope = (y[(i, j + 1)] - y[(i, j)]) / h[j];
let t = (yp[(i, j)] + yp[(i, j + 1)] - 2.0 * slope) / h[j];
c[0][j][i] = t / h[j]; c[1][j][i] = (slope - yp[(i, j)]) / h[j] - t; c[2][j][i] = yp[(i, j)]; c[3][j][i] = y[(i, j)]; }
}
PPoly {
c,
x: x.iter().cloned().collect(),
extrapolate: Extrapolate::Bool(true),
axis: 1, }
}
pub fn estimate_rms_residuals(
fun: &ODEFunction,
sol: &PPoly,
x: &DVector<f64>,
h: &DVector<f64>,
p: &DVector<f64>,
r_middle: &DMatrix<f64>,
f_middle: &DMatrix<f64>,
) -> DVector<f64> {
let (n, m1) = r_middle.shape();
let mut x_middle = DVector::zeros(m1);
for j in 0..m1 {
x_middle[j] = x[j] + 0.5 * h[j];
}
let mut x1 = DVector::zeros(m1);
let mut x2 = DVector::zeros(m1);
for j in 0..m1 {
let s = 0.5 * h[j] * ((3.0 / 7.0) as f64).sqrt();
x1[j] = x_middle[j] + s;
x2[j] = x_middle[j] - s;
}
let y1 = sol.call(&x1.data.as_vec(), &[m1], Some(0), None);
let y2 = sol.call(&x2.data.as_vec(), &[m1], Some(0), None);
let y1_prime = sol.call(&x1.data.as_vec(), &[m1], Some(1), None);
let y2_prime = sol.call(&x2.data.as_vec(), &[m1], Some(1), None);
let f1 = fun(&x1, &y1.transpose(), p);
let f2 = fun(&x2, &y2.transpose(), p);
let mut r1 = DMatrix::zeros(n, m1);
let mut r2 = DMatrix::zeros(n, m1);
for j in 0..m1 {
for i in 0..n {
r1[(i, j)] = y1_prime[(j, i)] - f1[(i, j)];
r2[(i, j)] = y2_prime[(j, i)] - f2[(i, j)];
}
}
let mut r_middle_norm = DVector::zeros(m1);
let mut r1_norm = DVector::zeros(m1);
let mut r2_norm = DVector::zeros(m1);
for j in 0..m1 {
let mut sum_r_middle = 0.0;
let mut sum_r1 = 0.0;
let mut sum_r2 = 0.0;
for i in 0..n {
let r_mid_norm = r_middle[(i, j)] / (1.0 + f_middle[(i, j)].abs());
let r1_norm_val = r1[(i, j)] / (1.0 + f1[(i, j)].abs());
let r2_norm_val = r2[(i, j)] / (1.0 + f2[(i, j)].abs());
sum_r_middle += r_mid_norm * r_mid_norm;
sum_r1 += r1_norm_val * r1_norm_val;
sum_r2 += r2_norm_val * r2_norm_val;
}
r_middle_norm[j] = sum_r_middle;
r1_norm[j] = sum_r1;
r2_norm[j] = sum_r2;
}
let mut rms_res = DVector::zeros(m1);
for j in 0..m1 {
let integral =
0.5 * (32.0 / 45.0 * r_middle_norm[j] + 49.0 / 90.0 * (r1_norm[j] + r2_norm[j]));
rms_res[j] = integral.sqrt();
}
rms_res
}
pub fn modify_mesh(x: &DVector<f64>, insert_1: &[usize], insert_2: &[usize]) -> DVector<f64> {
let mut new_points = x.iter().cloned().collect::<Vec<f64>>();
for &i in insert_1 {
let mid = 0.5 * (x[i] + x[i + 1]);
new_points.push(mid);
}
for &i in insert_2 {
let p1 = (2.0 * x[i] + x[i + 1]) / 3.0;
let p2 = (x[i] + 2.0 * x[i + 1]) / 3.0;
new_points.push(p1);
new_points.push(p2);
}
new_points.sort_by(|a, b| a.partial_cmp(b).unwrap());
DVector::from_vec(new_points)
}
pub fn solve_bvp(
fun: &ODEFunction,
bc: &BCFunction,
mut x: DVector<f64>,
mut y: DMatrix<f64>,
p: Option<DVector<f64>>,
_s: Option<DMatrix<f64>>, fun_jac: Option<&ODEJacobian>,
bc_jac: Option<&BCJacobian>,
tol: f64,
max_nodes: usize,
verbose: u8,
bc_tol: Option<f64>,
) -> Result<BVPResult, String> {
let n = y.nrows();
let mut m = x.len();
if y.ncols() != m {
return Err("y must have same number of columns as x has elements".to_string());
}
let mut p = p.unwrap_or_else(|| DVector::zeros(0));
let bc_tol = bc_tol.unwrap_or(tol);
let max_iteration = 10;
let f_test = fun(&x, &y, &p);
if f_test.shape() != y.shape() {
return Err(format!(
"Function return shape {:?} doesn't match y shape {:?}",
f_test.shape(),
y.shape()
));
}
let bc_test = bc(&y.column(0).into(), &y.column(m - 1).into(), &p);
let expected_bc_size = n + p.len();
if bc_test.len() != expected_bc_size {
return Err(format!(
"BC return size {} doesn't match expected size {}",
bc_test.len(),
expected_bc_size
));
}
let mut status = 0;
let mut iteration = 0;
if verbose == 2 {
println!(
"{:^15}{:^15}{:^15}{:^15}{:^15}",
"Iteration", "Max residual", "Max BC residual", "Total nodes", "Nodes added"
);
}
loop {
m = x.len();
let mut h = DVector::zeros(m - 1);
for i in 0..(m - 1) {
h[i] = x[i + 1] - x[i];
}
let (y_new, p_new, singular) = solve_newton(
n,
m,
&h,
fun,
bc,
fun_jac,
bc_jac,
y.clone(),
p.clone(),
&x,
tol,
bc_tol,
);
y = y_new;
p = p_new;
iteration += 1;
let (col_res, _y_middle, f, f_middle) = collocation_fun(fun, &y, &p, &x, &h);
let bc_res = bc(&y.column(0).into(), &y.column(m - 1).into(), &p);
let max_bc_res = bc_res.iter().map(|x| x.abs()).fold(0.0, f64::max);
if singular {
status = 2;
break;
}
let mut r_middle = DMatrix::zeros(n, m - 1);
for j in 0..(m - 1) {
for i in 0..n {
r_middle[(i, j)] = 1.5 * col_res[(i, j)] / h[j];
}
}
let sol = create_spline(&y, &f, &x, &h);
let rms_res = estimate_rms_residuals(fun, &sol, &x, &h, &p, &r_middle, &f_middle);
let max_rms_res = rms_res.iter().cloned().fold(0.0, f64::max);
let mut insert_1 = Vec::new();
let mut insert_2 = Vec::new();
for j in 0..(m - 1) {
if rms_res[j] > tol && rms_res[j] < 100.0 * tol {
insert_1.push(j);
} else if rms_res[j] >= 100.0 * tol {
insert_2.push(j);
}
}
let nodes_added = insert_1.len() + 2 * insert_2.len();
if m + nodes_added > max_nodes {
status = 1;
if verbose == 2 {
println!(
"{:^15}{:^15.2e}{:^15.2e}{:^15}{:^15}",
iteration,
max_rms_res,
max_bc_res,
m,
format!("({})", nodes_added)
);
}
break;
}
if verbose == 2 {
println!(
"{:^15}{:^15.2e}{:^15.2e}{:^15}{:^15}",
iteration, max_rms_res, max_bc_res, m, nodes_added
);
}
if nodes_added > 0 {
x = modify_mesh(&x, &insert_1, &insert_2);
let x_eval: Vec<f64> = x.iter().cloned().collect();
let y_new_vals = sol.call(&x_eval, &[x.len()], Some(0), None);
y = y_new_vals.transpose();
} else if max_bc_res <= bc_tol {
status = 0;
break;
} else if iteration >= max_iteration {
status = 3;
break;
}
}
if verbose > 0 {
match status {
0 => println!(
"Solved in {} iterations, number of nodes {}.",
iteration,
x.len()
),
1 => println!("Number of nodes exceeded after iteration {}.", iteration),
2 => println!("Singular Jacobian encountered on iteration {}.", iteration),
3 => println!(
"Unable to satisfy boundary conditions tolerance on iteration {}.",
iteration
),
_ => {}
}
}
let final_f = fun(&x, &y, &p);
let final_h = DVector::from_iterator(x.len() - 1, (0..x.len() - 1).map(|i| x[i + 1] - x[i]));
let (col_res_final, _y_middle_final, _, f_middle_final) =
collocation_fun(fun, &y, &p, &x, &final_h);
let mut r_middle_final = DMatrix::zeros(n, x.len() - 1);
for j in 0..(x.len() - 1) {
for i in 0..n {
r_middle_final[(i, j)] = 1.5 * col_res_final[(i, j)] / final_h[j];
}
}
let final_sol = create_spline(&y, &final_f, &x, &final_h);
let final_rms_res = estimate_rms_residuals(
fun,
&final_sol,
&x,
&final_h,
&p,
&r_middle_final,
&f_middle_final,
);
let message = match status {
0 => "The algorithm converged to the desired accuracy.",
1 => "The maximum number of mesh nodes is exceeded.",
2 => "A singular Jacobian encountered when solving the collocation system.",
3 => "The solver was unable to satisfy boundary conditions tolerance on iteration 10.",
_ => "Unknown status",
};
Ok(BVPResult {
sol: Some(final_sol),
p: if p.len() > 0 { Some(p) } else { None },
x,
y,
yp: final_f,
rms_residuals: final_rms_res,
niter: iteration,
status,
message: message.to_string(),
success: status == 0,
})
}