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 IdasBuilder {
y0: Vec<f64>,
yp0: Vec<f64>,
t0: f64,
rtol: f64,
atol: f64,
max_steps: Option<i64>,
p: Box<[f64]>,
s0: Vec<Vec<f64>>,
}
impl IdasBuilder {
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,
p: Box::new([]),
s0: Vec::new(),
}
}
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 with_forward_sensitivity(mut self, p: Vec<f64>, s0: Vec<Vec<f64>>) -> Self {
self.p = p.into_boxed_slice(); self.s0 = s0;
self
}
pub fn params_ptr(&self) -> *const f64 {
self.p.as_ptr()
}
pub fn build<F>(self, res: F) -> Result<IdasSolver<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 (IDAS)"));
}
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()) },
"IDAS", "IDAInit",
)?;
check_flag(
unsafe { sys::IDASStolerances(mem, self.rtol, self.atol) },
"IDAS", "IDASStolerances",
)?;
check_flag(
unsafe { sys::IDASetUserData(mem, ud_ptr) },
"IDAS", "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()) },
"IDAS", "IDASetLinearSolver",
)?;
if let Some(n) = self.max_steps {
check_flag(
unsafe { sys::IDASetMaxNumSteps(mem, n) },
"IDAS", "IDASetMaxNumSteps",
)?;
}
let mut sens_vecs: Vec<NVector> = Vec::new();
let mut sens_vecs_yp: Vec<NVector> = Vec::new();
let ns = self.p.len();
if ns > 0 {
for s in &self.s0 {
sens_vecs.push(NVector::from_slice(s, ctx.raw())?);
sens_vecs_yp.push(NVector::new(neq, ctx.raw())?);
}
let mut sv_ptrs: Vec<sys::N_Vector> =
sens_vecs.iter().map(|v| v.as_ptr()).collect();
let mut svp_ptrs: Vec<sys::N_Vector> =
sens_vecs_yp.iter().map(|v| v.as_ptr()).collect();
check_flag(
unsafe {
sys::IDASensInit(
mem, ns as c_int, sys::IDA_SIMULTANEOUS as c_int,
None, sv_ptrs.as_mut_ptr(), svp_ptrs.as_mut_ptr(),
)
},
"IDAS", "IDASensInit",
)?;
check_flag(
unsafe { sys::IDASensEEtolerances(mem) },
"IDAS", "IDASensEEtolerances",
)?;
}
let p = self.p;
let solver = IdasSolver {
mem,
y,
yp,
_matrix: matrix,
_ls: ls,
_user_data: user_data,
sens_vecs,
sens_vecs_yp,
p,
_ctx: ctx,
t: self.t0,
};
if solver.ns() > 0 {
check_flag(
unsafe {
sys::IDASetSensParams(
solver.mem,
solver.p.as_ptr() as *mut f64,
std::ptr::null_mut(),
std::ptr::null_mut(),
)
},
"IDAS", "IDASetSensParams",
)?;
}
Ok(solver)
}
}
pub struct IdasSolver<F> {
mem: *mut c_void,
y: NVector,
yp: NVector,
_matrix: DenseMatrix,
_ls: LinearSolver,
_user_data: Box<UserData<F>>,
sens_vecs: Vec<NVector>,
#[allow(dead_code)]
sens_vecs_yp: Vec<NVector>,
p: Box<[f64]>,
_ctx: SunContext,
t: f64,
}
impl<F> IdasSolver<F> {
fn ns(&self) -> usize { self.sens_vecs.len() }
}
impl<F> IdasSolver<F>
where
F: Fn(f64, &[f64], &[f64], &mut [f64]) -> Result<(), i32>,
{
pub fn new(y0: &[f64], yp0: &[f64]) -> IdasBuilder {
IdasBuilder::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) },
"IDAS", "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, "IDAS", "IDASolve")?;
self.t = t_out;
Ok((t_out, self.y.as_slice(), self.yp.as_slice()))
}
pub fn sensitivities(&mut self) -> Result<Vec<&[f64]>, SundialsError> {
if self.sens_vecs.is_empty() {
return Ok(Vec::new());
}
let mut sv_ptrs: Vec<sys::N_Vector> =
self.sens_vecs.iter().map(|v| v.as_ptr()).collect();
check_flag(
unsafe { sys::IDAGetSens(self.mem, &mut self.t, sv_ptrs.as_mut_ptr()) },
"IDAS", "IDAGetSens",
)?;
Ok(self.sens_vecs.iter().map(|v| v.as_slice()).collect())
}
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 IdasSolver<F> {
fn drop(&mut self) {
unsafe { sys::IDAFree(&mut self.mem) };
}
}
unsafe impl<F: Send> Send for IdasSolver<F> {}