use super::FixedPoint;
use super::FixedVector;
use super::linalg::{round_to_storage, upscale_to_compute, ComputeStorage};
use crate::fixed_point::universal::fasc::stack_evaluator::BinaryStorage;
use crate::fixed_point::universal::fasc::stack_evaluator::compute::{
compute_add, compute_mul_div_int, compute_multiply, compute_negate, compute_subtract,
};
use crate::fixed_point::core_types::errors::OverflowDetected;
pub trait OdeSystem {
fn eval(&self, t: FixedPoint, x: &FixedVector) -> FixedVector;
}
pub struct OdeFn<F: Fn(FixedPoint, &FixedVector) -> FixedVector> {
pub f: F,
}
impl<F: Fn(FixedPoint, &FixedVector) -> FixedVector> OdeSystem for OdeFn<F> {
fn eval(&self, t: FixedPoint, x: &FixedVector) -> FixedVector {
(self.f)(t, x)
}
}
pub fn ode_fn<F: Fn(FixedPoint, &FixedVector) -> FixedVector>(f: F) -> OdeFn<F> {
OdeFn { f }
}
#[derive(Clone, Debug)]
pub struct OdePoint {
pub t: FixedPoint,
pub x: FixedVector,
}
pub fn rk4_step<S: OdeSystem>(
sys: &S,
t: FixedPoint,
x: &FixedVector,
h: FixedPoint,
) -> FixedVector {
let x_c = state_to_compute(x);
state_to_storage(&rk4_step_compute(sys, upscale_to_compute(t.raw()), &x_c, upscale_to_compute(h.raw())))
}
pub(crate) fn rk4_step_compute<S: OdeSystem>(
sys: &S,
t: ComputeStorage,
x: &[ComputeStorage],
h: ComputeStorage,
) -> Vec<ComputeStorage> {
let t_s = time_to_storage(t);
let t_half = time_to_storage(compute_add(t, scale(h, 1, 2)));
let t_full = time_to_storage(compute_add(t, h));
let k1 = sys.eval(t_s, &state_to_storage(x));
let k2 = sys.eval(t_half, &state_to_storage(&stage(x, h, &[(1, 2, &k1)])));
let k3 = sys.eval(t_half, &state_to_storage(&stage(x, h, &[(1, 2, &k2)])));
let k4 = sys.eval(t_full, &state_to_storage(&stage(x, h, &[(1, 1, &k3)])));
stage(x, h, &[(1, 6, &k1), (2, 6, &k2), (2, 6, &k3), (1, 6, &k4)])
}
fn scale(c: ComputeStorage, num: i64, den: i64) -> ComputeStorage {
compute_mul_div_int(c, num, den).expect("ode: stage term exceeds the compute tier")
}
fn stage(x: &[ComputeStorage], h: ComputeStorage, terms: &[(i64, i64, &FixedVector)]) -> Vec<ComputeStorage> {
(0..x.len()).map(|i| {
let mut acc = upscale_to_compute(FixedPoint::ZERO.raw());
for &(num, den, k) in terms {
acc = compute_add(acc, scale(upscale_to_compute(k[i].raw()), num, den));
}
compute_add(x[i], compute_multiply(acc, h))
}).collect()
}
pub(crate) fn state_to_compute(x: &FixedVector) -> Vec<ComputeStorage> {
(0..x.len()).map(|i| upscale_to_compute(x[i].raw())).collect()
}
pub(crate) fn state_to_storage(x: &[ComputeStorage]) -> FixedVector {
FixedVector::from_slice(&x.iter().map(|&v| FixedPoint::from_raw(round_to_storage(v))).collect::<Vec<_>>())
}
fn time_to_storage(t: ComputeStorage) -> FixedPoint {
FixedPoint::from_raw(round_to_storage(t))
}
pub fn rk4_integrate<S: OdeSystem>(
sys: &S,
x0: &FixedVector,
t0: FixedPoint,
t_end: FixedPoint,
h: FixedPoint,
) -> Vec<OdePoint> {
let mut trajectory = Vec::new();
let mut t = t0;
let mut x = state_to_compute(x0);
trajectory.push(OdePoint { t, x: x0.clone() });
while t < t_end {
let remaining = t_end - t;
let step = if remaining < h { remaining } else { h };
if step.is_zero() { break; }
x = rk4_step_compute(sys, upscale_to_compute(t.raw()), &x, upscale_to_compute(step.raw()));
t = t + step;
trajectory.push(OdePoint { t, x: state_to_storage(&x) });
}
trajectory
}
pub struct Rk45Config {
pub tol: FixedPoint,
pub h_init: FixedPoint,
pub h_min: FixedPoint,
pub h_max: FixedPoint,
pub max_steps: usize,
}
impl Rk45Config {
pub fn new(tol: FixedPoint, h_init: FixedPoint) -> Self {
let h_min = FixedPoint::from_raw(quantum_raw());
Self {
tol,
h_init,
h_min,
h_max: h_init.mul_count(16),
max_steps: 100_000,
}
}
}
pub fn rk45_integrate<S: OdeSystem>(
sys: &S,
x0: &FixedVector,
t0: FixedPoint,
t_end: FixedPoint,
config: &Rk45Config,
) -> Result<(Vec<OdePoint>, usize), OverflowDetected> {
let mut trajectory = Vec::new();
let mut t = t0;
let mut x = state_to_compute(x0);
let mut h = config.h_init;
let mut rejected = 0usize;
let tol = upscale_to_compute(config.tol.raw());
trajectory.push(OdePoint { t, x: x0.clone() });
for _ in 0..config.max_steps {
if t >= t_end { break; }
let remaining = t_end - t;
let step = if remaining < h { remaining } else { h };
if step.is_zero() { break; }
let (x4, x5) = dp45_pair(sys, t, &x, step);
let err = inf_norm_diff(&x5, &x4);
if err > tol {
h = h_half(h);
if h < config.h_min { h = config.h_min; }
rejected += 1;
continue;
}
x = x5;
t = t + step;
trajectory.push(OdePoint { t, x: state_to_storage(&x) });
let err32 = compute_mul_div_int(err, 32, 1);
if matches!(err32, Ok(e) if e < tol) {
h = h + h; if h > config.h_max { h = config.h_max; }
}
}
Ok((trajectory, rejected))
}
fn dp45_pair<S: OdeSystem>(
sys: &S,
t: FixedPoint,
x: &[ComputeStorage],
h: FixedPoint,
) -> (Vec<ComputeStorage>, Vec<ComputeStorage>) {
let t_c = upscale_to_compute(t.raw());
let h_c = upscale_to_compute(h.raw());
let at = |num: i64, den: i64| time_to_storage(compute_add(t_c, scale(h_c, num, den)));
let st = |terms: &[(i64, i64, &FixedVector)]| state_to_storage(&stage(x, h_c, terms));
let k1 = sys.eval(t, &state_to_storage(x));
let k2 = sys.eval(at(1, 5), &st(&[(1, 5, &k1)]));
let k3 = sys.eval(at(3, 10), &st(&[(3, 40, &k1), (9, 40, &k2)]));
let k4 = sys.eval(at(4, 5), &st(&[(44, 45, &k1), (-56, 15, &k2), (32, 9, &k3)]));
let k5 = sys.eval(at(8, 9), &st(&[
(19372, 6561, &k1), (-25360, 2187, &k2), (64448, 6561, &k3), (-212, 729, &k4),
]));
let k6 = sys.eval(t + h, &st(&[
(9017, 3168, &k1), (-355, 33, &k2), (46732, 5247, &k3), (49, 176, &k4), (-5103, 18656, &k5),
]));
let x5 = stage(x, h_c, &[
(35, 384, &k1), (500, 1113, &k3), (125, 192, &k4), (-2187, 6784, &k5), (11, 84, &k6),
]);
let k7 = sys.eval(t + h, &state_to_storage(&x5));
let x4 = stage(x, h_c, &[
(5179, 57600, &k1), (7571, 16695, &k3), (393, 640, &k4), (-92097, 339200, &k5),
(187, 2100, &k6), (1, 40, &k7),
]);
(x4, x5)
}
pub trait HamiltonianSystem {
fn force(&self, q: &FixedVector, p: &FixedVector) -> FixedVector;
fn velocity(&self, q: &FixedVector, p: &FixedVector) -> FixedVector;
fn energy(&self, q: &FixedVector, p: &FixedVector) -> FixedPoint;
}
#[derive(Clone, Debug)]
pub struct HamiltonianPoint {
pub t: FixedPoint,
pub q: FixedVector,
pub p: FixedVector,
pub energy: FixedPoint,
}
pub fn verlet_step<H: HamiltonianSystem>(
sys: &H,
q: &FixedVector,
p: &FixedVector,
h: FixedPoint,
) -> (FixedVector, FixedVector) {
let (q_new, p_new) = verlet_step_compute(sys, &state_to_compute(q), &state_to_compute(p), upscale_to_compute(h.raw()));
(state_to_storage(&q_new), state_to_storage(&p_new))
}
fn verlet_step_compute<H: HamiltonianSystem>(
sys: &H,
q: &[ComputeStorage],
p: &[ComputeStorage],
h: ComputeStorage,
) -> (Vec<ComputeStorage>, Vec<ComputeStorage>) {
let q_s = state_to_storage(q);
let f0 = sys.force(&q_s, &state_to_storage(p));
let p_half = stage(p, h, &[(1, 2, &f0)]);
let p_half_s = state_to_storage(&p_half);
let v_half = sys.velocity(&q_s, &p_half_s);
let q_new = stage(q, h, &[(1, 1, &v_half)]);
let f1 = sys.force(&state_to_storage(&q_new), &p_half_s);
let p_new = stage(&p_half, h, &[(1, 2, &f1)]);
(q_new, p_new)
}
pub fn verlet_integrate<H: HamiltonianSystem>(
sys: &H,
q0: &FixedVector,
p0: &FixedVector,
t0: FixedPoint,
t_end: FixedPoint,
h: FixedPoint,
) -> Vec<HamiltonianPoint> {
let mut trajectory = Vec::new();
let mut t = t0;
let mut q = state_to_compute(q0);
let mut p = state_to_compute(p0);
trajectory.push(HamiltonianPoint {
t,
q: q0.clone(),
p: p0.clone(),
energy: sys.energy(q0, p0),
});
while t < t_end {
let remaining = t_end - t;
let step = if remaining < h { remaining } else { h };
if step.is_zero() { break; }
let (q_new, p_new) = verlet_step_compute(sys, &q, &p, upscale_to_compute(step.raw()));
q = q_new;
p = p_new;
t = t + step;
let (q_s, p_s) = (state_to_storage(&q), state_to_storage(&p));
let energy = sys.energy(&q_s, &p_s);
trajectory.push(HamiltonianPoint { t, q: q_s, p: p_s, energy });
}
trajectory
}
pub fn monitor_invariant<F: Fn(&FixedVector) -> FixedPoint>(
invariant: F,
trajectory: &[OdePoint],
) -> (FixedPoint, Vec<FixedPoint>) {
if trajectory.is_empty() {
return (FixedPoint::ZERO, Vec::new());
}
let c0 = invariant(&trajectory[0].x);
let mut max_drift = FixedPoint::ZERO;
let mut drifts = Vec::with_capacity(trajectory.len());
for point in trajectory {
let ci = invariant(&point.x);
let drift = (ci - c0).abs();
if drift > max_drift { max_drift = drift; }
drifts.push(drift);
}
(max_drift, drifts)
}
#[inline]
fn h_half(h: FixedPoint) -> FixedPoint {
#[cfg(any(table_format = "q32_32", table_format = "q16_16"))]
{ FixedPoint::from_raw(h.raw() >> 1) }
#[cfg(table_format = "q64_64")]
{ FixedPoint::from_raw(h.raw() >> 1u32) }
#[cfg(table_format = "q128_128")]
{ FixedPoint::from_raw(h.raw() >> 1u32) }
#[cfg(table_format = "q256_256")]
{ FixedPoint::from_raw(h.raw() >> 1usize) }
}
fn inf_norm_diff(a: &[ComputeStorage], b: &[ComputeStorage]) -> ComputeStorage {
assert_eq!(a.len(), b.len());
let zero = upscale_to_compute(FixedPoint::ZERO.raw());
let mut max_val = zero;
for i in 0..a.len() {
let d = compute_subtract(a[i], b[i]);
let d = if d < zero { compute_negate(d) } else { d };
if d > max_val { max_val = d; }
}
max_val
}
#[cfg(table_format = "q32_32")]
fn quantum_raw() -> BinaryStorage { 1i64 }
#[cfg(table_format = "q16_16")]
fn quantum_raw() -> BinaryStorage { 1i32 }
#[cfg(table_format = "q64_64")]
fn quantum_raw() -> BinaryStorage { 1i128 }
#[cfg(table_format = "q128_128")]
fn quantum_raw() -> BinaryStorage {
use crate::fixed_point::I256;
I256::from_i128(1)
}
#[cfg(table_format = "q256_256")]
fn quantum_raw() -> BinaryStorage {
use crate::fixed_point::I512;
I512::from_i128(1)
}