use nalgebra::{DMatrix, DVector, SMatrix, SVector};
use crate::integrators::config::IntegratorConfig;
use crate::math::jacobian::{DJacobianProvider, SJacobianProvider};
use crate::math::sensitivity::{DSensitivityProvider, SSensitivityProvider};
pub type SStateDynamics<const S: usize, const P: usize> =
Box<dyn Fn(f64, &SVector<f64, S>, Option<&SVector<f64, P>>) -> SVector<f64, S> + Send + Sync>;
pub type SVariationalMatrix<const S: usize, const P: usize> =
Option<Box<dyn SJacobianProvider<S, P> + Send + Sync>>;
pub type SControlInput<const S: usize, const P: usize> = Option<
Box<dyn Fn(f64, &SVector<f64, S>, Option<&SVector<f64, P>>) -> SVector<f64, S> + Send + Sync>,
>;
pub type DStateDynamics =
Box<dyn Fn(f64, &DVector<f64>, Option<&DVector<f64>>) -> DVector<f64> + Send + Sync>;
pub type DVariationalMatrix = Option<Box<dyn DJacobianProvider>>;
pub type DControlInput =
Option<Box<dyn Fn(f64, &DVector<f64>, Option<&DVector<f64>>) -> DVector<f64> + Send + Sync>>;
pub type SSensitivityDynamics<const S: usize, const P: usize> =
Box<dyn Fn(f64, &SVector<f64, S>, &SVector<f64, P>) -> SVector<f64, S> + Send + Sync>;
pub type DSensitivityDynamics =
Box<dyn Fn(f64, &DVector<f64>, &DVector<f64>) -> DVector<f64> + Send + Sync>;
pub use crate::math::sensitivity::{
DAnalyticSensitivity, DNumericalSensitivity, SAnalyticSensitivity, SNumericalSensitivity,
};
pub type SSensitivity<const S: usize, const P: usize> =
Option<Box<dyn SSensitivityProvider<S, P> + Send + Sync>>;
pub type DSensitivity = Option<Box<dyn DSensitivityProvider>>;
pub trait DIntegrator: Send + Sync {
fn dimension(&self) -> usize;
fn config(&self) -> &IntegratorConfig;
fn reset_cache(&self) {}
fn step(
&self,
t: f64,
state: DVector<f64>,
params: Option<&DVector<f64>>,
dt: Option<f64>,
) -> DIntegratorStepResult;
fn step_with_varmat(
&self,
t: f64,
state: DVector<f64>,
params: Option<&DVector<f64>>,
phi: DMatrix<f64>,
dt: Option<f64>,
) -> DIntegratorStepResult;
fn step_with_sensmat(
&self,
t: f64,
state: DVector<f64>,
sens: DMatrix<f64>,
params: &DVector<f64>,
dt: Option<f64>,
) -> DIntegratorStepResult;
fn step_with_varmat_sensmat(
&self,
t: f64,
state: DVector<f64>,
phi: DMatrix<f64>,
sens: DMatrix<f64>,
params: &DVector<f64>,
dt: Option<f64>,
) -> DIntegratorStepResult;
}
pub trait DIntegratorConstructor: DIntegrator + Sized {
fn with_config(
dimension: usize,
f: DStateDynamics,
varmat: DVariationalMatrix,
sensmat: DSensitivity,
control: DControlInput,
config: IntegratorConfig,
) -> Self;
}
pub trait SIntegrator<const S: usize, const P: usize>: Send + Sync {
fn config(&self) -> &IntegratorConfig;
fn reset_cache(&self) {}
fn step(
&self,
t: f64,
state: SVector<f64, S>,
params: Option<&SVector<f64, P>>,
dt: Option<f64>,
) -> SIntegratorStepResult<S, P>;
fn step_with_varmat(
&self,
t: f64,
state: SVector<f64, S>,
params: Option<&SVector<f64, P>>,
phi: SMatrix<f64, S, S>,
dt: Option<f64>,
) -> SIntegratorStepResult<S, P>;
fn step_with_sensmat(
&self,
t: f64,
state: SVector<f64, S>,
sens: SMatrix<f64, S, P>,
params: &SVector<f64, P>,
dt: Option<f64>,
) -> SIntegratorStepResult<S, P>;
fn step_with_varmat_sensmat(
&self,
t: f64,
state: SVector<f64, S>,
phi: SMatrix<f64, S, S>,
sens: SMatrix<f64, S, P>,
params: &SVector<f64, P>,
dt: Option<f64>,
) -> SIntegratorStepResult<S, P>;
}
pub trait SIntegratorConstructor<const S: usize, const P: usize>:
SIntegrator<S, P> + Sized
{
fn new(
f: SStateDynamics<S, P>,
varmat: SVariationalMatrix<S, P>,
sensmat: SSensitivity<S, P>,
control: SControlInput<S, P>,
) -> Self;
fn with_config(
f: SStateDynamics<S, P>,
varmat: SVariationalMatrix<S, P>,
sensmat: SSensitivity<S, P>,
control: SControlInput<S, P>,
config: IntegratorConfig,
) -> Self;
}
#[derive(Debug, Clone)]
pub struct SIntegratorStepResult<const S: usize, const P: usize> {
pub state: SVector<f64, S>,
pub phi: Option<SMatrix<f64, S, S>>,
pub sens: Option<SMatrix<f64, S, P>>,
pub dt_used: f64,
pub error_estimate: Option<f64>,
pub dt_next: f64,
}
#[derive(Debug, Clone)]
pub struct DIntegratorStepResult {
pub state: DVector<f64>,
pub phi: Option<DMatrix<f64>>,
pub sens: Option<DMatrix<f64>>,
pub dt_used: f64,
pub error_estimate: Option<f64>,
pub dt_next: f64,
}
pub(crate) fn compute_next_step_size(
error: f64,
h: f64,
order_accept: f64,
config: &IntegratorConfig,
) -> f64 {
if error > 0.0 {
let raw_scale = (1.0 / error).powf(order_accept);
let scale = config
.step_safety_factor
.map_or(raw_scale, |safety| safety * raw_scale);
let h_sign = h.signum();
let h_abs = h.abs();
let mut h_next = h_abs * scale;
if let Some(min_scale) = config.min_step_scale_factor {
h_next = h_next.max(min_scale * h_abs);
}
if let Some(max_scale) = config.max_step_scale_factor {
h_next = h_next.min(max_scale * h_abs);
}
if let Some(max_step) = config.max_step {
h_next = h_next.min(max_step);
}
if let Some(min_step) = config.min_step {
h_next = h_next.max(min_step);
}
h_sign * h_next
} else {
let h_sign = h.signum();
let h_abs = h.abs();
let h_next = if let Some(max_scale) = config.max_step_scale_factor {
max_scale * h_abs
} else {
10.0 * h_abs };
let h_next = config.max_step.map_or(h_next, |max| h_next.min(max));
h_sign * h_next
}
}
pub(crate) fn compute_reduced_step_size(
error: f64,
h: f64,
order_reject: f64,
config: &IntegratorConfig,
) -> f64 {
let raw_scale = (1.0 / error).powf(order_reject);
let scale = config
.step_safety_factor
.map_or(raw_scale, |safety| safety * raw_scale);
let h_sign = h.signum();
let h_abs = h.abs();
let mut h_new = h_abs * scale;
if let Some(min_scale) = config.min_step_scale_factor {
h_new = h_new.max(min_scale * h_abs);
}
if let Some(min_step) = config.min_step {
h_new = h_new.max(min_step);
}
h_sign * h_new
}
pub(crate) fn compute_normalized_error(
error_vec: &DVector<f64>,
state_high: &DVector<f64>,
state_orig: &DVector<f64>,
config: &IntegratorConfig,
) -> f64 {
let mut error: f64 = 0.0;
for i in 0..error_vec.len() {
let tol = config.abs_tol + config.rel_tol * state_high[i].abs().max(state_orig[i].abs());
error = error.max((error_vec[i] / tol).abs());
}
error
}
pub(crate) fn compute_normalized_error_s<const S: usize>(
error_vec: &SVector<f64, S>,
state_high: &SVector<f64, S>,
state_orig: &SVector<f64, S>,
config: &IntegratorConfig,
) -> f64 {
let mut error: f64 = 0.0;
for i in 0..S {
let tol = config.abs_tol + config.rel_tol * state_high[i].abs().max(state_orig[i].abs());
error = error.max((error_vec[i] / tol).abs());
}
error
}
use crate::propagators::IntegratorMethod;
pub fn create_dintegrator(
method: IntegratorMethod,
dimension: usize,
dynamics: DStateDynamics,
varmat: DVariationalMatrix,
sensmat: DSensitivity,
control: DControlInput,
config: IntegratorConfig,
) -> Box<dyn DIntegrator> {
use crate::integrators::{
DormandPrince54DIntegrator, RK4DIntegrator, RKF45DIntegrator, RKN1210DIntegrator,
};
match method {
IntegratorMethod::RK4 => Box::new(RK4DIntegrator::with_config(
dimension, dynamics, varmat, sensmat, control, config,
)),
IntegratorMethod::RKF45 => Box::new(RKF45DIntegrator::with_config(
dimension, dynamics, varmat, sensmat, control, config,
)),
IntegratorMethod::DP54 => Box::new(DormandPrince54DIntegrator::with_config(
dimension, dynamics, varmat, sensmat, control, config,
)),
IntegratorMethod::RKN1210 => Box::new(RKN1210DIntegrator::with_config(
dimension, dynamics, varmat, sensmat, control, config,
)),
}
}
pub fn get_step_size(dt: Option<f64>, config: &IntegratorConfig) -> f64 {
match dt {
Some(step) => step,
None => config.fixed_step_size.unwrap_or_else(|| {
panic!(
"Fixed-step integrator requires a step size. \
Either provide dt to step() or set fixed_step_size in IntegratorConfig."
)
}),
}
}