use std::os::raw::{c_int, c_void};
use sundials_rs_sys as sys;
use crate::{
context::SunContext,
error::{check_flag, SundialsError},
linear_solver::LinearSolver,
matrix::DenseMatrix,
nvector::NVector,
};
struct UserData<F> {
res: F,
neq: usize,
}
unsafe extern "C" fn res_trampoline<F>(
t: sys::sunrealtype,
y: sys::N_Vector,
yp: sys::N_Vector,
rr: sys::N_Vector,
user_data: *mut c_void,
) -> c_int
where
F: Fn(f64, &[f64], &[f64], &mut [f64]) -> Result<(), i32>,
{
let ud = &*(user_data as *const UserData<F>);
let n = ud.neq;
let y_s = std::slice::from_raw_parts(sys::N_VGetArrayPointer(y), n);
let yp_s = std::slice::from_raw_parts(sys::N_VGetArrayPointer(yp), n);
let rr_s = std::slice::from_raw_parts_mut(sys::N_VGetArrayPointer(rr), n);
match (ud.res)(t, y_s, yp_s, rr_s) {
Ok(()) => 0,
Err(f) => f,
}
}
pub struct IdaBuilder {
y0: Vec<f64>,
yp0: Vec<f64>,
t0: f64,
rtol: f64,
atol: f64,
max_steps: Option<i64>,
}
impl IdaBuilder {
pub fn new(y0: &[f64], yp0: &[f64]) -> Self {
assert_eq!(y0.len(), yp0.len(), "y0 and yp0 must have the same length");
Self {
y0: y0.to_vec(),
yp0: yp0.to_vec(),
t0: 0.0,
rtol: 1e-6,
atol: 1e-9,
max_steps: None,
}
}
pub fn rtol(mut self, rtol: f64) -> Self { self.rtol = rtol; self }
pub fn atol(mut self, atol: f64) -> Self { self.atol = atol; self }
pub fn t0(mut self, t0: f64) -> Self { self.t0 = t0; self }
pub fn max_steps(mut self, n: i64) -> Self { self.max_steps = Some(n); self }
pub fn build<F>(self, res: F) -> Result<IdaSolver<F>, SundialsError>
where
F: Fn(f64, &[f64], &[f64], &mut [f64]) -> Result<(), i32>,
{
let neq = self.y0.len();
let ctx = SunContext::new()?;
let mem = unsafe { sys::IDACreate(ctx.raw()) };
if mem.is_null() {
return Err(SundialsError::Memory("IDACreate"));
}
let y = NVector::from_slice(&self.y0, ctx.raw())?;
let yp = NVector::from_slice(&self.yp0, ctx.raw())?;
let user_data = Box::new(UserData { res, neq });
let ud_ptr = &*user_data as *const UserData<F> as *mut c_void;
check_flag(
unsafe { sys::IDAInit(mem, Some(res_trampoline::<F>), self.t0, y.as_ptr(), yp.as_ptr()) },
"IDA", "IDAInit",
)?;
check_flag(
unsafe { sys::IDASStolerances(mem, self.rtol, self.atol) },
"IDA", "IDASStolerances",
)?;
check_flag(
unsafe { sys::IDASetUserData(mem, ud_ptr) },
"IDA", "IDASetUserData",
)?;
let matrix = DenseMatrix::new(neq, neq, ctx.raw())?;
let ls = LinearSolver::dense(&y, &matrix, ctx.raw())?;
check_flag(
unsafe { sys::IDASetLinearSolver(mem, ls.ptr.as_ptr(), matrix.ptr.as_ptr()) },
"IDA", "IDASetLinearSolver",
)?;
if let Some(n) = self.max_steps {
check_flag(
unsafe { sys::IDASetMaxNumSteps(mem, n) },
"IDA", "IDASetMaxNumSteps",
)?;
}
Ok(IdaSolver {
mem,
y,
yp,
_matrix: matrix,
_ls: ls,
_user_data: user_data,
_ctx: ctx,
t: self.t0,
})
}
}
pub struct IdaSolver<F> {
mem: *mut c_void,
y: NVector,
yp: NVector,
_matrix: DenseMatrix,
_ls: LinearSolver,
_user_data: Box<UserData<F>>,
_ctx: SunContext,
t: f64,
}
impl<F> IdaSolver<F>
where
F: Fn(f64, &[f64], &[f64], &mut [f64]) -> Result<(), i32>,
{
pub fn new(y0: &[f64], yp0: &[f64]) -> IdaBuilder {
IdaBuilder::new(y0, yp0)
}
pub fn calc_ic(&mut self, tout1: f64) -> Result<(), SundialsError> {
check_flag(
unsafe { sys::IDACalcIC(self.mem, sys::IDA_YA_YDP_INIT as c_int, tout1) },
"IDA", "IDACalcIC",
)
}
pub fn step(&mut self, tout: f64) -> Result<(f64, &[f64], &[f64]), SundialsError> {
let mut t_out: sys::sunrealtype = 0.0;
let flag = unsafe {
sys::IDASolve(
self.mem, tout, &mut t_out,
self.y.as_ptr(), self.yp.as_ptr(),
sys::IDA_NORMAL as c_int,
)
};
check_flag(flag, "IDA", "IDASolve")?;
self.t = t_out;
Ok((t_out, self.y.as_slice(), self.yp.as_slice()))
}
pub fn reinit(&mut self, t0: f64, y0: &[f64], yp0: &[f64]) -> Result<(), SundialsError> {
self.y.as_mut_slice().copy_from_slice(y0);
self.yp.as_mut_slice().copy_from_slice(yp0);
check_flag(
unsafe { sys::IDAReInit(self.mem, t0, self.y.as_ptr(), self.yp.as_ptr()) },
"IDA", "IDAReInit",
)?;
self.t = t0;
Ok(())
}
pub fn t(&self) -> f64 { self.t }
pub fn y(&self) -> &[f64] { self.y.as_slice() }
pub fn yp(&self) -> &[f64] { self.yp.as_slice() }
}
impl<F> Drop for IdaSolver<F> {
fn drop(&mut self) {
unsafe { sys::IDAFree(&mut self.mem) };
}
}
unsafe impl<F: Send> Send for IdaSolver<F> {}