use crate::common::IntegrateFloat;
use crate::error::IntegrateResult;
use crate::symplectic::{HamiltonianFn, SymplecticIntegrator};
use scirs2_core::ndarray::Array1;
use std::marker::PhantomData;
#[derive(Debug, Clone)]
pub struct SymplecticEulerA<F: IntegrateFloat> {
_marker: PhantomData<F>,
}
impl<F: IntegrateFloat> SymplecticEulerA<F> {
pub fn new() -> Self {
SymplecticEulerA {
_marker: PhantomData,
}
}
}
impl<F: IntegrateFloat> Default for SymplecticEulerA<F> {
fn default() -> Self {
Self::new()
}
}
impl<F: IntegrateFloat> SymplecticIntegrator<F> for SymplecticEulerA<F> {
fn step(
&self,
system: &dyn HamiltonianFn<F>,
t: F,
q: &Array1<F>,
p: &Array1<F>,
dt: F,
) -> IntegrateResult<(Array1<F>, Array1<F>)> {
let dq = system.dq_dt(t, q, p)?;
let q_new = q + &(&dq * dt);
let dp = system.dp_dt(t, &q_new, p)?;
let p_new = p + &(&dp * dt);
Ok((q_new, p_new))
}
}
#[derive(Debug, Clone)]
pub struct SymplecticEulerB<F: IntegrateFloat> {
_marker: PhantomData<F>,
}
impl<F: IntegrateFloat> SymplecticEulerB<F> {
pub fn new() -> Self {
SymplecticEulerB {
_marker: PhantomData,
}
}
}
impl<F: IntegrateFloat> Default for SymplecticEulerB<F> {
fn default() -> Self {
Self::new()
}
}
impl<F: IntegrateFloat> SymplecticIntegrator<F> for SymplecticEulerB<F> {
fn step(
&self,
system: &dyn HamiltonianFn<F>,
t: F,
q: &Array1<F>,
p: &Array1<F>,
dt: F,
) -> IntegrateResult<(Array1<F>, Array1<F>)> {
let dp = system.dp_dt(t, q, p)?;
let p_new = p + &(&dp * dt);
let dq = system.dq_dt(t, q, &p_new)?;
let q_new = q + &(&dq * dt);
Ok((q_new, p_new))
}
}
#[allow(dead_code)]
pub fn symplectic_euler<F: IntegrateFloat>(
system: &dyn HamiltonianFn<F>,
t: F,
q: &Array1<F>,
p: &Array1<F>,
dt: F,
) -> IntegrateResult<(Array1<F>, Array1<F>)> {
SymplecticEulerA::new().step(system, t, q, p, dt)
}
#[allow(dead_code)]
pub fn symplectic_euler_a<F: IntegrateFloat>(
system: &dyn HamiltonianFn<F>,
t: F,
q: &Array1<F>,
p: &Array1<F>,
dt: F,
) -> IntegrateResult<(Array1<F>, Array1<F>)> {
SymplecticEulerA::new().step(system, t, q, p, dt)
}
#[allow(dead_code)]
pub fn symplectic_euler_b<F: IntegrateFloat>(
system: &dyn HamiltonianFn<F>,
t: F,
q: &Array1<F>,
p: &Array1<F>,
dt: F,
) -> IntegrateResult<(Array1<F>, Array1<F>)> {
SymplecticEulerB::new().step(system, t, q, p, dt)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::symplectic::potential::SeparableHamiltonian;
use scirs2_core::ndarray::array;
#[test]
fn test_symplectic_euler() {
let system = SeparableHamiltonian::new(
|_t, p| -> f64 { 0.5 * p.dot(p) },
|_t, q| -> f64 { 0.5 * q.dot(q) },
);
let q0 = array![1.0];
let p0 = array![0.0];
let t0 = 0.0;
let dt = 0.1;
let (q1_a, p1_a) = symplectic_euler_a(&system, t0, &q0, &p0, dt).expect("Operation failed");
let (q1_b, p1_b) = symplectic_euler_b(&system, t0, &q0, &p0, dt).expect("Operation failed");
assert!((q1_a[0] - 1.0).abs() < 1e-12);
assert!((p1_a[0] + 0.1).abs() < 1e-12);
assert!((q1_b[0] - 0.99).abs() < 1e-12);
assert!((p1_b[0] + 0.1).abs() < 1e-12);
}
#[test]
fn test_energy_conservation() {
let system = SeparableHamiltonian::new(
|_t, p| -> f64 { 0.5 * p.dot(p) },
|_t, q| -> f64 { 0.5 * q.dot(q) },
);
let q0 = array![1.0];
let p0 = array![0.0];
let t0 = 0.0;
let tf = 10.0;
let dt = 0.1;
let integrator = SymplecticEulerA::new();
let result = integrator
.integrate(&system, t0, tf, dt, q0, p0)
.expect("Operation failed");
if let Some(error) = result.energy_relative_error {
assert!(error < 0.1, "Energy error too large: {error}");
}
for i in 0..result.t.len() {
let q = &result.q[i];
let p = &result.p[i];
let radius_squared = q[0] * q[0] + p[0] * p[0];
assert!(
(radius_squared - 1.0).abs() < 0.1,
"Point ({}, {}) is too far from unit circle, r² = {}",
q[0],
p[0],
radius_squared
);
}
}
}