use num_complex::Complex64;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LaplaceTransform;
impl LaplaceTransform {
pub fn transform<F>(f: F, s: Complex64, t_max: f64, n_steps: usize) -> Complex64
where
F: Fn(f64) -> f64,
{
let dt = t_max / n_steps as f64;
let mut sum = Complex64::new(0.0, 0.0);
for i in 0..=n_steps {
let t = i as f64 * dt;
let ft = f(t);
let weight = if i == 0 || i == n_steps { 0.5 } else { 1.0 };
sum += weight * ft * (-s * t).exp() * dt;
}
sum
}
pub fn inverse_transform<F>(f_laplace: F, t: f64, n_terms: usize) -> f64
where
F: Fn(Complex64) -> Complex64,
{
let r = 2.0 * n_terms as f64 / (5.0 * t);
let mut sum = Complex64::new(0.0, 0.0);
for k in 0..n_terms {
let theta = std::f64::consts::PI * k as f64 / n_terms as f64;
let s = Complex64::new(r * theta.cos(), r * theta.sin());
let ds_dtheta = Complex64::new(-r * theta.sin(), r * theta.cos());
if k == 0 {
sum += f_laplace(s) * ds_dtheta;
} else {
sum += f_laplace(s) * ds_dtheta * Complex64::new(0.0, -1.0) * theta.sin();
}
}
let result = (sum / (std::f64::consts::PI * Complex64::new(0.0, 1.0))).re * r / (n_terms as f64 * 2.0);
Self::stehfest(f_laplace, t, n_terms.min(20))
}
fn stehfest<F>(f_laplace: F, t: f64, n: usize) -> f64
where
F: Fn(Complex64) -> Complex64,
{
let ln2_over_t = std::f64::consts::LN_2 / t;
let mut result = 0.0;
for i in 1..=n {
let v_i = Self::stehfest_weight(i, n);
let s = Complex64::new(i as f64 * ln2_over_t, 0.0);
result += v_i * f_laplace(s).re;
}
result * ln2_over_t
}
fn stehfest_weight(i: usize, n: usize) -> f64 {
let k_max = ((i as f64) / 2.0).floor() as usize;
let k_min = ((i as f64 + 1.0) / 2.0).floor() as usize;
let mut sum = 0.0;
for k in (k_min - 1)..k_max.min(n) {
let binom = Self::binomial(n, k + 1);
let sign = if (i as isize - k as isize - 1) % 2 == 0 { 1.0 } else { -1.0 };
let power = (k + 1) as f64;
let term = sign * power.powi(i as i32) * binom;
let inner_binom = Self::binomial(2 * k + 1, k);
sum += term * inner_binom;
}
let factorial_n = Self::factorial(n);
let sign2 = if n % 2 == 0 { 1.0 } else { -1.0 };
sign2 * sum / factorial_n
}
fn binomial(n: usize, k: usize) -> f64 {
if k > n { return 0.0; }
Self::factorial(n) / (Self::factorial(k) * Self::factorial(n - k))
}
fn factorial(n: usize) -> f64 {
(1..=n).fold(1.0, |acc, x| acc * x as f64)
}
pub fn unit_step(s: Complex64) -> Complex64 {
1.0 / s
}
pub fn exponential(s: Complex64, a: f64) -> Complex64 {
1.0 / (s + a)
}
pub fn cosine(s: Complex64, omega: f64) -> Complex64 {
s / (s * s + omega * omega)
}
pub fn sine(s: Complex64, omega: f64) -> Complex64 {
omega / (s * s + omega * omega)
}
pub fn second_order_system(s: Complex64, k: f64, zeta: f64, wn: f64) -> Complex64 {
let denom = s * s + 2.0 * zeta * wn * s + wn * wn;
k / denom
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_laplace_unit_step() {
let result = LaplaceTransform::transform(
|_| 1.0,
Complex64::new(1.0, 0.0),
50.0,
10000,
);
assert!((result.re - 1.0).abs() < 0.05, "Unit step: {}", result.re);
}
#[test]
fn test_laplace_exponential() {
let a = 2.0;
let result = LaplaceTransform::transform(
|t| (-a * t).exp(),
Complex64::new(3.0, 0.0),
20.0,
10000,
);
assert!((result.re - 0.2).abs() < 0.01, "Exponential: {}", result.re);
}
#[test]
fn test_laplace_sine() {
let omega = 1.0;
let result = LaplaceTransform::transform(
|t| (omega * t).sin(),
Complex64::new(2.0, 0.0),
50.0,
10000,
);
assert!((result.re - 0.2).abs() < 0.01, "Sine: {}", result.re);
}
#[test]
fn test_common_transforms() {
let s = Complex64::new(2.0, 0.0);
assert!((LaplaceTransform::unit_step(s).re - 0.5).abs() < 1e-10);
assert!((LaplaceTransform::exponential(s, 1.0).re - 1.0/3.0).abs() < 1e-10);
assert!((LaplaceTransform::cosine(s, 1.0).re - 0.4).abs() < 1e-10);
assert!((LaplaceTransform::sine(s, 1.0).re - 0.2).abs() < 1e-10);
}
#[test]
fn test_second_order_system() {
let s = Complex64::new(0.0, 1.0);
let h = LaplaceTransform::second_order_system(s, 1.0, 0.5, 1.0);
assert!(h.im.abs() > 0.0, "Second order should be non-zero");
}
}