# Transforms
Laplace, Fourier, Mellin and Z transforms, Fourier series, and one-sided limits. Transforms that have an unevaluated node (`LaplaceTransform`, `InverseLaplaceTransform`) return `Ex` and have `try_` twins; the others (`fourier_transform`, `mellin_transform`, `z_transform`) are `Result`-only because no unevaluated node exists for them.
## Laplace
`laplace(&t, &s)` and `inverse_laplace(&s, &t)` are table-driven with linearity, shifting, scaling, differentiation, integration and `f(t)/t` rules. 0.2 extends the tables (Bessel `J₀`, `tⁿe^(−at)`, `sinh`/`cosh`, `erf` where possible, `1/√s`, delayed step functions) and adds the initial- and final-value theorems.
```rust
use symplex::prelude::*;
fn main() {
let ctx = Context::new();
symplex::syms!(ctx; t, s);
let a = ctx.symbol_with("a", &[Assumption::Positive]);
println!("{}", t.sin().laplace(&t, &s)); // 1/(s^2 + 1)
println!("{}", (&t.powi(2) * &(&t * -3).exp()).laplace(&t, &s)); // 2*(s + 3)^(-3)
println!("{}", ((&t * 2).sin() / &t).laplace(&t, &s)); // atan(2/s)
println!("{}", (&t - 2).heaviside().laplace(&t, &s)); // exp(-2*s)/s
println!("{}", t.bessel_j(&ctx.int(0)).laplace(&t, &s)); // 1/sqrt(s^2 + 1)
println!("{}", (&a * &t).sinh().laplace(&t, &s)); // a/(-a^2 + s^2)
println!("{}", t.erf().laplace(&t, &s)); // LaplaceTransform(erf(t), t, s) — not in table
println!("{}", (&s / (&s.powi(2) + &s * 2 + 5)).inverse_laplace(&s, &t)); // -1/2*sin(2*t)*exp(-t) + cos(2*t)*exp(-t)
println!("{}", ((&s * -2).exp() / &s).inverse_laplace(&s, &t)); // H(t - 2)
println!("{}", (1 / &s.sqrt()).inverse_laplace(&s, &t)); // t^(-1/2)/sqrt(pi)
let f = (&s + 1) / (&s * (&s.powi(2) + &s * 2 + 5));
println!("{}", f.laplace_initial_value(&s).unwrap()); // 0 (f(0⁺))
println!("{}", f.laplace_final_value(&s).unwrap()); // 1/5 (f(∞))
assert!(matches!((1 / (&s * (&s - 1))).laplace_final_value(&s), Err(SymplexError::Divergent { .. })));
}
```
## Fourier transform
`fourier_transform(&t, &w)` uses the non-unitary angular convention `F(ω) = ∫ f(t) e^(−iωt) dt`; `fourier_transform_with(&t, &w, FourierConvention::{NonUnitaryAngular, UnitaryAngular, Ordinary})` selects another. The table covers δ, constants, `H(t)`, `sign`, `1/t`, `|t|`, rectangular windows, `e^(−a|t|)`, Gaussians, `tⁿe^(−at)H(t)`, `cos`/`sin`, `sinc`, extended by linearity, shift, modulation, scaling, the derivative rule and `t·f(t) → iF′(ω)`. Symbols other than `t` and `ω` are treated as real parameters; a required sign condition that cannot be proven is an **error**, not an assumption.
```rust
use symplex::prelude::*;
fn main() {
let ctx = Context::new();
symplex::syms!(ctx; t, w, nu);
let a = ctx.symbol_with("a", &[Assumption::Positive]);
let i = ctx.i_unit();
println!("{}", (-&a * t.abs()).exp().fourier_transform(&t, &w).unwrap()); // 2*a/(a^2 + w^2)
println!("{}", ((&t + 1).heaviside() - (&t - 1).heaviside()).fourier_transform(&t, &w).unwrap()); // 2*sin(w)/w
println!("{}", ((&t * -2).exp() * t.heaviside()).fourier_transform(&t, &w).unwrap()); // 1/(w*I + 2)
println!("{}", (-t.powi(2)).exp().fourier_transform(&t, &w).unwrap()); // sqrt(pi)*exp(-1/4*w^2)
println!("{}", (&t * 3).cos().fourier_transform(&t, &w).unwrap()); // DiracDelta(w - 3)*pi + DiracDelta(w + 3)*pi
println!("{}", (-(ctx.pi() * t.powi(2))).exp()
.fourier_transform_with(&t, &nu, FourierConvention::Ordinary).unwrap()); // exp(-nu^2*pi) (self-dual)
println!("{}", (1 / (&i * &w + 2)).inverse_fourier_transform(&w, &t).unwrap()); // exp(-2*t)*H(t)
let b = ctx.symbol("b");
assert!((-&b * t.abs()).exp().fourier_transform(&t, &w).is_err()); // sign of b unknown
}
```
## Mellin transform
`mellin_transform(&x, &s)` returns `(F(s), strip)` where the strip is the `BoolEx` condition on `re(s)` for convergence; `inverse_mellin_transform(&s, &x)` inverts by table lookup (choosing the strip to the right of the pole when ambiguous).
```rust
use symplex::prelude::*;
fn main() {
let ctx = Context::new();
symplex::syms!(ctx; x, s);
for f in [(-&x).exp(), 1 / (1 + &x), 1 / (1 + &x).powi(3), x.powi(2) * (&x * -3).exp(), x.sin()] {
let (m, strip) = f.mellin_transform(&x, &s).unwrap();
println!("M{{{f}}} = {m} on {strip}");
}
// Gamma(s) on re(s) > 0
// pi/sin(s*pi) on re(s) > 0 & 1 > re(s)
// B(s, -s + 3) on re(s) > 0 & 3 > re(s)
// 3^(-s - 2)*Gamma(s + 2) on re(s) > -2
// sin(1/2*s*pi)*Gamma(s) on re(s) > -1 & 1 > re(s)
println!("{}", s.gamma().inverse_mellin_transform(&s, &x).unwrap()); // exp(-x)
}
```
## Fourier series
`fourier_series_on(&x, &lower, &upper, n_terms)` computes exact coefficients as definite integrals, so `sign`, `|x|`, sawtooth and piecewise waves work (0.1 returned wrong coefficients for these). The `FourierSeries` exposes `a0`, `coefficient_a(k)`, `coefficient_b(k)`, complex `coefficient_c(k)`, `omega0()`, `n_terms()` and `truncate(n)`. `fourier_series(&x, n)` is the `[−π, π]` shorthand.
```rust
use symplex::prelude::*;
fn main() {
let ctx = Context::new();
symplex::syms!(ctx; x);
let pi = ctx.pi();
let square = x.sign().fourier_series_on(&x, &(-&pi), &pi, 5).unwrap();
println!("{}", square.truncate(5)); // 4*sin(x)/pi + 4*sin(3*x)/(3*pi) + 4*sin(5*x)/(5*pi)
println!("{} {}", square.coefficient_b(1), square.coefficient_b(2)); // 4/pi 0
let tri = x.abs().fourier_series_on(&x, &(-&pi), &pi, 3).unwrap();
println!("{} {}", tri.a0, tri.truncate(3)); // pi -4*cos(x)/pi - 4*cos(3*x)/(9*pi) + 1/2*pi
let parab = x.powi(2).fourier_series_on(&x, &ctx.int(-1), &ctx.int(1), 2).unwrap();
println!("{}", parab.truncate(2)); // -4*pi^(-2)*cos(x*pi) + pi^(-2)*cos(2*x*pi) + 1/3
}
```
## Z-transform
`z_transform(&n, &z)` and `inverse_z_transform(&z, &n)` are table-driven (`aⁿ`, `n`, `n²`, `n·aⁿ`, `cos(ωn)`, `1/n!`, …) with linearity and shift rules.
```rust
use symplex::prelude::*;
fn main() {
let ctx = Context::new();
symplex::syms!(ctx; n, z, w);
let a = ctx.symbol_with("a", &[Assumption::Positive]);
println!("{}", a.pow(&n).z_transform(&n, &z).unwrap()); // z/(-a + z)
println!("{}", n.powi(2).z_transform(&n, &z).unwrap()); // (z - 1)^(-3)*(z^2 + z)
println!("{}", (&w * &n).cos().z_transform(&n, &z).unwrap()); // z*(z - cos(w))/(z^2 - 2*z*cos(w) + 1)
println!("{}", (&z / (&z - 1).powi(2)).inverse_z_transform(&z, &n).unwrap()); // n
}
```
## One-sided limits
`limit_left`, `limit_right` and `limit_dir(&x, &a, Direction::{Left, Right})` were added in 0.2, with `try_` twins. The two-sided `limit` returns an unevaluated `Limit` node when the one-sided limits disagree, rather than picking one.
```rust
use symplex::prelude::*;
fn main() {
let ctx = Context::new();
symplex::syms!(ctx; x);
let zero = ctx.int(0);
for f in [1 / &x, &x.abs() / &x, (-1 / &x).exp()] {
println!("{f}: {} | {} | {}", f.limit_left(&x, &zero), f.limit_right(&x, &zero), f.limit(&x, &zero));
}
// 1/x: -oo | oo | Limit(1/x, x, 0)
// abs(x)/x: -1 | 1 | Limit(abs(x)/x, x, 0)
// exp(-1/x): oo | 0 | Limit(exp(-1/x), x, 0)
println!("{}", (&x * &x.ln()).limit_right(&x, &zero)); // 0
println!("{}", x.tan().limit_left(&x, &(ctx.pi() / 2))); // oo
println!("{}", x.floor().limit_dir(&x, &ctx.int(1), Direction::Left)); // 0
}
```
See `cargo run --example transforms` and `laplace_transforms`.