pub mod composition;
pub mod euler;
pub mod leapfrog;
pub mod potential;
pub mod runge_kutta;
pub use composition::CompositionMethod;
pub use euler::{symplectic_euler, symplectic_euler_a, symplectic_euler_b};
pub use leapfrog::{position_verlet, velocity_verlet, StormerVerlet};
pub use potential::{HamiltonianSystem, SeparableHamiltonian};
pub use runge_kutta::{GaussLegendre4, GaussLegendre6};
use crate::common::IntegrateFloat;
use crate::error::{IntegrateError, IntegrateResult};
use scirs2_core::ndarray::Array1;
#[derive(Debug, Clone)]
pub struct SymplecticResult<F: IntegrateFloat> {
pub t: Vec<F>,
pub q: Vec<Array1<F>>,
pub p: Vec<Array1<F>>,
pub total_time: F,
pub steps: usize,
pub n_evaluations: usize,
pub energy_relative_error: Option<F>,
}
pub trait SymplecticIntegrator<F: IntegrateFloat> {
fn step(
&self,
system: &dyn HamiltonianFn<F>,
t: F,
q: &Array1<F>,
p: &Array1<F>,
dt: F,
) -> IntegrateResult<(Array1<F>, Array1<F>)>;
fn integrate(
&self,
system: &dyn HamiltonianFn<F>,
t0: F,
tf: F,
dt: F,
q0: Array1<F>,
p0: Array1<F>,
) -> IntegrateResult<SymplecticResult<F>> {
if dt <= F::zero() {
return Err(IntegrateError::ValueError(
"Step size must be positive".into(),
));
}
if q0.len() != p0.len() {
return Err(IntegrateError::ValueError(
"Position and momentum vectors must have the same length".into(),
));
}
let t_span = tf - t0;
let n_steps = (t_span / dt).ceil().to_f64().expect("Operation failed") as usize;
let actual_dt = t_span / F::from_usize(n_steps).expect("Operation failed");
let mut t = Vec::with_capacity(n_steps + 1);
let mut q = Vec::with_capacity(n_steps + 1);
let mut p = Vec::with_capacity(n_steps + 1);
t.push(t0);
q.push(q0.clone());
p.push(p0.clone());
let mut curr_t = t0;
let mut curr_q = q0;
let mut curr_p = p0;
let mut n_evals = 0;
for _ in 0..n_steps {
let (next_q, next_p) = self.step(system, curr_t, &curr_q, &curr_p, actual_dt)?;
n_evals += 2;
curr_t += actual_dt;
t.push(curr_t);
q.push(next_q.clone());
p.push(next_p.clone());
curr_q = next_q;
curr_p = next_p;
}
let energy_error = if let Some(hamiltonian) = system.hamiltonian() {
let initial_energy = hamiltonian(t[0], &q[0], &p[0])?;
let final_energy = hamiltonian(t[t.len() - 1], &q[q.len() - 1], &p[p.len() - 1])?;
if initial_energy.abs() > F::from_f64(1e-10).expect("Operation failed") {
Some((final_energy - initial_energy).abs() / initial_energy.abs())
} else {
Some((final_energy - initial_energy).abs())
}
} else {
None
};
Ok(SymplecticResult {
t,
q,
p,
total_time: tf - t0,
steps: n_steps,
n_evaluations: n_evals,
energy_relative_error: energy_error,
})
}
}
pub type HamiltonianFnBox<'a, F> =
Box<dyn Fn(F, &Array1<F>, &Array1<F>) -> IntegrateResult<F> + 'a>;
pub trait HamiltonianFn<F: IntegrateFloat> {
fn dq_dt(&self, t: F, q: &Array1<F>, p: &Array1<F>) -> IntegrateResult<Array1<F>>;
fn dp_dt(&self, t: F, q: &Array1<F>, p: &Array1<F>) -> IntegrateResult<Array1<F>>;
fn hamiltonian(&self) -> Option<HamiltonianFnBox<'_, F>> {
None
}
}