symplex 0.26.0

Exact symbolic mathematics for Rust: calculus, summation, solving, linear algebra, transforms, compile-time dimensional analysis, and Rust/C code generation
Documentation
# Summation and Series

New in 0.2: a summation engine on `Ex` (`summation`, `product_over`), convergence tests, asymptotic series, and an `Ex`-based `FormalPowerSeries`.

## Finite sums

`summation(&k, &lower, &upper)` tries, in order: polynomial (Faulhaber) sums of any degree, geometric and arithmetico-geometric sums, telescoping (partial fractions in `k`), binomial identities (`Σ P(k)·C(n,k)·xᵏ` for any polynomial `P`), and Gosper's algorithm for hypergeometric terms. When nothing applies the result is an unevaluated `Sum` node; `try_summation` returns `Err(ComputationFailed)` instead.

```rust
use symplex::prelude::*;

fn main() {
    let ctx = Context::new();
    symplex::syms!(ctx; k, x);
    // Assumptions on the bound let the engine use n! and 2^n freely.
    let n = ctx.symbol_with("n", &[Assumption::Integer, Assumption::Positive]);
    let (zero, one) = (ctx.int(0), ctx.int(1));

    println!("{}", k.summation(&k, &one, &n));                       // 1/2*n^2 + 1/2*n
    println!("{}", k.powi(5).summation(&k, &one, &n));               // 1/6*n^6 + 1/2*n^5 + 5/12*n^4 - 1/12*n^2
    println!("{}", ctx.int(2).pow(&k).summation(&k, &zero, &n));     // 2^(n + 1) - 1
    println!("{}", (1 / (&k * (&k + 1))).summation(&k, &one, &n));   // -1/(n + 1) + 1
    println!("{}", n.binomial(&k).summation(&k, &zero, &n));         // 2^n
    println!("{}", (&k * &n.binomial(&k)).summation(&k, &zero, &n)); // n*2^(n - 1)
    println!("{}", (&k * &ctx.int(2).pow(&k)).summation(&k, &zero, &n)); // 2^(n + 1)*(n - 1) + 2
    println!("{}", (1 / &k).summation(&k, &one, &n));                // harmonic(n)

    // A geometric sum with symbolic ratio is only valid for x ≠ 1:
    println!("{}", x.pow(&k).summation(&k, &zero, &n));
    // Piecewise(n + 1 if x == 1, (-x^(n + 1) + 1)/(-x + 1) if x != 1)

    assert!(k.sin().try_summation(&k, &one, &n).is_err());
}
```

## Infinite series

Infinite upper bounds go through p-series (`ζ(2m)` exact; odd `p` gives a symbolic `zeta(p)`; alternating variants give `ln 2`, Catalan's constant), power-series recognition (`Σ xᵏ/k! = eˣ`, geometric series), telescoping limits, and Gosper tails. Divergent series evaluate to `oo` where the divergence is provable.

```rust
use symplex::prelude::*;

fn main() {
    let ctx = Context::new();
    symplex::syms!(ctx; k, x);
    let (zero, one, inf) = (ctx.int(0), ctx.int(1), ctx.infinity());

    println!("{}", k.powi(-2).summation(&k, &one, &inf));                          // 1/6*pi^2
    println!("{}", k.powi(-4).summation(&k, &one, &inf));                          // 1/90*pi^4
    println!("{}", k.powi(-3).summation(&k, &one, &inf));                          // zeta(3)
    println!("{}", (ctx.int(-1).pow(&k) / (&k * 2 + 1).powi(2)).summation(&k, &zero, &inf)); // Catalan
    println!("{}", (ctx.int(-1).pow(&k) / &k).summation(&k, &one, &inf));          // -ln(2)
    println!("{}", (1 / &k.factorial()).summation(&k, &zero, &inf));               // E
    println!("{}", (&x.pow(&k) / &k.factorial()).summation(&k, &zero, &inf));      // exp(x)
    println!("{}", (1 / (&k * (&k + 1))).summation(&k, &one, &inf));               // 1
    println!("{}", (1 / &k).summation(&k, &one, &inf));                            // oo
}
```

## Convergence

`is_convergent(&k)` and `is_absolutely_convergent(&k)` apply the p-series, ratio, root, alternating-series and comparison tests and return `Some(true)`/`Some(false)` only when a test is decisive; otherwise `None`. `hypergeometric_ratio(&k)` returns `t(k+1)/t(k)` as a rational function when the term is hypergeometric.

```rust
use symplex::prelude::*;

fn main() {
    let ctx = Context::new();
    symplex::syms!(ctx; k);
    let alt = ctx.int(-1).pow(&k) / &k;

    assert_eq!(k.powi(-2).is_convergent(&k), Some(true));
    assert_eq!((1 / &k).is_convergent(&k), Some(false));
    assert_eq!(alt.is_convergent(&k), Some(true));
    assert_eq!(alt.is_absolutely_convergent(&k), Some(false));

    let ratio = (ctx.int(2).pow(&k) / &k.factorial()).hypergeometric_ratio(&k).unwrap();
    println!("{ratio}");                                                    // 2/(k + 1)
}
```

## Products

`product_over` handles constant, polynomial-factorable, telescoping and factorial-type products, plus classical infinite products.

```rust
use symplex::prelude::*;

fn main() {
    let ctx = Context::new();
    symplex::syms!(ctx; k);
    let n = ctx.symbol_with("n", &[Assumption::Integer, Assumption::Positive]);

    println!("{}", k.product_over(&k, &ctx.int(1), &n));                  // n!
    println!("{}", (1 + 1 / &k).product_over(&k, &ctx.int(1), &n));       // n + 1
    println!("{}", (1 - k.powi(-2)).product_over(&k, &ctx.int(2), &ctx.infinity())); // 1/2
}
```

## Formal power series

`fps(&x, &point)` and `fps_maclaurin(&x)` return a `FormalPowerSeries`: coefficients are computed lazily and exactly on demand, and `general_term(&k)` returns a closed form for the *k*-th coefficient when one is recognised. Series support `add`, `sub`, `mul`, `scale`, `compose`, `inverse` (1/f), `reversion` (compositional inverse), `derivative`, `integral`, and `truncate(n)` back to an `Ex`.

```rust
use symplex::prelude::*;

fn main() {
    let ctx = Context::new();
    symplex::syms!(ctx; x, k);

    let sin = x.sin().fps_maclaurin(&x);
    let cos = x.cos().fps_maclaurin(&x);
    let exp = x.exp().fps_maclaurin(&x);

    println!("{:?}", sin.coefficients(8).iter().map(|c| c.to_string()).collect::<Vec<_>>());
    // ["0", "1", "0", "-1/6", "0", "1/120", "0", "-1/5040"]
    println!("{}", sin.coefficient(51));              // -1/51!  (exact)
    println!("{}", sin.general_term(&k).unwrap());    // sin(1/2*k*pi)/k!
    println!("{}", exp.general_term(&k).unwrap());    // 1/k!

    println!("{}", sin.mul(&cos).unwrap().truncate(6));               // 2/15*x^5 - 2/3*x^3 + x
    println!("{:?}", cos.inverse().unwrap().coefficients(7).iter().map(|c| c.to_string()).collect::<Vec<_>>());
    // sec x: ["1", "0", "1/2", "0", "5/24", "0", "61/720"]
    println!("{:?}", sin.reversion().unwrap().coefficients(6).iter().map(|c| c.to_string()).collect::<Vec<_>>());
    // asin x: ["0", "1", "0", "1/6", "0", "3/40"]
    let gauss = exp.compose(&(-&x.powi(2)).fps_maclaurin(&x)).unwrap();
    println!("{:?}", gauss.coefficients(7).iter().map(|c| c.to_string()).collect::<Vec<_>>());
    // e^(-x²): ["1", "0", "-1", "0", "1/2", "0", "-1/6"]
}
```

## Finite differences

`finite_diff::finite_diff_weights(order, &points, &x0)` returns exact Fornberg weights; `Ex::differentiate_finite(&x, &points, order)` applies them to an expression (formal `Derivative` nodes inside are replaced by their stencils; `order = 0` does just that replacement).

```rust
use symplex::prelude::*;
use symplex::finite_diff::finite_diff_weights;

fn main() {
    let ctx = Context::new();
    symplex::syms!(ctx; x, h);
    let stencil = [&x - &h, x.clone(), &x + &h];
    let w = finite_diff_weights(1, &stencil, &x);
    println!("{:?}", w.iter().map(|e| e.to_string()).collect::<Vec<_>>());
    // ["-1/(2*h)", "0", "1/(2*h)"]
    println!("{}", x.powi(3).differentiate_finite(&x, &stencil, 1).expand());   // h^2 + 3*x^2
    println!("{}", x.powi(4).differentiate_finite(&x, &stencil, 2).expand());   // 2*h^2 + 12*x^2
}
```

See `cargo run --example summation_and_series` for the full tour.