use anyhow::{format_err, Result};
use sundials_sys::sunindextype;
use crate::context::Context;
use crate::kinsol::{Strategy, KIN};
use crate::nvector;
use crate::nvector::NVector;
use crate::sunlinsol::LinearSolver;
use crate::sunmatrix::{SparseMatrix, SparseType};
#[test]
pub fn test_kinsol_roberts_fp() -> Result<()> {
const NEQ: sunindextype = 3; const Y10: f64 = 1.0; const Y20: f64 = 0.0;
const Y30: f64 = 0.0;
const TOL: f64 = 1e-10; const DSTEP: f64 = 0.1;
const PRIORS: usize = 2;
const ZERO: f64 = 0.0;
const ONE: f64 = 1.0;
fn ith(v: &NVector, i: usize) -> f64 {
v.as_slice()[i - 1]
}
fn set_ith(v: &mut NVector, i: usize, x: f64) {
v.as_slice_mut()[i - 1] = x;
}
fn roberts(y: &NVector, g: &mut NVector, _: &Option<()>) -> i32 {
let y1 = ith(y, 1);
let y2 = ith(y, 2);
let y3 = ith(y, 3);
let yd1 = DSTEP * (-0.04 * y1 + 1.0e4 * y2 * y3);
let yd3 = DSTEP * 3.0e2 * y2 * y2;
set_ith(g, 1, yd1 + Y10);
set_ith(g, 2, -yd1 - yd3 + Y20);
set_ith(g, 3, yd3 + Y30);
0
}
fn print_output(y: &NVector) {
let y1 = ith(y, 1);
let y2 = ith(y, 2);
let y3 = ith(y, 3);
println!("y = {:e} {:e} {:e}", y1, y2, y3);
}
fn print_final_stats(kmem: &KIN<()>) {
let nni = kmem.num_nonlin_solv_iters().unwrap();
let nfe = kmem.num_func_evals().unwrap();
println!("\nFinal Statistics..\n");
println!("nni = {:6} nfe = {:6}", nni, nfe);
}
fn check_ans(u: &NVector, rtol: f64, atol: f64) -> Result<()> {
let r#ref = u.clone();
let mut ewt = u.clone();
r#ref.as_slice_mut()[0] = 9.9678538655358029e-01;
r#ref.as_slice_mut()[1] = 2.9530060962800345e-03;
r#ref.as_slice_mut()[2] = 2.6160735013975683e-04;
r#ref.abs(&mut ewt);
ewt *= rtol;
ewt += atol;
if ewt.min() <= ZERO {
return Err(format_err!("SUNDIALS_ERROR: check_ans failed - ewt <= 0"));
}
ewt.inv();
let err = NVector::linear_sum(ONE, u, -ONE, &r#ref).wrms_norm(&ewt);
if err >= ONE {
return Err(format_err!("SUNDIALS_WARNING: check_ans error={}", err));
}
Ok(())
}
println!("Example problem from chemical kinetics solving");
println!("the first time step in a Backward Euler solution for the");
println!("following three rate equations:");
println!(" dy1/dt = -.04*y1 + 1.e4*y2*y3");
println!(" dy2/dt = .04*y1 - 1.e4*y2*y3 - 3.e2*(y2)^2");
println!(" dy3/dt = 3.e2*(y2)^2");
println!("on the interval from t = 0.0 to t = 0.1, with initial");
println!("conditions: y1 = 1.0, y2 = y3 = 0.");
println!("Solution method: Anderson accelerated fixed point iteration.");
let sunctx = Context::new().unwrap();
let mut y = NVector::new_serial(NEQ, &sunctx)?;
let mut scale = NVector::new_serial(NEQ, &sunctx)?;
let mut kmem: KIN<()> = KIN::new(&sunctx)?;
kmem.set_maa(PRIORS)?;
kmem.init(Some(roberts), None, None, None, &y)?;
let fnormtol = TOL;
kmem.set_func_norm_tol(fnormtol)?;
y.fill_with(ZERO);
set_ith(&mut y, 1, ONE);
scale.fill_with(ONE);
kmem.solve(
&mut y, Strategy::FP, &scale, &scale, )?;
let fnorm = kmem.func_norm()?;
println!("\nComputed solution (||F|| = {:e}):\n", fnorm);
print_output(&y);
print_final_stats(&kmem);
check_ans(&y, 1e-4, 1e-6)?;
Ok(())
}
#[test]
fn test_fer_tron_kinsol() -> Result<()> {
const NVAR: sunindextype = 2;
const NEQ_FT: sunindextype = 3 * NVAR;
const NNZ: sunindextype = 12;
const FTOL: f64 = 1e-5; const STOL: f64 = 1e-5;
const ZERO: f64 = 0.0;
const PT25: f64 = 0.25;
const PT5: f64 = 0.5;
const ONE: f64 = 1.0;
const ONEPT5: f64 = 1.5;
const TWO: f64 = 2.0;
const PI: f64 = 3.1415926;
const E: f64 = 2.7182818;
struct FerrarisTronconi {
lb: Vec<f64>,
ub: Vec<f64>,
nnz: sunindextype,
}
#[allow(non_snake_case)]
fn fer_tron_func(u: &NVector, f: &mut NVector, user_data: &Option<FerrarisTronconi>) -> i32 {
let params = user_data.as_ref().unwrap();
let lb = ¶ms.lb;
let ub = ¶ms.ub;
let udata = u.as_slice();
let fdata = f.as_slice_mut();
let x1 = udata[0];
let x2 = udata[1];
let l1 = udata[2];
let L1 = udata[3];
let l2 = udata[4];
let L2 = udata[5];
fdata[0] = PT5 * f64::sin(x1 * x2) - PT25 * x2 / PI - PT5 * x1;
fdata[1] = (ONE - PT25 / PI) * (f64::exp(TWO * x1) - E) + E * x2 / PI - TWO * E * x1;
fdata[2] = l1 - x1 + lb[0];
fdata[3] = L1 - x1 + ub[0];
fdata[4] = l2 - x2 + lb[1];
fdata[5] = L2 - x2 + ub[1];
0
}
#[allow(non_snake_case)]
fn fer_tron_jac(
y: &NVector,
_f: &mut NVector,
J: &mut SparseMatrix,
_user_data: &Option<FerrarisTronconi>,
_tmp1: &NVector,
_tmp2: &NVector,
) -> i32 {
J.zero().unwrap();
let (rowptrs, colvals, data) = J.index_pointers_values_data_mut();
let yd = y.as_slice();
rowptrs[0] = 0;
rowptrs[1] = 2;
rowptrs[2] = 4;
rowptrs[3] = 6;
rowptrs[4] = 8;
rowptrs[5] = 10;
rowptrs[6] = 12;
data[0] = PT5 * f64::cos(yd[0] * yd[1]) * yd[1] - PT5;
colvals[0] = 0;
data[1] = PT5 * f64::cos(yd[0] * yd[1]) * yd[0] - PT25 / PI;
colvals[1] = 1;
data[2] = TWO * (ONE - PT25 / PI) * (f64::exp(TWO * yd[0]) - E);
colvals[2] = 0;
data[3] = E / PI;
colvals[3] = 1;
data[4] = -ONE;
colvals[4] = 0;
data[5] = ONE;
colvals[5] = 2;
data[6] = -ONE;
colvals[6] = 0;
data[7] = ONE;
colvals[7] = 3;
data[8] = -ONE;
colvals[8] = 1;
data[9] = ONE;
colvals[9] = 4;
data[10] = -ONE;
colvals[10] = 1;
data[11] = ONE;
colvals[11] = 5;
0
}
fn set_initial_guess1(u: &mut NVector, data: &FerrarisTronconi) {
let udata = u.as_slice_mut();
let lb = &data.lb;
let ub = &data.ub;
let x1 = lb[0];
let x2 = lb[1];
udata[0] = x1;
udata[1] = x2;
udata[2] = x1 - lb[0];
udata[3] = x1 - ub[0];
udata[4] = x2 - lb[1];
udata[5] = x2 - ub[1];
}
fn set_initial_guess2(u: &NVector, data: &FerrarisTronconi) {
let udata = u.as_slice_mut();
let lb = &data.lb;
let ub = &data.ub;
let x1 = PT5 * (lb[0] + ub[0]);
let x2 = PT5 * (lb[1] + ub[1]);
udata[0] = x1;
udata[1] = x2;
udata[2] = x1 - lb[0];
udata[3] = x1 - ub[0];
udata[4] = x2 - lb[1];
udata[5] = x2 - ub[1];
}
fn print_header(fnormtol: f64, scsteptol: f64) {
println!("\nFerraris and Tronconi test problem");
println!("Tolerance parameters:");
println!(
" fnormtol = {:16.6}\n scsteptol = {:16.6}",
fnormtol, scsteptol
);
}
fn print_output(u: &NVector) {
let ud = u.as_slice();
println!(" {:8.6} {:8.6}", ud[0], ud[1])
}
fn print_final_stats(kmem: &KIN<FerrarisTronconi>) {
let nni = kmem.num_nonlin_solv_iters().unwrap();
let nfe = kmem.num_func_evals().unwrap();
let nje = kmem.num_jac_evals().unwrap();
println!("Final Statistics:");
println!(" nni = {:5} nfe = {:5}", nni, nfe);
println!(" nje = {:5}", nje)
}
fn solve_it(
kmem: &mut KIN<FerrarisTronconi>,
u: &mut NVector,
s: &NVector,
glstr: Strategy,
mset: sunindextype,
) -> Result<()> {
println!();
if mset == 1 {
print!("Exact Newton");
} else {
print!("Modified Newton");
}
if glstr == Strategy::None {
println!();
} else {
println!(" with line search");
}
kmem.set_max_setup_calls(mset as i64)?;
kmem.solve(u, glstr, s, s)?;
print!("Solution:\n [x1,x2] = ");
print_output(u);
print_final_stats(kmem);
Ok(())
}
let mut data = FerrarisTronconi {
lb: vec![0.0; NVAR as usize],
ub: vec![0.0; NVAR as usize],
nnz: 0,
};
data.lb[0] = PT25;
data.ub[0] = ONE;
data.lb[1] = ONEPT5;
data.ub[1] = TWO * PI;
data.nnz = NNZ;
let sunctx = Context::new()?;
let mut u1 = NVector::new_serial(NEQ_FT, &sunctx)?;
let mut u2 = NVector::new_serial(NEQ_FT, &sunctx)?;
let mut u = NVector::new_serial(NEQ_FT, &sunctx)?;
let mut s = NVector::new_serial(NEQ_FT, &sunctx)?;
let c = NVector::new_serial(NEQ_FT, &sunctx)?;
set_initial_guess1(&mut u1, &data);
set_initial_guess2(&mut u2, &data);
s.fill_with(ONE);
{
let cd = c.as_slice_mut();
cd[0] = ZERO; cd[1] = ZERO; cd[2] = ONE; cd[3] = -ONE; cd[4] = ONE; cd[5] = -ONE; }
let fnormtol = FTOL;
let scsteptol = STOL;
let mut kmem: KIN<FerrarisTronconi> = KIN::new(&sunctx)?;
kmem.set_constraints(&c)?;
kmem.set_func_norm_tol(fnormtol)?;
kmem.set_scaled_step_tol(scsteptol)?;
#[allow(non_snake_case)]
let J = SparseMatrix::new(NEQ_FT, NEQ_FT, NNZ, SparseType::CSR, &sunctx);
#[allow(non_snake_case)]
#[cfg(not(feature = "klu"))]
let LS = LinearSolver::new_faer(&u, &J, &sunctx);
#[cfg(feature = "klu")]
let LS = LinearSolver::new_klu(&u, &J, &sunctx);
kmem.init(
Some(fer_tron_func),
Some((&LS, &J)),
Some(fer_tron_jac),
Some(data),
&u,
)?;
print_header(fnormtol, scsteptol);
println!("\n------------------------------------------");
println!("\nInitial guess on lower bounds");
print!(" [x1,x2] = ");
print_output(&u1);
nvector::scale(ONE, &u1, &mut u);
let glstr = Strategy::None;
let mset = 1;
solve_it(&mut kmem, &mut u, &s, glstr, mset)?;
nvector::scale(ONE, &u1, &mut u);
let glstr = Strategy::LineSearch;
let mset = 1;
solve_it(&mut kmem, &mut u, &s, glstr, mset)?;
nvector::scale(ONE, &u1, &mut u);
let glstr = Strategy::None;
let mset = 0;
solve_it(&mut kmem, &mut u, &s, glstr, mset)?;
nvector::scale(ONE, &u1, &mut u);
let glstr = Strategy::LineSearch;
let mset = 0;
solve_it(&mut kmem, &mut u, &s, glstr, mset)?;
println!("\n------------------------------------------");
println!("\nInitial guess in middle of feasible region");
print!(" [x1,x2] = ");
print_output(&u2);
nvector::scale(ONE, &u2, &mut u);
let glstr = Strategy::None;
let mset = 1;
solve_it(&mut kmem, &mut u, &s, glstr, mset)?;
nvector::scale(ONE, &u2, &mut u);
let glstr = Strategy::LineSearch;
let mset = 1;
solve_it(&mut kmem, &mut u, &s, glstr, mset)?;
nvector::scale(ONE, &u2, &mut u);
let glstr = Strategy::None;
let mset = 0;
solve_it(&mut kmem, &mut u, &s, glstr, mset)?;
nvector::scale(ONE, &u2, &mut u);
let glstr = Strategy::LineSearch;
let mset = 0;
solve_it(&mut kmem, &mut u, &s, glstr, mset)?;
Ok(())
}