use sim_kernel::{Args, Cx, Error, Result, Symbol, Value};
use sim_lib_numbers_cas::CasExpr;
use sim_lib_numbers_func::Func;
pub trait NumericPlugin: Send + Sync + 'static {
fn name(&self) -> Symbol;
fn kind(&self) -> NumericKind;
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum NumericKind {
Differentiator,
QuadratureFixed,
QuadratureAdaptive,
OdeFixed,
OdeAdaptive,
DaeImplicit,
}
#[derive(Clone, Debug)]
pub struct DiffOpts {
pub method: Symbol,
pub h: f64,
}
impl DiffOpts {
pub fn auto() -> Self {
Self {
method: Symbol::new("auto"),
h: 1.0e-6,
}
}
}
#[derive(Clone, Debug)]
pub struct QuadOpts {
pub method: Symbol,
pub n: Option<usize>,
pub tol: Option<f64>,
}
impl QuadOpts {
pub fn fixed_default() -> Self {
Self {
method: Symbol::new("auto"),
n: None,
tol: None,
}
}
pub fn adaptive_default() -> Self {
Self {
method: Symbol::new("auto"),
n: None,
tol: Some(1.0e-10),
}
}
}
#[derive(Clone, Debug)]
pub struct OdeOpts {
pub method: Symbol,
pub h: Option<f64>,
pub tol: Option<f64>,
pub max_steps: Option<usize>,
}
impl OdeOpts {
pub fn default_adaptive() -> Self {
Self {
method: Symbol::new("auto"),
h: None,
tol: Some(1.0e-8),
max_steps: None,
}
}
}
#[derive(Clone)]
pub struct NumericCallable {
value: Value,
vars: Vec<Symbol>,
body_cas: Option<CasExpr>,
differentiator_hint: Option<Symbol>,
}
enum FuncVarPolicy {
Exact,
UseFuncVarsWithArity,
}
impl NumericCallable {
pub fn unary(value: Value, var: Symbol) -> Result<Self> {
Self::from_value(
value,
vec![var],
FuncVarPolicy::Exact,
"numeric methods require the Func parameter to match the requested variable",
"numeric methods expect a Func or ordinary callable",
)
}
pub fn binary(value: Value, var: Symbol, y_var: Symbol) -> Result<Self> {
Self::from_value(
value,
vec![var, y_var],
FuncVarPolicy::Exact,
"ode-solve requires the Func parameters to match the requested x and y variables",
"ode-solve expects a Func or ordinary callable",
)
}
pub fn sampled_unary(value: Value, fallback_var: Symbol) -> Result<Self> {
Self::from_value(
value,
vec![fallback_var],
FuncVarPolicy::UseFuncVarsWithArity,
"numeric sampling requires a unary Func",
"numeric sampling expects a Func or ordinary callable",
)
}
pub fn sampled_binary(
value: Value,
fallback_var: Symbol,
fallback_y_var: Symbol,
) -> Result<Self> {
Self::from_value(
value,
vec![fallback_var, fallback_y_var],
FuncVarPolicy::UseFuncVarsWithArity,
"numeric sampling requires a binary Func",
"numeric sampling expects a Func or ordinary callable",
)
}
fn from_value(
value: Value,
requested_vars: Vec<Symbol>,
policy: FuncVarPolicy,
func_mismatch: &str,
callable_mismatch: &str,
) -> Result<Self> {
if let Some(func) = value.object().downcast_ref::<Func>() {
let vars = match policy {
FuncVarPolicy::Exact => {
if func.vars.as_slice() != requested_vars.as_slice() {
return Err(Error::Eval(func_mismatch.to_owned()));
}
requested_vars
}
FuncVarPolicy::UseFuncVarsWithArity => {
if func.vars.len() != requested_vars.len() {
return Err(Error::Eval(func_mismatch.to_owned()));
}
func.vars.clone()
}
};
let body_cas = func.body_cas().cloned();
let differentiator_hint = func.metadata.differentiator_hint.clone();
return Ok(Self {
value,
vars,
body_cas,
differentiator_hint,
});
}
value
.object()
.as_callable()
.ok_or_else(|| Error::Eval(callable_mismatch.to_owned()))?;
Ok(Self {
value,
vars: requested_vars,
body_cas: None,
differentiator_hint: None,
})
}
pub fn call(&self, cx: &mut Cx, args: Vec<Value>) -> Result<Value> {
cx.call_value(self.value.clone(), Args::new(args))
}
pub fn value(&self) -> &Value {
&self.value
}
pub fn as_func(&self) -> Option<&Func> {
self.value.object().downcast_ref::<Func>()
}
pub fn vars(&self) -> &[Symbol] {
&self.vars
}
pub fn body_cas(&self) -> Option<&CasExpr> {
self.body_cas.as_ref()
}
pub fn differentiator_hint(&self) -> Option<&Symbol> {
self.differentiator_hint.as_ref()
}
}
pub struct OdeProblem<'a> {
pub dy: &'a NumericCallable,
pub var: &'a Symbol,
pub y_var: &'a Symbol,
pub x0: &'a Value,
pub y0: &'a Value,
pub x_end: &'a Value,
}
pub trait Differentiator: NumericPlugin {
fn diff_at(
&self,
cx: &mut Cx,
f: &Func,
var: &Symbol,
point: &Value,
opt: DiffOpts,
) -> Result<Value>;
fn diff_callable_at(
&self,
cx: &mut Cx,
f: &NumericCallable,
var: &Symbol,
point: &Value,
opt: DiffOpts,
) -> Result<Value> {
let func = f
.as_func()
.ok_or_else(|| Error::Eval("differentiator requires a Func value".to_owned()))?;
self.diff_at(cx, func, var, point, opt)
}
}
pub trait Quadrature: NumericPlugin {
fn integrate(
&self,
cx: &mut Cx,
f: &NumericCallable,
var: &Symbol,
lo: &Value,
hi: &Value,
opt: QuadOpts,
) -> Result<Value>;
}
pub trait OdeSolver: NumericPlugin {
fn solve(
&self,
cx: &mut Cx,
problem: OdeProblem<'_>,
opt: OdeOpts,
) -> Result<Vec<(Value, Value)>>;
}