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,
};
#[derive(Debug, Clone, Copy)]
pub enum Method {
Adams,
BDF,
}
impl Method {
fn as_c_int(self) -> c_int {
match self {
Method::Adams => sys::CV_ADAMS as c_int,
Method::BDF => sys::CV_BDF as c_int,
}
}
}
#[derive(Debug, Clone)]
pub struct CvodeStats {
pub num_steps: i64,
pub num_rhs_evals: i64,
pub num_err_test_fails: i64,
pub last_order: i32,
pub current_time: f64,
}
struct UserData<F> {
rhs: F,
neq: usize,
}
unsafe extern "C" fn rhs_trampoline<F>(
t: sys::sunrealtype,
y: sys::N_Vector,
ydot: sys::N_Vector,
user_data: *mut c_void,
) -> c_int
where
F: Fn(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 ydot_s = std::slice::from_raw_parts_mut(sys::N_VGetArrayPointer(ydot), n);
match (ud.rhs)(t, y_s, ydot_s) {
Ok(()) => 0,
Err(flag) => flag,
}
}
enum AtolMode {
Scalar(f64),
Vector(Vec<f64>),
}
pub struct CvodeBuilder {
method: Method,
y0: Vec<f64>,
t0: f64,
rtol: f64,
atol: AtolMode,
max_steps: Option<i64>,
}
impl CvodeBuilder {
pub fn new(method: Method, y0: &[f64]) -> Self {
Self {
method,
y0: y0.to_vec(),
t0: 0.0,
rtol: 1e-6,
atol: AtolMode::Scalar(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 = AtolMode::Scalar(atol);
self
}
pub fn atol_vec(mut self, atol: Vec<f64>) -> Self {
self.atol = AtolMode::Vector(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, rhs: F) -> Result<CvodeSolver<F>, SundialsError>
where
F: Fn(f64, &[f64], &mut [f64]) -> Result<(), i32>,
{
let neq = self.y0.len();
let ctx = SunContext::new()?;
let mem = unsafe { sys::CVodeCreate(self.method.as_c_int(), ctx.raw()) };
if mem.is_null() {
return Err(SundialsError::Memory("CVodeCreate"));
}
let y = NVector::from_slice(&self.y0, ctx.raw())?;
let user_data = Box::new(UserData { rhs, neq });
let ud_ptr = &*user_data as *const UserData<F> as *mut c_void;
check_flag(
unsafe { sys::CVodeInit(mem, Some(rhs_trampoline::<F>), self.t0, y.as_ptr()) },
"CVODE", "CVodeInit",
)?;
match self.atol {
AtolMode::Scalar(atol) => {
check_flag(
unsafe { sys::CVodeSStolerances(mem, self.rtol, atol) },
"CVODE", "CVodeSStolerances",
)?;
}
AtolMode::Vector(ref v) => {
let atol_vec = NVector::from_slice(v, ctx.raw())?;
check_flag(
unsafe { sys::CVodeSVtolerances(mem, self.rtol, atol_vec.as_ptr()) },
"CVODE", "CVodeSVtolerances",
)?;
}
}
check_flag(
unsafe { sys::CVodeSetUserData(mem, ud_ptr) },
"CVODE", "CVodeSetUserData",
)?;
if let Some(n) = self.max_steps {
check_flag(
unsafe { sys::CVodeSetMaxNumSteps(mem, n) },
"CVODE", "CVodeSetMaxNumSteps",
)?;
}
let matrix = DenseMatrix::new(neq, neq, ctx.raw())?;
let ls = LinearSolver::dense(&y, &matrix, ctx.raw())?;
check_flag(
unsafe { sys::CVodeSetLinearSolver(mem, ls.ptr.as_ptr(), matrix.ptr.as_ptr()) },
"CVODE", "CVodeSetLinearSolver",
)?;
Ok(CvodeSolver {
mem,
y,
_matrix: matrix,
_ls: ls,
_user_data: user_data,
_ctx: ctx,
t: self.t0,
})
}
}
pub struct CvodeSolver<F> {
mem: *mut c_void,
y: NVector,
_matrix: DenseMatrix,
_ls: LinearSolver,
_user_data: Box<UserData<F>>,
_ctx: SunContext,
t: f64,
}
impl<F> CvodeSolver<F>
where
F: Fn(f64, &[f64], &mut [f64]) -> Result<(), i32>,
{
pub fn new(method: Method, y0: &[f64]) -> CvodeBuilder {
CvodeBuilder::new(method, y0)
}
pub fn step(&mut self, tout: f64) -> Result<(f64, &[f64]), SundialsError> {
let mut t_out: sys::sunrealtype = 0.0;
let flag = unsafe {
sys::CVode(self.mem, tout, self.y.as_ptr(), &mut t_out, sys::CV_NORMAL as c_int)
};
check_flag(flag, "CVODE", "CVode")?;
self.t = t_out;
Ok((t_out, self.y.as_slice()))
}
pub fn reinit(&mut self, t0: f64, y0: &[f64]) -> Result<(), SundialsError> {
self.y.as_mut_slice().copy_from_slice(y0);
check_flag(
unsafe { sys::CVodeReInit(self.mem, t0, self.y.as_ptr()) },
"CVODE", "CVodeReInit",
)?;
self.t = t0;
Ok(())
}
pub fn stats(&self) -> Result<CvodeStats, SundialsError> {
let mut nsteps: i64 = 0;
let mut nrhs: i64 = 0;
let mut netfails: i64 = 0;
let mut order: c_int = 0;
let mut tcur: f64 = 0.0;
check_flag(
unsafe { sys::CVodeGetNumSteps(self.mem, &mut nsteps) },
"CVODE", "CVodeGetNumSteps",
)?;
check_flag(
unsafe { sys::CVodeGetNumRhsEvals(self.mem, &mut nrhs) },
"CVODE", "CVodeGetNumRhsEvals",
)?;
check_flag(
unsafe { sys::CVodeGetNumErrTestFails(self.mem, &mut netfails) },
"CVODE", "CVodeGetNumErrTestFails",
)?;
check_flag(
unsafe { sys::CVodeGetLastOrder(self.mem, &mut order) },
"CVODE", "CVodeGetLastOrder",
)?;
check_flag(
unsafe { sys::CVodeGetCurrentTime(self.mem, &mut tcur) },
"CVODE", "CVodeGetCurrentTime",
)?;
Ok(CvodeStats {
num_steps: nsteps,
num_rhs_evals: nrhs,
num_err_test_fails: netfails,
last_order: order,
current_time: tcur,
})
}
pub fn t(&self) -> f64 { self.t }
pub fn y(&self) -> &[f64] { self.y.as_slice() }
}
impl<F> Drop for CvodeSolver<F> {
fn drop(&mut self) {
unsafe { sys::CVodeFree(&mut self.mem) };
}
}
unsafe impl<F: Send> Send for CvodeSolver<F> {}