pub struct Expr<S: Sort> { /* private fields */ }Expand description
A symbolic expression handle, parameterized by sort.
Expr<Numeric> (aliased as Ex) represents numeric expressions.
Expr<Boolean> (aliased as BoolEx) represents boolean expressions.
The sort parameter is a phantom type — zero runtime cost. It prevents invalid operations at compile time:
sin(bool_expr)won’t compile (sin is only onExpr<Numeric>)bool_expr + 1won’t compile (Add is only onExpr<Numeric>)numeric.and(other)won’t compile (and is only onExpr<Boolean>)
Implementations§
Source§impl<S: Sort> Expr<S>
impl<S: Sort> Expr<S>
Sourcepub fn id(&self) -> ExprId
pub fn id(&self) -> ExprId
Returns the raw ExprId inside this handle.
Note: This is an opaque arena-local index. It is only
meaningful within the Context
that created this expression. Comparing ExprId values across
contexts is undefined.
Sourcepub fn is_zero_structural(&self) -> bool
pub fn is_zero_structural(&self) -> bool
Returns true if this expression is structurally zero (O(1)).
Sourcepub fn is_one_structural(&self) -> bool
pub fn is_one_structural(&self) -> bool
Returns true if this expression is structurally one (O(1)).
Sourcepub fn free_symbols(&self) -> Vec<Ex> ⓘ
pub fn free_symbols(&self) -> Vec<Ex> ⓘ
Returns the set of free symbols in this expression.
Each symbol appears at most once. The order is deterministic but unspecified.
Symbols are always numeric, so this returns Vec<Ex> regardless
of the sort of self.
Sourcepub fn has_unevaluated(&self) -> bool
pub fn has_unevaluated(&self) -> bool
Returns true if this expression contains any unevaluated formal
nodes such as Integral(...), Derivative(...), Limit(...), etc.
Useful for checking whether a symbolic computation fully evaluated or left formal/unevaluated placeholders.
Sourcepub fn count_ops(&self) -> usize
pub fn count_ops(&self) -> usize
Count the number of operations (non-atom nodes) in this expression.
Atoms (numbers, symbols, constants) count as 0. Each operator or function application counts as 1.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
assert_eq!(x.count_ops(), 0); // atom
assert_eq!((&x + 1).count_ops(), 1); // one Add
assert_eq!(x.sin().powi(2).count_ops(), 2); // Sin + PowSourcepub fn term_count(&self) -> usize
pub fn term_count(&self) -> usize
Returns the number of top-level terms in this expression.
For an Add node, returns the number of summands.
For anything else, returns 1.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
assert_eq!((&x + 1).term_count(), 2);
assert_eq!(x.powi(2).term_count(), 1);Sourcepub fn args(&self) -> Vec<Expr<S>>
pub fn args(&self) -> Vec<Expr<S>>
Returns the direct children (arguments) of this expression.
- For
Add: returns the summands. - For
Mul: returns the factors. - For
Pow: returns[base, exponent]. - For
Neg: returns[inner]. - For functions (sin, cos, etc.): returns
[argument]. - For atoms (numbers, symbols, constants): returns
[].
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
let expr = &x + 1;
let children = expr.args();
assert_eq!(children.len(), 2);Sourcepub fn expr_type(&self) -> ExprType
pub fn expr_type(&self) -> ExprType
Returns the structural type of this expression.
Collapses the internal ExprNode enum (over 70 variants) into a
user-friendly ExprType classification.
§Examples
use symplex::prelude::*;
use symplex::expr::ExprType;
let ctx = Context::new();
let x = ctx.symbol("x");
assert_eq!(x.expr_type(), ExprType::Symbol);
assert_eq!(x.sin().expr_type(), ExprType::Function);
assert_eq!((&x + 1).expr_type(), ExprType::Add);Sourcepub fn subs(&self, old: &Ex, new: &Ex) -> Expr<S>
pub fn subs(&self, old: &Ex, new: &Ex) -> Expr<S>
Structural substitution: replace every occurrence of old with new.
This is structural — only exact node matches are replaced.
(1/x).subs(x², 1) returns 1/x unchanged because x² does
not appear as a node in x⁻¹.
The result is re-canonicalized, so like-term collection and other invariants are maintained.
Returns self unchanged (same Expr) if old does not appear.
Sourcepub fn subs_i64(&self, old: &Ex, new: i64) -> Expr<S>
pub fn subs_i64(&self, old: &Ex, new: i64) -> Expr<S>
Substitute a symbol with an integer value.
Convenience shorthand for self.subs(old, &ctx.int(n)) that
avoids needing to construct the integer expression manually.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
let expr = x.powi(2);
let result = expr.subs_i64(&x, 3);
assert_eq!(format!("{result}"), "9");Sourcepub fn subs_map(&self, replacements: &[(&Ex, &Ex)]) -> Expr<S>
pub fn subs_map(&self, replacements: &[(&Ex, &Ex)]) -> Expr<S>
Simultaneous substitution of multiple (old, new) pairs.
All replacements happen “at once” — earlier substitutions do not affect later ones.
Sourcepub fn expand(&self) -> Expr<S>
pub fn expand(&self) -> Expr<S>
Algebraic expansion (distribute products over sums, expand integer powers of sums).
a * (b + c)→a*b + a*c(a + b)^n→ multinomial expansion
Does NOT evaluate functions, factor, or simplify.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
let expr = (&x + 1).powi(2);
assert_eq!(format!("{}", expr.expand()), "x^2 + 2*x + 1");Sourcepub fn simplify_with(&self, opts: &SimplifyOpts) -> Expr<S>
pub fn simplify_with(&self, opts: &SimplifyOpts) -> Expr<S>
Like simplify, but with configurable options.
Use SimplifyOpts to control the fixpoint iteration count.
This always runs the numeric simplification engine, whatever the
sort of self; for BoolEx and SetEx prefer their own
simplify(), which additionally applies boolean / set algebra.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
let expr = &x.sin().powi(2) + &x.cos().powi(2);
// Single-pass simplification (no fixpoint iteration)
let result = expr.simplify_with(&SimplifyOpts::single_pass());
assert_eq!(format!("{}", result), "1");Sourcepub fn to_tree(&self) -> ExprTree
pub fn to_tree(&self) -> ExprTree
Convert this expression to a standalone serializable ExprTree.
The tree can be serialized to JSON (or any serde format) and
deserialized back via Context::from_tree().
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
let tree = x.powi(2).to_tree();
let json = serde_json::to_string(&tree).unwrap();
assert!(json.contains("Pow"));Sourcepub fn to_json(&self) -> Result<String, SymplexError>
pub fn to_json(&self) -> Result<String, SymplexError>
Serialize this expression to a JSON string.
This is a convenience shorthand for
serde_json::to_string(&expr.to_tree()).
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
let json = x.powi(2).to_json().unwrap();
assert!(json.contains("\"type\":\"Pow\""));Sourcepub fn to_json_pretty(&self) -> Result<String, SymplexError>
pub fn to_json_pretty(&self) -> Result<String, SymplexError>
Serialize this expression to a pretty-printed JSON string.
Sourcepub fn apply_until_stable<F>(
&self,
max_iterations: usize,
f: F,
) -> (Expr<S>, usize)
pub fn apply_until_stable<F>( &self, max_iterations: usize, f: F, ) -> (Expr<S>, usize)
Apply a transformation repeatedly until the expression stops changing,
or max_iterations is reached.
Returns the final expression and the number of iterations performed. Useful for building custom simplification pipelines.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
let expr = (&x + 1).powi(2);
let (result, iters) = expr.apply_until_stable(10, |e| e.expand());
assert_eq!(format!("{result}"), "x^2 + 2*x + 1");
assert_eq!(iters, 1); // stabilized after 1 iterationSource§impl Expr<Numeric>
impl Expr<Numeric>
Sourcepub fn contains(&self, needle: &Ex) -> bool
pub fn contains(&self, needle: &Ex) -> bool
Returns true if needle appears as a sub-expression of self.
This is a structural check — it walks the expression DAG and
returns true if any node has the same ExprId as needle.
For set membership use SetEx::contains / Ex::is_in.
Sourcepub fn eval(&self) -> Ex
pub fn eval(&self) -> Ex
Exact evaluation of known special values.
Replaces function applications with their exact values when the arguments are known constants:
sin(0)→0,sin(π)→0,sin(π/2)→1cos(0)→1,cos(π)→-1exp(0)→1,ln(1)→0sqrt(4)→2,abs(-3)→3
Only evaluates when the result is a simpler atom.
Does NOT evaluate cos(π/4) → √2/2.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let expr = ctx.pi().cos();
assert_eq!(format!("{}", expr.eval()), "-1");Sourcepub fn simplify(&self) -> Ex
pub fn simplify(&self) -> Ex
Simplification (identity application, trig identities, etc.).
Simplify the expression using all available strategies.
Tries 12+ strategies (eval, expand, factor, trig, log, cancel, power, radical, assumption-aware refinement, …), picks the simplest result, then iterates to a fixpoint (up to 10 passes) until the expression stops getting simpler.
This is the “just make this simpler” function. For finer control,
use simplify_with or the domain-specific
methods (simplify_trig,
simplify_powers, etc.).
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
// Trig identity
let expr = &x.sin().powi(2) + &x.cos().powi(2);
assert_eq!(format!("{}", expr.simplify()), "1");
// Polynomial cancellation
let expr = &(&x + 1).powi(2) - &x.powi(2) - &x * 2;
assert_eq!(format!("{}", expr.simplify()), "1");Source§impl Expr<Boolean>
impl Expr<Boolean>
Sourcepub fn contains(&self, needle: &Ex) -> bool
pub fn contains(&self, needle: &Ex) -> bool
Returns true if needle appears as a sub-expression of self.
This is a structural check — it walks the expression DAG and
returns true if any node has the same ExprId as needle.
Sourcepub fn xor(&self, other: &BoolEx) -> BoolEx
pub fn xor(&self, other: &BoolEx) -> BoolEx
Exclusive or: self ⊕ other = (self ∧ ¬other) ∨ (¬self ∧ other).
Sourcepub fn implies(&self, other: &BoolEx) -> BoolEx
pub fn implies(&self, other: &BoolEx) -> BoolEx
Logical implication: self → other = ¬self ∨ other.
Sourcepub fn equivalent(&self, other: &BoolEx) -> BoolEx
pub fn equivalent(&self, other: &BoolEx) -> BoolEx
Logical biconditional: self ↔ other = (self → other) ∧ (other → self).
Source§impl Expr<SetValued>
impl Expr<SetValued>
Sourcepub fn union(&self, other: &SetEx) -> SetEx
pub fn union(&self, other: &SetEx) -> SetEx
Union: self ∪ other (structural; call simplify
to merge overlapping intervals).
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let a = ctx.interval(&ctx.int(0), &ctx.int(1), IntervalKind::Closed);
let b = ctx.interval(&ctx.int(2), &ctx.int(3), IntervalKind::Closed);
let u = a.union(&b);
let s = format!("{u}");
assert!(!s.is_empty(), "union display: {s}");Sourcepub fn intersection(&self, other: &SetEx) -> SetEx
pub fn intersection(&self, other: &SetEx) -> SetEx
Intersection: self ∩ other (structural; call
simplify to evaluate).
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let a = ctx.interval(&ctx.int(0), &ctx.int(1), IntervalKind::Closed);
let e = ctx.empty_set();
let result = a.intersection(&e);
assert_eq!(format!("{result}"), "EmptySet");Sourcepub fn complement(&self, other: &SetEx) -> SetEx
pub fn complement(&self, other: &SetEx) -> SetEx
Relative complement: self \ other (structural).
This is the lazy constructor; difference
returns the evaluated normal form and
absolute_complement computes ℝ \ self.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let a = ctx.interval(&ctx.int(0), &ctx.int(3), IntervalKind::Closed);
let b = ctx.interval(&ctx.int(1), &ctx.int(2), IntervalKind::Open);
let lazy = a.complement(&b);
assert_eq!(format!("{lazy}"), "[0, 3] \\ (1, 2)");
assert_eq!(format!("{}", lazy.simplify()), "[0, 1] ∪ [2, 3]");Source§impl Expr<Numeric>
impl Expr<Numeric>
Sourcepub fn minimal_polynomial(&self, var: &Ex) -> Option<Ex>
pub fn minimal_polynomial(&self, var: &Ex) -> Option<Ex>
Minimal polynomial over ℚ of an algebraic-number expression, as a
polynomial in var (SymPy minimal_polynomial).
self must be a constant built from rational numbers, radicals
(n^{p/q}), i, φ, sums, products, negations, and integer or
rational powers of such numbers ((1 + √2)⁻¹, √(3 + 2√2); a
fractional power needs a positive real base). The result has
integer coefficients with no common factor and a positive leading
coefficient, so 3/4 gives 4·var − 3 and √2 + √3 gives
var⁴ − 10·var² + 1. Returns None for input that is not
recognised as algebraic — π, e, free symbols, transcendental
functions.
The result is verified, never guessed: the irreducible factor of
each intermediate resultant is chosen by evaluating the number to
320 bits and accepting the unique factor whose residual is
negligible, and the factorisation itself must be certified complete.
If either check fails the method returns None rather than a
polynomial that might be reducible or vanish at a different
conjugate.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
let a = ctx.int(2).sqrt() + ctx.int(3).sqrt();
let m = a.minimal_polynomial(&x).unwrap();
assert_eq!(m, &x.powi(4) - &x.powi(2) * 10 + 1);
let cbrt2 = ctx.int(2).pow(&ctx.rational(1, 3));
assert_eq!(cbrt2.minimal_polynomial(&x).unwrap(), &x.powi(3) - 2);
assert!(ctx.pi().minimal_polynomial(&x).is_none());Sourcepub fn gcd_all(&self, other: &Ex) -> Option<Ex>
pub fn gcd_all(&self, other: &Ex) -> Option<Ex>
Polynomial greatest common divisor of self and other over ℚ in
all of their free symbols at once (SymPy gcd(f, g)).
Both inputs must be polynomials with rational coefficients; anything
else (sin(x), 1/x, π) gives None. The result is normalised
like MultiPoly::gcd: integer coefficients, positive leading
coefficient in graded reverse lexicographic order, and integer
content equal to the gcd of the inputs’ integer contents once their
denominators are cleared — for polynomials over ℤ this is exactly
the gcd over ℤ (gcd(2x, 4x) = 2x). gcd(0, 0) = 0; two rational
constants give their integer gcd.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let (x, y) = (ctx.symbol("x"), ctx.symbol("y"));
let f = &x.powi(2) - &y.powi(2);
let g = &x - &y;
assert_eq!(f.gcd_all(&g).unwrap(), g);
assert_eq!((&x * 2).gcd_all(&(&x * 4)).unwrap(), &x * 2);
assert!(x.sin().gcd_all(&x).is_none());Sourcepub fn lcm_all(&self, other: &Ex) -> Option<Ex>
pub fn lcm_all(&self, other: &Ex) -> Option<Ex>
Polynomial least common multiple of self and other over ℚ in
all of their free symbols (SymPy lcm(f, g)).
Same preconditions and normalisation as gcd_all:
lcm = a·b / gcd(a, b) computed on the integer-normalised inputs,
so lcm(2x, 4x) = 4x; zero if either input is zero.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let (x, y) = (ctx.symbol("x"), ctx.symbol("y"));
let f = &x.powi(2) - &y.powi(2);
let g = &x - &y;
assert_eq!(f.lcm_all(&g).unwrap(), f);
assert_eq!((&x * &y).lcm_all(&y.powi(2)).unwrap(), &x * &y.powi(2));Sourcepub fn groebner(
polys: &[Ex],
vars: &[Ex],
order: MonomialOrder,
) -> Result<Vec<Ex>, SymplexError>
pub fn groebner( polys: &[Ex], vars: &[Ex], order: MonomialOrder, ) -> Result<Vec<Ex>, SymplexError>
Reduced Gröbner basis of the ideal generated by polys in the
variables vars under the monomial order order (SymPy
groebner(polys, *vars, order=…)).
Every element of the basis is monic; the list is sorted by leading
monomial, largest first. An empty polys (or all zeros) gives an
empty basis; an ideal containing a non-zero constant gives [1].
MonomialOrder::GrevLex runs Buchberger directly;
MonomialOrder::Lex computes in grevlex first and converts with
FGLM when the ideal is zero-dimensional, which is much faster than
lex Buchberger and yields the same (unique) reduced basis.
§Errors
InvalidArgument if vars is empty, contains duplicates or
non-symbols, or if some polynomial is not a polynomial in vars
with rational coefficients (other symbols count as non-rational
coefficients).
§Examples
use symplex::prelude::*;
use symplex::multipoly::MonomialOrder;
let ctx = Context::new();
let (x, y) = (ctx.symbol("x"), ctx.symbol("y"));
let f = &x.powi(2) + &y.powi(2) - 1;
let g = &x - &y;
let vars = [x.clone(), y.clone()];
let basis = Ex::groebner(&[f, g.clone()], &vars, MonomialOrder::Lex).unwrap();
assert_eq!(basis, vec![g, &y.powi(2) - ctx.rational(1, 2)]);Sourcepub fn reduce_modulo(
&self,
basis: &[Ex],
vars: &[Ex],
order: MonomialOrder,
) -> Result<Ex, SymplexError>
pub fn reduce_modulo( &self, basis: &[Ex], vars: &[Ex], order: MonomialOrder, ) -> Result<Ex, SymplexError>
Remainder of self on multivariate division by basis in the
variables vars under order (SymPy reduced(f, G)[1] /
GroebnerBasis.reduce).
When basis is a Gröbner basis for order this is the unique
normal form of self modulo the ideal — zero exactly when self
lies in the ideal. For an arbitrary basis it is a remainder,
which may depend on the order of the divisors.
§Errors
As groebner: invalid vars, or self or a
basis element that is not a polynomial in vars with rational
coefficients.
§Examples
use symplex::prelude::*;
use symplex::multipoly::MonomialOrder;
let ctx = Context::new();
let (x, y) = (ctx.symbol("x"), ctx.symbol("y"));
let vars = [x.clone(), y.clone()];
let basis = Ex::groebner(&[&x.powi(2) + &y.powi(2) - 1, &x - &y], &vars, MonomialOrder::Lex).unwrap();
// x² ≡ y² ≡ 1/2 modulo the ideal
let r = x.powi(2).reduce_modulo(&basis, &vars, MonomialOrder::Lex).unwrap();
assert_eq!(r, ctx.rational(1, 2));
assert!((&x.powi(2) - &y.powi(2)).reduce_modulo(&basis, &vars, MonomialOrder::Lex).unwrap().is_zero_structural());Sourcepub fn real_roots(&self, var: &Ex) -> Option<Vec<Ex>>
pub fn real_roots(&self, var: &Ex) -> Option<Vec<Ex>>
The distinct real roots of self as a polynomial in var, as
exact expressions in increasing order (SymPy real_roots, except
that a repeated root is listed once, as in
count_real_roots).
Rational roots are returned as numbers. Every other root is a
RootOf(g, k) node, where g is the irreducible factor over ℤ
that vanishes there and k indexes g’s roots sorted by real then
imaginary part — the same node solve produces for degree ≥ 5, so
it evaluates numerically (eval_f64) and prints as RootOf(…).
The order is decided exactly with Sturm sequences.
Returns None if self is not a polynomial in var with rational
coefficients or is constant; a polynomial without real roots gives
Some(vec![]). None is also returned in the rare case that a
root cannot be named reliably — the factorisation over ℤ could
not be certified complete, or the RootOf index of a root is not
stable because another root of the same factor has the same real
part to within 2⁻⁶⁰ (the (re, im) order would then depend on
rounding). Every RootOf(g, k) that is returned has been checked
to evaluate inside the root’s exact isolating interval.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
let roots = (&x.powi(3) - &x * 2).real_roots(&x).unwrap(); // −√2, 0, √2
assert_eq!(roots.len(), 3);
assert_eq!(roots[1], ctx.int(0));
assert!((roots[2].eval_f64().unwrap() - 2f64.sqrt()).abs() < 1e-12);
assert!((&x.powi(2) + 1).real_roots(&x).unwrap().is_empty());Sourcepub fn root_of(&self, var: &Ex, index: usize) -> Option<Ex>
pub fn root_of(&self, var: &Ex, index: usize) -> Option<Ex>
The index-th distinct real root of self in var, counting from
the smallest (0-based) — real_roots(var)[index] (SymPy
rootof(f, index) for the real roots).
None under the same conditions as real_roots,
or if there are at most index real roots.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
let f = &x.powi(5) - &x - 1; // one real root ≈ 1.1673
let r = f.root_of(&x, 0).unwrap();
assert!((r.eval_f64().unwrap() - 1.1673039782614187).abs() < 1e-12);
assert!(f.root_of(&x, 1).is_none());Sourcepub fn factor_mod(
&self,
var: &Ex,
p: u64,
) -> Result<(Ex, Vec<(Ex, u32)>), SymplexError>
pub fn factor_mod( &self, var: &Ex, p: u64, ) -> Result<(Ex, Vec<(Ex, u32)>), SymplexError>
Factorisation of self, a polynomial in var, over the prime
field GF(p) (SymPy factor_list(f, modulus=p)).
Returns (lc, [(factor, multiplicity), …]) with self ≡ lc · ∏ factorᵢ^multᵢ (mod p): lc is the leading coefficient reduced mod
p, each factor is monic and irreducible over GF(p) with
coefficients in [0, p), and the list is sorted by degree then
coefficients. Rational coefficients are reduced through the
inverse of their denominator. A polynomial that vanishes
identically mod p gives (0, []); a constant gives (c mod p, []).
§Errors
InvalidArgument if p is not prime, if p is 2 or at least
2³¹ (the finite-field arithmetic supports odd primes below
factor_zassenhaus::MAX_PRIME),
if self is not a polynomial in var with rational coefficients,
or if some coefficient has a denominator divisible by p.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
// x² + 1 ≡ (x + 2)(x + 3) (mod 5)
let (lc, factors) = (&x.powi(2) + 1).factor_mod(&x, 5).unwrap();
assert_eq!(lc, ctx.int(1));
assert_eq!(factors, vec![(&x + 2, 1), (&x + 3, 1)]);
// … but irreducible mod 3.
let (_, factors) = (&x.powi(2) + 1).factor_mod(&x, 3).unwrap();
assert_eq!(factors, vec![(&x.powi(2) + 1, 1)]);
assert!((&x.powi(2) + 1).factor_mod(&x, 6).is_err());Sourcepub fn resultant_symbolic(&self, other: &Ex, var: &Ex) -> Option<Ex>
pub fn resultant_symbolic(&self, other: &Ex, var: &Ex) -> Option<Ex>
Resultant res_var(self, other) of two polynomials in var whose
coefficients may be symbolic (SymPy resultant(f, g, var)).
Computed as the determinant of the Sylvester matrix over Ex
entries and expanded, so the result is a polynomial in the
parameters; resultant is the faster exact
route when every coefficient is rational. Returns None if either
expression is not polynomial in var (or var is not a symbol).
The resultant of two constants is 1; if one polynomial is a
constant c and the other has degree d, the result is c^d.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let (x, a, b) = (ctx.symbol("x"), ctx.symbol("a"), ctx.symbol("b"));
// res(x − a, x − b) = g(a) = a − b
let r = (&x - &a).resultant_symbolic(&(&x - &b), &x).unwrap();
assert_eq!(r, &a - &b);
// res(x² + a, x + b) = a + b²
let r = (&x.powi(2) + &a).resultant_symbolic(&(&x + &b), &x).unwrap();
assert_eq!(r, &a + &b.powi(2));Sourcepub fn discriminant_symbolic(&self, var: &Ex) -> Option<Ex>
pub fn discriminant_symbolic(&self, var: &Ex) -> Option<Ex>
Discriminant of self as a polynomial in var with possibly
symbolic coefficients (SymPy discriminant(f, var)).
disc(f) = (−1)^{n(n−1)/2} · res(f, f′) / lc(f), evaluated
division-free: the leading coefficient is eliminated from the
Sylvester matrix of f and f′ by one row operation before taking
the determinant, so the result is an expanded polynomial in the
parameters (b² − 4ac for ax² + bx + c). Returns None for
non-polynomial or constant input; a linear polynomial has
discriminant 1.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let (x, a, b, c) = (ctx.symbol("x"), ctx.symbol("a"), ctx.symbol("b"), ctx.symbol("c"));
let quad = &a * &x.powi(2) + &b * &x + &c;
assert_eq!(quad.discriminant_symbolic(&x).unwrap(), &b.powi(2) - &a * &c * 4);
// depressed cubic x³ + px + q: −4p³ − 27q²
let (p, q) = (ctx.symbol("p"), ctx.symbol("q"));
let cubic = &x.powi(3) + &p * &x + &q;
assert_eq!(cubic.discriminant_symbolic(&x).unwrap(), -(&p.powi(3) * 4) - &q.powi(2) * 27);Source§impl Expr<Numeric>
impl Expr<Numeric>
Sourcepub fn singularities(
&self,
var: &Ex,
domain: Option<&SetEx>,
) -> Result<SetEx, SymplexError>
pub fn singularities( &self, var: &Ex, domain: Option<&SetEx>, ) -> Result<SetEx, SymplexError>
The points of domain (default ℝ) where self is undefined —
SymPy’s singularities.
The rule set is SymPy’s: zeros of the base of every negative power
(this covers denominators, sec, csc and cot), zeros of the
argument of ln, poles of tan, and atanh(g) at g = ±1. Only
real points are reported.
Zeros are found exactly with solve; equations with
sin/cos/tan of var use solve_general
and the periodic families are enumerated inside a bounded domain
(tan(x) on [0, 10] gives {π/2, 3π/2, 5π/2}). On an unbounded
domain such a family is returned as the condition set
{x | cos(x) = 0} (intersected with the domain), since the
infinite family has no interval / finite-set representation.
§Errors
InvalidArgument—varis not a symbol.ComputationFailed— the zeros of some source cannot be found exactly (the solver fails ong = 0), or, on the periodic-family path only, a family is not linear in its integer parameter, its members cannot be located numerically, more than 10 000 of them may lie in the domain, or the membership of a member indomaincannot be decided. Plain (non-periodic) zeros whose membership indomainis undecided do not error: the result is thenOkwith the intersection{p, …} ∩ domainleft unevaluated.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
// SymPy: singularities(1/(x**2 - 1), x) == {-1, 1}
let s = (1 / (&x.powi(2) - 1)).singularities(&x, None).unwrap();
assert_eq!(s.to_string(), "{-1, 1}");
// SymPy: singularities(log(x), x) == {0}
assert_eq!(x.ln().singularities(&x, None).unwrap().to_string(), "{0}");
// Polynomials have none.
assert_eq!(x.powi(2).singularities(&x, None).unwrap().is_empty(), Some(true));
// Restricted to a domain.
let dom = ctx.interval(&ctx.int(0), &ctx.int(5), IntervalKind::Closed);
assert_eq!((1 / (&x.powi(2) - 1)).singularities(&x, Some(&dom)).unwrap().to_string(), "{1}");Sourcepub fn stationary_points(
&self,
var: &Ex,
domain: Option<&SetEx>,
) -> Result<SetEx, SymplexError>
pub fn stationary_points( &self, var: &Ex, domain: Option<&SetEx>, ) -> Result<SetEx, SymplexError>
The real solutions of d self / d var = 0 in domain (default ℝ)
— SymPy’s stationary_points.
Periodic families of critical points (sin, cos, tan) are
enumerated on a bounded domain and returned as a condition set
{x | f'(x) = 0} on an unbounded one; see
singularities. An expression that does not
depend on var has derivative 0, so every point of the domain is
stationary and the domain itself is returned (as SymPy does).
The sign(h) factors that differentiating |h| introduces are
resolved by cases: on each region where every h has a fixed sign
the derivative is a plain expression whose zeros are found and kept
where the assumed signs hold (|x − 1| + x² → {1/2}). A region
on which the derivative vanishes identically is returned whole
(|x| + x on [−1, 2] → [−1, 0)), and a kink at which the
derivative evaluates to zero counts as stationary (|x| → {0},
since sign(0) = 0) — all as SymPy does. At most four distinct
sign factors are resolved.
§Errors
InvalidArgument—varis not a symbol.ComputationFailed— the derivative is a formalDerivative, the zeros of the derivative cannot be found exactly, or more than foursignfactors would have to be resolved.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
let f = &x.powi(3) - &x * 3;
// SymPy: stationary_points(x**3 - 3*x, x) == {-1, 1}
assert_eq!(f.stationary_points(&x, None).unwrap().to_string(), "{-1, 1}");
// SymPy: stationary_points(x**3 - 3*x, x, Interval(0, 5)) == {1}
let dom = ctx.interval(&ctx.int(0), &ctx.int(5), IntervalKind::Closed);
assert_eq!(f.stationary_points(&x, Some(&dom)).unwrap().to_string(), "{1}");
// SymPy: stationary_points(sin(x), x, Interval(0, 2*pi)) == {pi/2, 3*pi/2}
let two_pi = ctx.interval(&ctx.int(0), &(&ctx.pi() * 2), IntervalKind::Closed);
let sp = x.sin().stationary_points(&x, Some(&two_pi)).unwrap();
assert_eq!(sp.as_finite_set().unwrap().len(), 2);
// SymPy: stationary_points(Abs(x - 1) + x**2, x, Interval(-1, 2)) == {1/2}
let g = (&x - 1).abs() + x.powi(2);
let dom = ctx.interval(&ctx.int(-1), &ctx.int(2), IntervalKind::Closed);
assert_eq!(g.stationary_points(&x, Some(&dom)).unwrap().to_string(), "{1/2}");Sourcepub fn maximum(&self, var: &Ex, domain: &SetEx) -> Result<Ex, SymplexError>
pub fn maximum(&self, var: &Ex, domain: &SetEx) -> Result<Ex, SymplexError>
Supremum of self (continuous in var) over domain, a union of
intervals — SymPy’s maximum.
The candidates are the values at the stationary points and abs
kinks inside the domain (see stationary_points
for how |h| is handled: maximum(|x|, [−1, 2]) = 2,
minimum(|x − 1| + x², [−1, 2]) = 3/4), at closed endpoints, and
the one-sided limits at open or infinite endpoints; +∞ / −∞ are
legitimate results. Candidates are compared exactly (see the module
notes for the numeric fallbacks). The supremum need not be
attained: maximum(x, (0, 1)) = 1.
§Errors
InvalidArgument—varis not a symbol, ordomainis empty or not a union of intervals.ComputationFailed—selfhas singularities inside the domain, contains a discontinuous or opaque node (floor,sign,Piecewise, an unknown function, …), its stationary points cannot be enumerated, an endpoint limit cannot be computed, or two candidates cannot be compared.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
let f = &x.powi(3) - &x * 3;
let dom = ctx.interval(&ctx.int(-2), &ctx.int(2), IntervalKind::Closed);
// SymPy: maximum(x**3 - 3*x, x, Interval(-2, 2)) == 2
assert_eq!(f.maximum(&x, &dom).unwrap().to_string(), "2");
// SymPy: maximum(x**2, x, S.Reals) == oo
assert_eq!(x.powi(2).maximum(&x, &ctx.reals()).unwrap(), ctx.infinity());
// SymPy: maximum(1/x, x, Interval(1, oo)) == 1
let tail = ctx.interval(&ctx.int(1), &ctx.infinity(), IntervalKind::RightOpen);
assert_eq!((1 / &x).maximum(&x, &tail).unwrap().to_string(), "1");Sourcepub fn minimum(&self, var: &Ex, domain: &SetEx) -> Result<Ex, SymplexError>
pub fn minimum(&self, var: &Ex, domain: &SetEx) -> Result<Ex, SymplexError>
Infimum of self (continuous in var) over domain — SymPy’s
minimum. Same method, candidates and errors as
maximum.
§Errors
See maximum.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
let f = &x.powi(3) - &x * 3;
let dom = ctx.interval(&ctx.int(-2), &ctx.int(2), IntervalKind::Closed);
// SymPy: minimum(x**3 - 3*x, x, Interval(-2, 2)) == -2
assert_eq!(f.minimum(&x, &dom).unwrap().to_string(), "-2");
// SymPy: minimum(1/x, x, Interval(1, oo)) == 0 (a limit, not attained)
let tail = ctx.interval(&ctx.int(1), &ctx.infinity(), IntervalKind::RightOpen);
assert_eq!((1 / &x).minimum(&x, &tail).unwrap().to_string(), "0");
// SymPy: minimum(x**2, x, S.Reals) == 0
assert_eq!(x.powi(2).minimum(&x, &ctx.reals()).unwrap().to_string(), "0");Sourcepub fn is_increasing(&self, var: &Ex, domain: &SetEx) -> Option<bool>
pub fn is_increasing(&self, var: &Ex, domain: &SetEx) -> Option<bool>
Is self non-decreasing in var on domain (f' ≥ 0 there)?
SymPy’s is_increasing.
Three-valued. Polynomial and rational derivatives with rational
coefficients are decided exactly (Sturm sequences on each interval of
the domain, the denominator having constant sign there); otherwise
the assumption system and the inequality solver
(solve_ge) are consulted. A derivative that is
undefined at a closed endpoint (√x at 0) is tested on the
interior instead, provided the function is continuous there.
None means undecided — never a guess; expressions with
discontinuous or opaque nodes (floor, sign, unknown functions)
are always None. The domain must be a union of intervals (None
otherwise); the empty domain is vacuously Some(true).
A pole strictly inside the domain refutes monotonicity regardless of
the sign of f' on either side: 1/x is not decreasing on
[−1, 1] (f(−1) = −1 < 1 = f(1)) and tan x is not increasing
on [0, π], although f' < 0 resp. f' > 0 wherever it is
defined. (SymPy tests the derivative alone and answers True for
is_increasing(tan(x), Interval(0, pi)).) When the singularities
inside the domain cannot be enumerated (tan x on ℝ) the answer is
None.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
let reals = ctx.reals();
// SymPy: is_increasing(x**3, S.Reals, x) is True
assert_eq!(x.powi(3).is_increasing(&x, &reals), Some(true));
// SymPy: is_increasing(x**2, S.Reals, x) is False
assert_eq!(x.powi(2).is_increasing(&x, &reals), Some(false));
// SymPy: is_increasing(x**2, Interval(0, oo), x) is True
let half = ctx.interval(&ctx.int(0), &ctx.infinity(), IntervalKind::RightOpen);
assert_eq!(x.powi(2).is_increasing(&x, &half), Some(true));
// SymPy: is_increasing(exp(x), S.Reals, x) is True
assert_eq!(x.exp().is_increasing(&x, &reals), Some(true));Sourcepub fn is_decreasing(&self, var: &Ex, domain: &SetEx) -> Option<bool>
pub fn is_decreasing(&self, var: &Ex, domain: &SetEx) -> Option<bool>
Is self non-increasing in var on domain (f' ≤ 0 there)?
SymPy’s is_decreasing. Same method as
is_increasing.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
// SymPy: is_decreasing(x**2, Interval(-oo, 0), x) is True
let left = ctx.interval(&ctx.neg_infinity(), &ctx.int(0), IntervalKind::LeftOpen);
assert_eq!(x.powi(2).is_decreasing(&x, &left), Some(true));
// SymPy: is_decreasing(1/x, Interval.open(0, oo), x) is True
let pos = ctx.interval(&ctx.int(0), &ctx.infinity(), IntervalKind::Open);
assert_eq!((1 / &x).is_decreasing(&x, &pos), Some(true));
assert_eq!(x.powi(3).is_decreasing(&x, &ctx.reals()), Some(false));Sourcepub fn is_strictly_increasing(&self, var: &Ex, domain: &SetEx) -> Option<bool>
pub fn is_strictly_increasing(&self, var: &Ex, domain: &SetEx) -> Option<bool>
Is self strictly increasing in var on domain? SymPy’s
is_strictly_increasing.
Decided as f' ≥ 0 with only isolated zeros: for polynomial and
rational derivatives this is exact (x³ is strictly increasing on
ℝ although f'(0) = 0, and so is x² on [0, ∞)); otherwise
Some(true) needs f' > 0 on the domain or a finite zero set from
the inequality solver, Some(false) needs f' < 0 somewhere, and
anything else is None. (SymPy tests domain ⊆ {f' > 0} and
answers None / False for x³ on ℝ; the mathematically correct
answer is returned here.)
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
assert_eq!(x.powi(3).is_strictly_increasing(&x, &ctx.reals()), Some(true));
assert_eq!(x.powi(2).is_strictly_increasing(&x, &ctx.reals()), Some(false));
// A constant is increasing but not strictly.
assert_eq!(ctx.int(3).is_increasing(&x, &ctx.reals()), Some(true));
assert_eq!(ctx.int(3).is_strictly_increasing(&x, &ctx.reals()), Some(false));Sourcepub fn is_strictly_decreasing(&self, var: &Ex, domain: &SetEx) -> Option<bool>
pub fn is_strictly_decreasing(&self, var: &Ex, domain: &SetEx) -> Option<bool>
Is self strictly decreasing in var on domain? SymPy’s
is_strictly_decreasing; see
is_strictly_increasing.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
assert_eq!((-&x.powi(3)).is_strictly_decreasing(&x, &ctx.reals()), Some(true));
let pos = ctx.interval(&ctx.int(0), &ctx.infinity(), IntervalKind::Open);
assert_eq!((1 / &x).is_strictly_decreasing(&x, &pos), Some(true));Sourcepub fn is_monotonic(&self, var: &Ex, domain: &SetEx) -> Option<bool>
pub fn is_monotonic(&self, var: &Ex, domain: &SetEx) -> Option<bool>
Is self monotonic (non-decreasing or non-increasing) in var on
domain? SymPy’s is_monotonic.
The three-valued disjunction of is_increasing
and is_decreasing: Some(true) when either
is proven, Some(false) when both are refuted, None otherwise.
(SymPy’s is_monotonic instead asks whether f' has no zeros in
the domain and therefore answers False for x³ on ℝ.)
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
assert_eq!(x.powi(3).is_monotonic(&x, &ctx.reals()), Some(true));
assert_eq!((-&x).is_monotonic(&x, &ctx.reals()), Some(true));
assert_eq!(x.powi(2).is_monotonic(&x, &ctx.reals()), Some(false));Sourcepub fn is_convex(&self, var: &Ex, domain: &SetEx) -> Option<bool>
pub fn is_convex(&self, var: &Ex, domain: &SetEx) -> Option<bool>
Is self convex in var on domain (f'' ≥ 0 there)? SymPy’s
is_convex for one variable.
Same machinery as is_increasing, applied to
the first derivative. Three-valued.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
// SymPy: is_convex(x**2, x) is True, is_convex(x**3, x) is False
assert_eq!(x.powi(2).is_convex(&x, &ctx.reals()), Some(true));
assert_eq!(x.powi(3).is_convex(&x, &ctx.reals()), Some(false));
// SymPy: is_convex(x**3, x, domain=Interval(0, oo)) is True
let half = ctx.interval(&ctx.int(0), &ctx.infinity(), IntervalKind::RightOpen);
assert_eq!(x.powi(3).is_convex(&x, &half), Some(true));
assert_eq!(x.exp().is_convex(&x, &ctx.reals()), Some(true));Sourcepub fn periodicity(&self, var: &Ex) -> Option<Ex>
pub fn periodicity(&self, var: &Ex) -> Option<Ex>
A period of self in var — SymPy’s periodicity. Like
SymPy’s, the value is a period, not necessarily the fundamental
one: composite expressions get the lcm of the periods of their
pieces, and identities that shorten the period are not detected
(sin²x·cos²x = sin²(2x)/4 gives π, whose fundamental period is
π/2 — SymPy answers π/2 here through its own simplification).
Some(0)whenselfdoes not depend onvar.sin(a·x + b),cos(a·x + b)→2π/|a|;tan(a·x + b)→π/|a|;sec,csc,cot(which are built fromsin/cos) follow, with productssin(g)ᵖ·cos(g)ᵠof even exponent sum (sin·cos,cos/sin,sin²) and|sin g|,|cos g|getting the half periodπ/|a|.- Sums, products, powers and compositions (
exp(sin x),sin(2x) + cos(3x)) take the lcm of the periods of theirvar-dependent parts; the lcm needs pairwise rational ratios. Nonewhen avar-dependent part is not recognised as periodic (x²,sin(x²),sin(x) + x,sin(√2·x) + sin(x)).
The expression is simplified first (sin²x + cos²x → 1 →
Some(0)); the original form is tried if the simplified one is not
recognised. Note that SymPy reports 2π for sin(x)²; the
half-period rule gives π here.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
let p = |e: &Ex| e.periodicity(&x).map(|p| p.to_string());
// SymPy: periodicity(sin(2*x) + cos(3*x), x) == 2*pi
assert_eq!(p(&(&(&x * 2).sin() + &(&x * 3).cos())), Some("2*pi".into()));
// SymPy: periodicity(tan(x), x) == pi
assert_eq!(p(&x.tan()), Some("pi".into()));
// SymPy: periodicity(sin(3*x + 1), x) == 2*pi/3
assert_eq!(p(&(&x * 3 + 1).sin()), Some("2/3*pi".into()));
// SymPy: periodicity(S(3), x) == 0; periodicity(x**2, x) is None
assert_eq!(p(&ctx.int(3)), Some("0".into()));
assert_eq!(p(&x.powi(2)), None);Sourcepub fn function_range(
&self,
var: &Ex,
domain: &SetEx,
) -> Result<SetEx, SymplexError>
pub fn function_range( &self, var: &Ex, domain: &SetEx, ) -> Result<SetEx, SymplexError>
The image of self (continuous in var) over domain, a union of
intervals — SymPy’s function_range.
On each interval of the domain the infimum and supremum are found
as in minimum / maximum; the image
of that interval is [inf, sup] with an endpoint open exactly when
the value is only approached (a one-sided limit at an open or
infinite endpoint that is not also attained elsewhere) or infinite.
The pieces are united and simplified.
§Errors
InvalidArgument—varis not a symbol, ordomainis not a union of intervals (the empty domain gives the empty set).ComputationFailed— as formaximum.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
let r = |f: &Ex, d: &SetEx| f.function_range(&x, d).unwrap().to_string();
// SymPy: function_range(sin(x), x, Interval(0, pi)) == Interval(0, 1)
assert_eq!(r(&x.sin(), &ctx.interval(&ctx.int(0), &ctx.pi(), IntervalKind::Closed)), "[0, 1]");
// SymPy: function_range(x**2, x, S.Reals) == Interval(0, oo)
assert_eq!(r(&x.powi(2), &ctx.reals()), "[0, oo)");
// SymPy: function_range(1/x, x, Interval(1, oo)) == Interval.Lopen(0, 1)
let tail = ctx.interval(&ctx.int(1), &ctx.infinity(), IntervalKind::RightOpen);
assert_eq!(r(&(1 / &x), &tail), "(0, 1]");
// SymPy: function_range(exp(x), x, S.Reals) == Interval.open(0, oo)
assert_eq!(r(&x.exp(), &ctx.reals()), "(0, oo)");Source§impl Expr<Numeric>
impl Expr<Numeric>
Sourcepub fn re(&self) -> Ex
pub fn re(&self) -> Ex
Real part re(self).
Evaluates at construction whenever the real part is determinable
(numbers, constants, symbols assumed real, sums, real scalings,
products/powers of fully decomposable factors, exp, sin, cos,
sinh, cosh, ln, …). Otherwise an unevaluated re(…) node is
returned — never a silently-wrong answer.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let i = ctx.i_unit();
let z = &ctx.int(3) + &(&ctx.int(4) * &i);
assert_eq!(format!("{}", z.re()), "3");
// Unknown symbols stay symbolic …
let w = ctx.symbol("w");
assert_eq!(format!("{}", w.re()), "re(w)");
assert_eq!(format!("{}", (&i * &w).re()), "-im(w)");
// … unless assumed real.
let x = ctx.symbol_with("x", &[Assumption::Real]);
assert_eq!(x.re(), x);Sourcepub fn im(&self) -> Ex
pub fn im(&self) -> Ex
Imaginary part im(self) — a real quantity such that
self = re(self) + i·im(self).
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let i = ctx.i_unit();
let z = &ctx.int(3) + &(&ctx.int(4) * &i);
assert_eq!(format!("{}", z.im()), "4");
let x = ctx.symbol_with("x", &[Assumption::Real]);
assert!(x.im().is_zero_structural());
assert_eq!(format!("{}", (&x.exp() * &i).im()), "exp(x)");Sourcepub fn conjugate(&self) -> Ex
pub fn conjugate(&self) -> Ex
Complex conjugate conjugate(self).
Distributes over sums, products and integer powers, and commutes
with real-analytic functions that have no branch cut off the real
axis (exp, sin, cos, tan, sinh, cosh, tanh, Γ,
erf, erfc, ψ, ζ, Si). Functions with branch cuts (ln,
non-integer powers, inverse trig/hyperbolic, W, …) stay as
unevaluated conjugate(…) nodes unless the argument is provably
real.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let i = ctx.i_unit();
let z = &ctx.int(3) + &(&ctx.int(4) * &i);
assert_eq!(format!("{}", z.conjugate()), "-4*I + 3");
let w = ctx.symbol("w");
assert_eq!(format!("{}", w.sin().conjugate()), "sin(conjugate(w))");
assert_eq!(w.conjugate().conjugate(), w);Sourcepub fn arg(&self) -> Ex
pub fn arg(&self) -> Ex
Principal complex argument arg(self) ∈ (−π, π].
Positive reals give 0, negative reals give π, i gives π/2,
and a + bi with real a, b gives atan2(b, a) (folded for
numeric arguments). Positive real factors are discarded:
arg(3·z) = arg(z). When the sign or complex structure cannot be
determined the result is an unevaluated arg(…) node.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let z = &ctx.int(1) + &ctx.i_unit();
assert_eq!(format!("{}", z.arg()), "1/4*pi");
assert_eq!(format!("{}", ctx.int(-2).arg()), "pi");
let x = ctx.symbol_with("x", &[Assumption::Real]);
assert_eq!(format!("{}", x.arg()), "arg(x)"); // sign unknownSourcepub fn as_real_imag(&self) -> (Ex, Ex)
pub fn as_real_imag(&self) -> (Ex, Ex)
Decompose into (re, im) with self = re + i·im.
Both parts are real-valued expressions; unknown quantities appear as
re(…)/im(…) nodes.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol_with("x", &[Assumption::Real]);
let z = (&ctx.i_unit() * &x).exp(); // e^{ix}
let (re, im) = z.as_real_imag();
assert_eq!(format!("{re}"), "cos(x)");
assert_eq!(format!("{im}"), "sin(x)");Sourcepub fn expand_complex(&self) -> Ex
pub fn expand_complex(&self) -> Ex
Rewrite as re + i·im, expanding every unknown symbol z into
re(z) + i·im(z) (symbols assumed real are left alone).
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let z = ctx.symbol("z");
assert_eq!(format!("{}", z.expand_complex()), "im(z)*I + re(z)");
let x = ctx.symbol_with("x", &[Assumption::Real]);
let e = (&ctx.i_unit() * &x).exp().expand_complex();
assert_eq!(format!("{e}"), "sin(x)*I + cos(x)");Sourcepub fn polar(&self) -> (Ex, Ex)
pub fn polar(&self) -> (Ex, Ex)
Polar form (|self|, arg(self)).
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let z = &ctx.int(3) + &(&ctx.int(4) * &ctx.i_unit());
let (r, theta) = z.polar();
assert!((r.eval_f64().unwrap() - 5.0).abs() < 1e-12);
assert!((theta.eval_f64().unwrap() - (4.0f64).atan2(3.0)).abs() < 1e-12);Sourcepub fn abs_squared(&self) -> Ex
pub fn abs_squared(&self) -> Ex
|self|² = re² + im² = self·conjugate(self).
When the real/imaginary decomposition is fully determined the result
is re² + im²; otherwise it is self·conjugate(self).
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let z = &ctx.int(3) + &(&ctx.int(4) * &ctx.i_unit());
assert_eq!(format!("{}", z.abs_squared().eval()), "25");
let w = ctx.symbol("w");
assert_eq!(format!("{}", w.abs_squared()), "w*conjugate(w)");Sourcepub fn is_real_valued(&self) -> Option<bool>
pub fn is_real_valued(&self) -> Option<bool>
Three-valued test: is this expression real-valued?
Some(true) if provably real (assumptions, or a decomposition with a
structurally zero imaginary part), Some(false) if provably not real
(e.g. a non-zero numeric imaginary part), None if unknown.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let i = ctx.i_unit();
assert_eq!(ctx.int(3).is_real_valued(), Some(true));
assert_eq!((&ctx.int(3) + &i).is_real_valued(), Some(false));
assert_eq!(ctx.symbol("z").is_real_valued(), None);
assert_eq!(ctx.symbol("z").abs().is_real_valued(), Some(true));
let x = ctx.symbol_with("x", &[Assumption::Real]);
assert_eq!((&x + &i).is_real_valued(), Some(false));Source§impl Expr<Numeric>
impl Expr<Numeric>
Sourcepub fn si(&self) -> Ex
pub fn si(&self) -> Ex
Sine integral Si(self) = ∫₀ˣ sin(t)/t dt.
Exact values: Si(0) = 0, Si(∞) = π/2, Si(−x) = −Si(x).
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
assert_eq!(format!("{}", x.si()), "Si(x)");
assert_eq!(format!("{}", x.si().diff(&x)), "sin(x)/x");
let v = ctx.int(1).si().eval_f64().unwrap();
assert!((v - 0.946083070367183).abs() < 1e-14);Sourcepub fn ci(&self) -> Ex
pub fn ci(&self) -> Ex
Cosine integral Ci(self) = γ + ln(x) + ∫₀ˣ (cos(t) − 1)/t dt.
Exact values: Ci(∞) = 0. For x < 0 the numerical value is
complex: Ci(−x) = Ci(x) + iπ.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let v = ctx.int(1).ci().eval_f64().unwrap();
assert!((v - 0.337403922900968).abs() < 1e-14);Sourcepub fn ei(&self) -> Ex
pub fn ei(&self) -> Ex
Exponential integral Ei(self) = −∫_{−x}^{∞} e^{−t}/t dt (Cauchy
principal value for x > 0).
Exact values: Ei(−∞) = 0, Ei(∞) = ∞.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let v = ctx.int(1).ei().eval_f64().unwrap();
assert!((v - 1.895117816355937).abs() < 1e-14);
let x = ctx.symbol("x");
assert_eq!(format!("{}", x.ei().diff(&x)), "exp(x)/x");Sourcepub fn li(&self) -> Ex
pub fn li(&self) -> Ex
Logarithmic integral li(self) = ∫₀ˣ dt/ln(t) = Ei(ln x).
Exact values: li(0) = 0, li(1) = −∞, li(e^y) = Ei(y).
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let v = ctx.int(2).li().eval_f64().unwrap();
assert!((v - 1.045163780117493).abs() < 1e-14);
let x = ctx.symbol("x");
assert_eq!(format!("{}", x.li().diff(&x)), "1/ln(x)");Sourcepub fn zeta(&self) -> Ex
pub fn zeta(&self) -> Ex
Riemann zeta function ζ(self).
Exact values: ζ(1) = zoo, ζ(0) = −1/2, ζ(−n) = −Bₙ₊₁/(n+1),
ζ(2k) as a rational multiple of π^{2k}; odd positive arguments
stay symbolic.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
assert_eq!(format!("{}", ctx.int(2).zeta()), "1/6*pi^2");
assert_eq!(format!("{}", ctx.int(4).zeta()), "1/90*pi^4");
assert_eq!(format!("{}", ctx.int(-1).zeta()), "-1/12");
assert_eq!(format!("{}", ctx.int(3).zeta()), "zeta(3)");
let v = ctx.int(3).zeta().eval_f64().unwrap();
assert!((v - 1.202056903159594).abs() < 1e-14);Sourcepub fn polygamma(&self, n: &Ex) -> Ex
pub fn polygamma(&self, n: &Ex) -> Ex
Polygamma function ψ⁽ⁿ⁾(self) — the n-th derivative of the
digamma function.
ψ⁽⁰⁾ is canonicalised to digamma; ψ⁽ⁿ⁾(1),
ψ⁽ⁿ⁾(1/2) and small integer / half-integer shifts of them fold to
multiples of ζ(n+1).
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let one = ctx.int(1);
// trigamma(1) = ζ(2) = π²/6
assert_eq!(format!("{}", one.polygamma(&one)), "1/6*pi^2");
let x = ctx.symbol("x");
assert_eq!(format!("{}", x.digamma().diff(&x)), "polygamma(1, x)");
let v = ctx.int(1).polygamma(&one).eval_f64().unwrap();
assert!((v - std::f64::consts::PI.powi(2) / 6.0).abs() < 1e-14);Sourcepub fn kronecker_delta(&self, other: &Ex) -> Ex
pub fn kronecker_delta(&self, other: &Ex) -> Ex
Kronecker delta δ(self, other): 1 if the arguments are equal,
0 if they are provably different numbers, otherwise symbolic.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let i = ctx.symbol("i");
let j = ctx.symbol("j");
assert_eq!(format!("{}", i.kronecker_delta(&i)), "1");
assert_eq!(format!("{}", ctx.int(2).kronecker_delta(&ctx.int(3))), "0");
let d = i.kronecker_delta(&j);
assert_eq!(format!("{d}"), "KroneckerDelta(i, j)");
assert_eq!(d, j.kronecker_delta(&i)); // symmetric
assert_eq!(format!("{}", (&i + 1).kronecker_delta(&i)), "0");Source§impl Expr<Numeric>
impl Expr<Numeric>
Sourcepub fn log(&self, base: &Ex) -> Ex
pub fn log(&self, base: &Ex) -> Ex
Logarithm with arbitrary base: log_base(self).
Computed as ln(self) / ln(base).
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
let result = x.log(&ctx.int(2));
let s = format!("{result}");
assert!(s.contains("ln"), "log should be expressed in terms of ln, got: {s}");Sourcepub fn atan2(&self, x: &Ex) -> Ex
pub fn atan2(&self, x: &Ex) -> Ex
Two-argument arctangent: atan2(y, x).
Returns the angle in (-π, π] between the positive x-axis and the point (x, y). Correctly handles all four quadrants.
Sourcepub fn min_of(ctx: &Context, exprs: impl IntoIterator<Item = Ex>) -> Ex
pub fn min_of(ctx: &Context, exprs: impl IntoIterator<Item = Ex>) -> Ex
N-ary minimum of a collection of expressions.
Sourcepub fn max_of(ctx: &Context, exprs: impl IntoIterator<Item = Ex>) -> Ex
pub fn max_of(ctx: &Context, exprs: impl IntoIterator<Item = Ex>) -> Ex
N-ary maximum of a collection of expressions.
Sourcepub fn symbolic_sum(body: &Ex, var: &Ex, lower: &Ex, upper: &Ex) -> Ex
pub fn symbolic_sum(body: &Ex, var: &Ex, lower: &Ex, upper: &Ex) -> Ex
Symbolic summation: Sum(body, var=lower..upper) (unevaluated node).
Construction is cheap and does nothing. .eval() runs the symbolic
summation engine (see summation): small concrete
ranges are enumerated exactly, symbolic and infinite bounds get closed
forms where known. Use summation to evaluate
directly without building the node.
Sourcepub fn symbolic_product(body: &Ex, var: &Ex, lower: &Ex, upper: &Ex) -> Ex
pub fn symbolic_product(body: &Ex, var: &Ex, lower: &Ex, upper: &Ex) -> Ex
Symbolic product: Product(body, var=lower..upper) (unevaluated node).
When evaluated (.eval()), if lower and upper are concrete integers,
the product is computed by substituting each integer value for var in
body. Use product_over for closed forms with
symbolic or infinite bounds.
Sourcepub fn is_convergent(&self, var: &Ex) -> Option<bool>
pub fn is_convergent(&self, var: &Ex) -> Option<bool>
Test whether the infinite series Σ_{k=1}^{∞} self(var) converges
(absolutely or conditionally).
Returns Some(true) if the series converges, Some(false) if it
diverges, or None if the tests are inconclusive — never a guess.
Tests applied: divergence test, rational-function degree test,
exact Stirling growth analysis for hypergeometric-type terms (this
subsumes the ratio, root, p-series and alternating-series tests),
direct comparison for bounded factors (sin, cos), and the
integral test for log-exp terms such as 1/(k ln² k). See also
is_absolutely_convergent.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let k = ctx.symbol("k");
// 1/k² converges (p-series with p=2)
assert_eq!(k.powi(-2).is_convergent(&k), Some(true));
// k!/k^k converges (ratio test, limit 1/e)
assert_eq!((k.factorial() / k.pow(&k)).is_convergent(&k), Some(true));
// k/(k+1) diverges (terms do not tend to zero)
assert_eq!((&k / &(&k + 1)).is_convergent(&k), Some(false));Sourcepub fn closed_form_sum(&self) -> Ex
pub fn closed_form_sum(&self) -> Ex
Attempt closed-form evaluation of a symbolic sum.
Given a sum Σ_{var=lower}^{upper} body, tries to find a closed-form
expression using Faulhaber formulas, geometric series, linearity, etc.
Returns the closed form if found, or the original sum expression unchanged.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let k = ctx.symbol("k");
let n = ctx.symbol("n");
let s = Ex::symbolic_sum(&k, &k, &ctx.int(1), &n);
let closed = s.closed_form_sum();
// Should give n*(n+1)/2Sourcepub fn gamma(&self) -> Ex
pub fn gamma(&self) -> Ex
Gamma function: Γ(self).
For positive integer arguments, .eval() computes (n-1)!.
Gamma(1/2) = √π.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let result = ctx.int(5).gamma().eval();
assert_eq!(format!("{result}"), "24");Sourcepub fn log_gamma(&self) -> Ex
pub fn log_gamma(&self) -> Ex
Log-gamma function: ln(Γ(self)).
For positive integer arguments, .eval() computes ln((n-1)!).
Sourcepub fn erf(&self) -> Ex
pub fn erf(&self) -> Ex
Error function: erf(self) = 2/√π ∫₀ˢᵉˡᶠ e^(-t²) dt.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let result = ctx.int(0).erf().eval();
assert_eq!(format!("{result}"), "0");Sourcepub fn erfc(&self) -> Ex
pub fn erfc(&self) -> Ex
Complementary error function: erfc(self) = 1 - erf(self).
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let result = ctx.int(0).erfc().eval();
assert_eq!(format!("{result}"), "1");Sourcepub fn beta(&self, other: &Ex) -> Ex
pub fn beta(&self, other: &Ex) -> Ex
Beta function: B(self, other) = Γ(self)Γ(other)/Γ(self+other).
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let result = ctx.int(2).beta(&ctx.int(3)).eval();
assert_eq!(format!("{result}"), "1/12");Sourcepub fn factorial(&self) -> Ex
pub fn factorial(&self) -> Ex
Creates a Factorial node. For non-negative integer arguments,
.eval() will compute the exact value using arbitrary-precision
arithmetic.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let result = ctx.int(5).factorial().eval();
assert_eq!(format!("{result}"), "120");Sourcepub fn binomial(&self, k: &Ex) -> Ex
pub fn binomial(&self, k: &Ex) -> Ex
Compute the binomial coefficient C(self, k).
Creates a Binomial(self, k) node. For non-negative integer
arguments, .eval() will compute the exact value.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let result = ctx.int(10).binomial(&ctx.int(3)).eval();
assert_eq!(format!("{result}"), "120");Sourcepub fn factorial2(&self) -> Ex
pub fn factorial2(&self) -> Ex
Double factorial: self!!.
For non-negative integer arguments, .eval() computes the exact value.
0!! = 1, 1!! = 1, (-1)!! = 1.
Sourcepub fn subfactorial(&self) -> Ex
pub fn subfactorial(&self) -> Ex
Subfactorial (derangement count): !self.
For non-negative integer arguments, .eval() computes the exact value.
Sourcepub fn rising_factorial(&self, n: &Ex) -> Ex
pub fn rising_factorial(&self, n: &Ex) -> Ex
Rising factorial (Pochhammer symbol): (self)_n.
rising_factorial(x, n) = x * (x+1) * ... * (x+n-1).
Sourcepub fn falling_factorial(&self, n: &Ex) -> Ex
pub fn falling_factorial(&self, n: &Ex) -> Ex
Falling factorial: self^(n) = self * (self-1) * ... * (self-n+1).
Sourcepub fn fibonacci(&self) -> Ex
pub fn fibonacci(&self) -> Ex
Fibonacci number: F(self).
For non-negative integer arguments, .eval() computes the exact value.
F(0) = 0, F(1) = 1, F(n) = F(n-1) + F(n-2).
Sourcepub fn lucas(&self) -> Ex
pub fn lucas(&self) -> Ex
Lucas number: L(self).
For non-negative integer arguments, .eval() computes the exact value.
L(0) = 2, L(1) = 1, L(n) = L(n-1) + L(n-2).
Sourcepub fn bernoulli_number(&self) -> Ex
pub fn bernoulli_number(&self) -> Ex
Bernoulli number: B(self).
For non-negative integer arguments, .eval() computes the exact value.
B(0) = 1, B(1) = -1/2, B(2) = 1/6.
Sourcepub fn harmonic(&self) -> Ex
pub fn harmonic(&self) -> Ex
Harmonic number: H(self) = 1 + 1/2 + ... + 1/self.
For non-negative integer arguments, .eval() computes the exact value.
H(0) = 0.
Sourcepub fn catalan_number(&self) -> Ex
pub fn catalan_number(&self) -> Ex
Catalan number: C(self) = (2n)! / ((n+1)! * n!).
For non-negative integer arguments, .eval() computes the exact value.
Sourcepub fn bell(&self) -> Ex
pub fn bell(&self) -> Ex
Bell number: B(self).
For non-negative integer arguments, .eval() computes the exact value.
B(0) = 1, B(1) = 1, B(2) = 2, B(3) = 5.
Sourcepub fn euler_number(&self) -> Ex
pub fn euler_number(&self) -> Ex
Euler number: E(self).
For non-negative integer arguments, .eval() computes the exact value.
Odd indices are 0. E(0) = 1, E(2) = -1, E(4) = 5.
Sourcepub fn stirling2(&self, k: &Ex) -> Ex
pub fn stirling2(&self, k: &Ex) -> Ex
Stirling number of the second kind: S(self, k).
Counts the number of ways to partition a set of self elements
into exactly k non-empty subsets.
For non-negative integer arguments, .eval() computes the exact value.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let result = ctx.int(5).stirling2(&ctx.int(3)).eval();
assert_eq!(format!("{result}"), "25");Sourcepub fn stirling1(&self, k: &Ex) -> Ex
pub fn stirling1(&self, k: &Ex) -> Ex
Signed Stirling number of the first kind: s(self, k).
Related to the number of permutations of self elements with
exactly k cycles. Satisfies x^{(n)} = Σ_k s(n, k) x^k
where x^{(n)} is the falling factorial.
For non-negative integer arguments, .eval() computes the exact value.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let result = ctx.int(4).stirling1(&ctx.int(2)).eval();
assert_eq!(format!("{result}"), "11");Sourcepub fn partition_count(&self) -> Ex
pub fn partition_count(&self) -> Ex
Number of integer partitions of self.
An integer partition of n is a way to write n as a sum of
positive integers (order doesn’t matter).
For non-negative integer arguments, .eval() computes the exact value.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let result = ctx.int(5).partition_count().eval();
assert_eq!(format!("{result}"), "7");Sourcepub fn dirac_delta(&self) -> Ex
pub fn dirac_delta(&self) -> Ex
Dirac delta distribution: 0 for x≠0, symbolic at x=0.
Sourcepub fn le(&self, other: &Ex) -> BoolEx
pub fn le(&self, other: &Ex) -> BoolEx
Less than or equal: self <= other (implemented as other >= self).
Sourcepub fn eq_expr(&self, other: &Ex) -> BoolEx
pub fn eq_expr(&self, other: &Ex) -> BoolEx
Mathematical equality test (boolean-valued): self == other.
Sourcepub fn piecewise(pairs: &[(&Ex, &BoolEx)]) -> Ex
pub fn piecewise(pairs: &[(&Ex, &BoolEx)]) -> Ex
Piecewise function from (value, condition) pairs.
Returns the value of the first pair whose condition is true.
Sourcepub fn is_positive(&self) -> Option<bool>
pub fn is_positive(&self) -> Option<bool>
Query whether this expression is positive.
Returns Some(true) if provably positive, Some(false) if
provably not positive, or None if unknown.
Sourcepub fn is_zero(&self) -> Option<bool>
pub fn is_zero(&self) -> Option<bool>
Query whether this expression is zero.
Uses layered detection:
- Structural identity with 0 (O(1))
- Assumption system query
Returns Some(true) if provably zero, Some(false) if provably
nonzero, or None if unknown.
Sourcepub fn query(&self, prop: Props) -> Option<bool>
pub fn query(&self, prop: Props) -> Option<bool>
Query any mathematical property via Props.
Returns Some(true) if provably true, Some(false) if provably
false, or None if unknown.
Sourcepub fn is_negative(&self) -> Option<bool>
pub fn is_negative(&self) -> Option<bool>
Query whether this expression is negative.
Returns Some(true) if provably negative, Some(false) if
provably not negative, or None if unknown.
Sourcepub fn is_real(&self) -> Option<bool>
pub fn is_real(&self) -> Option<bool>
Query whether this expression is real.
Returns Some(true) if provably real, Some(false) if
provably not real, or None if unknown.
Sourcepub fn is_integer(&self) -> Option<bool>
pub fn is_integer(&self) -> Option<bool>
Query whether this expression is an integer.
Returns Some(true) if provably an integer, Some(false) if
provably not an integer, or None if unknown.
Sourcepub fn is_nonzero(&self) -> Option<bool>
pub fn is_nonzero(&self) -> Option<bool>
Query whether this expression is nonzero.
Returns Some(true) if provably nonzero, Some(false) if
provably zero, or None if unknown.
Sourcepub fn is_finite(&self) -> Option<bool>
pub fn is_finite(&self) -> Option<bool>
Query whether this expression is finite.
Returns Some(true) if provably finite, Some(false) if
provably not finite, or None if unknown.
Sourcepub fn is_nonnegative(&self) -> Option<bool>
pub fn is_nonnegative(&self) -> Option<bool>
Returns Some(true) if this expression is known to be ≥ 0.
Sourcepub fn is_nonpositive(&self) -> Option<bool>
pub fn is_nonpositive(&self) -> Option<bool>
Returns Some(true) if this expression is known to be ≤ 0.
Sourcepub fn is_imaginary(&self) -> Option<bool>
pub fn is_imaginary(&self) -> Option<bool>
Returns Some(true) if this expression is known to be imaginary.
Sourcepub fn is_complex(&self) -> Option<bool>
pub fn is_complex(&self) -> Option<bool>
Returns Some(true) if this expression is known to be complex.
Sourcepub fn is_rational(&self) -> Option<bool>
pub fn is_rational(&self) -> Option<bool>
Returns Some(true) if this expression is known to be rational.
Sourcepub fn is_composite(&self) -> Option<bool>
pub fn is_composite(&self) -> Option<bool>
Returns whether this expression is known to be composite.
Sourcepub fn is_algebraic(&self) -> Option<bool>
pub fn is_algebraic(&self) -> Option<bool>
Returns whether this expression is known to be algebraic.
Sourcepub fn is_transcendental(&self) -> Option<bool>
pub fn is_transcendental(&self) -> Option<bool>
Returns whether this expression is known to be transcendental.
Sourcepub fn is_irrational(&self) -> Option<bool>
pub fn is_irrational(&self) -> Option<bool>
Returns whether this expression is known to be irrational.
Sourcepub fn is_hermitian(&self) -> Option<bool>
pub fn is_hermitian(&self) -> Option<bool>
Returns whether this expression is known to be hermitian.
Sourcepub fn assume(self, assumption: Assumption) -> Ex
pub fn assume(self, assumption: Assumption) -> Ex
Set a mathematical assumption on this expression (must be a symbol).
Returns self for fluent chaining. If this expression is not a
symbol, the assumption is silently ignored.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let t = ctx.symbol("t")
.assume(Assumption::Positive)
.assume(Assumption::Real);
assert_eq!(t.is_positive(), Some(true));
assert_eq!(t.is_real(), Some(true));Sourcepub fn equals(&self, other: &Ex) -> Option<bool>
pub fn equals(&self, other: &Ex) -> Option<bool>
Mathematical equality: attempts to determine whether self == other
as mathematical objects.
Three-valued:
Some(true)—self − otheris structurally zero, or becomes zero afterexpandorsimplify, or the assumption system proves the difference is zero.Some(false)— the difference (possibly after expand/simplify) is a nonzero rational constant, the assumption system proves it nonzero (e.g.x² + 1for realx), or both sides are constants (no free symbols) whose 16-digit numeric values differ by more than1e-9relative.None— undetermined. In particularx.equals(&y)for distinct free symbols isNone, notSome(false); useprobably_equalfor a randomized test.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
assert_eq!((&x + 1).powi(2).equals(&(&x.powi(2) + &x * 2 + 1)), Some(true));
assert_eq!((&x.sin().powi(2) + &x.cos().powi(2)).equals(&ctx.one()), Some(true));
assert_eq!(x.equals(&(&x + 1)), Some(false));
assert_eq!(ctx.int(1).equals(&ctx.int(2)), Some(false));
assert_eq!(ctx.pi().equals(&ctx.rational(22, 7)), Some(false));
assert_eq!(x.equals(&ctx.symbol("y")), None);Sourcepub fn diff(&self, var: &Ex) -> Ex
pub fn diff(&self, var: &Ex) -> Ex
Symbolic differentiation with respect to var.
Computes the derivative using standard rules (linearity, product rule, chain rule, power rule) for all supported node types.
var should be a symbol expression (created via ctx.symbol()).
If var does not appear in the expression, the result is zero.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
let expr = x.powi(3);
let deriv = expr.diff(&x);
assert_eq!(format!("{deriv}"), "3*x^2");Sourcepub fn try_diff(&self, var: &Ex) -> Result<Ex, SymplexError>
pub fn try_diff(&self, var: &Ex) -> Result<Ex, SymplexError>
Like diff, but returns Err if the result contains
unevaluated forms (e.g. formal Derivative nodes).
Sourcepub fn formal_diff(&self, var: &Ex) -> Ex
pub fn formal_diff(&self, var: &Ex) -> Ex
Create a formal (unevaluated) derivative node.
Unlike diff which computes the derivative,
this creates a Derivative(self, var) node that represents
“the derivative of self with respect to var” without evaluating it.
This is used for constructing ODEs.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
let y = ctx.symbol("y");
let dy_dx = y.formal_diff(&x);
let s = format!("{dy_dx}");
assert!(s.contains("Derivative") || s.contains("d/d"), "got: {s}");Sourcepub fn diff_with_dependent(&self, var: &Ex, dependent_vars: &[&Ex]) -> Ex
pub fn diff_with_dependent(&self, var: &Ex, dependent_vars: &[&Ex]) -> Ex
Differentiate treating certain symbols as dependent on var.
For any symbol dep in dependent_vars, d/d(var)(dep) returns
a formal Derivative(dep, var) instead of zero. This enables
implicit differentiation and ODE construction.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
let y = ctx.symbol("y");
let expr = &x.powi(2) + &y.powi(2);
let result = expr.diff_with_dependent(&x, &[&y]);
// d/dx(x² + y²) with y depending on x = 2x + 2y·dy/dxSourcepub fn eval_derivatives(&self) -> Ex
pub fn eval_derivatives(&self) -> Ex
Concretely evaluate all formal Derivative nodes in this expression.
This is the “doit” operation: each Derivative(f, x) node is replaced
by the result of actually differentiating f with respect to x.
Useful after substituting a solution into an ODE for verification.
Sourcepub fn diff_n(&self, var: &Ex, n: usize) -> Ex
pub fn diff_n(&self, var: &Ex, n: usize) -> Ex
Compute the nth derivative with respect to var.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
let f = x.powi(4);
let d3 = f.diff_n(&x, 3);
assert_eq!(format!("{d3}"), "24*x");Sourcepub fn integrate(&self, var: &Ex) -> Ex
pub fn integrate(&self, var: &Ex) -> Ex
Compute the indefinite integral with respect to var.
Supports power rule, trigonometric, exponential, linearity,
and constant factor extraction. For integrands that don’t match
any known rule, returns an unevaluated Integral(body, var) node.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
let expr = x.powi(2);
let anti = expr.integrate(&x);
assert_eq!(format!("{anti}"), "1/3*x^3");Sourcepub fn try_integrate(&self, var: &Ex) -> Result<Ex, SymplexError>
pub fn try_integrate(&self, var: &Ex) -> Result<Ex, SymplexError>
Like integrate, but returns Err if the result
contains unevaluated forms (e.g. formal Integral nodes).
Sourcepub fn series(&self, var: &Ex, point: &Ex, order: u32) -> Ex
pub fn series(&self, var: &Ex, point: &Ex, order: u32) -> Ex
Compute the Taylor / Laurent series around point with every term
of exponent < order in (var − point).
Poles at point give negative powers (1/sin x = 1/x + x/6 + …);
point = ±∞ gives the asymptotic expansion in 1/var (see
series_at_infinity). Elementary
functions use closed-form coefficients, so high orders stay fast.
If no Laurent expansion exists (fractional-power or logarithmic
singularity, essential singularity) a formal Series node is
returned; see try_series.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
let s = x.exp().series(&x, &ctx.int(0), 4);
assert_eq!(s.to_string(), "1/6*x^3 + 1/2*x^2 + x + 1");
// ln x about 1
let s = x.ln().series(&x, &ctx.int(1), 3);
assert_eq!(s.to_string(), "x - 1/2*(x - 1)^2 - 1");
// Laurent expansion at a pole
let s = (&ctx.int(1) / &x.sin()).series(&x, &ctx.int(0), 2);
assert_eq!(s.to_string(), "1/x + 1/6*x");Sourcepub fn try_series(
&self,
var: &Ex,
point: &Ex,
order: u32,
) -> Result<Ex, SymplexError>
pub fn try_series( &self, var: &Ex, point: &Ex, order: u32, ) -> Result<Ex, SymplexError>
Like series, but returns Err if the result
contains unevaluated forms (e.g. a formal Series node).
Sourcepub fn maclaurin(&self, var: &Ex, order: u32) -> Ex
pub fn maclaurin(&self, var: &Ex, order: u32) -> Ex
Compute the Maclaurin series (Taylor series around 0) with every
term of exponent < order.
This is a convenience shorthand for self.series(var, &zero, order)
that avoids needing to construct a zero expression manually.
If the series cannot be computed, returns a formal Series node.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
assert_eq!(x.sin().maclaurin(&x, 6).to_string(), "1/120*x^5 - 1/6*x^3 + x");
assert_eq!(x.atan().maclaurin(&x, 6).to_string(), "1/5*x^5 - 1/3*x^3 + x");
// sqrt(x)·sin(x) is a Puiseux series: kept as a formal node
assert!((&x.sqrt() * &x.sin()).maclaurin(&x, 4).has_unevaluated());Sourcepub fn try_maclaurin(&self, var: &Ex, order: u32) -> Result<Ex, SymplexError>
pub fn try_maclaurin(&self, var: &Ex, order: u32) -> Result<Ex, SymplexError>
Like maclaurin, but returns Err if the result
contains unevaluated forms (e.g. a formal Series node).
Sourcepub fn residue(&self, var: &Ex, point: &Ex) -> Ex
pub fn residue(&self, var: &Ex, point: &Ex) -> Ex
Compute the residue of this expression at var = point.
The residue is the coefficient of 1/(x-a) in the Laurent series
expansion of the function around a. Poles of any order are
handled: the order m is detected from the denominator (or by
limits for non-polynomial denominators) and
Res = 1/(m−1)! · d^{m−1}/dx^{m−1} [(x−a)^m f(x)] at x = a.
Essential singularities and undetectable cases yield a formal
Residue node.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
// Res(1/x, x=0) = 1
let f = &ctx.int(1) / &x;
let result = f.residue(&x, &ctx.int(0));
assert_eq!(format!("{result}"), "1");
// Double pole: Res(e^x / x², x=0) = 1
let g = &x.exp() / &x.powi(2);
assert_eq!(format!("{}", g.residue(&x, &ctx.int(0))), "1");Sourcepub fn try_residue(&self, var: &Ex, point: &Ex) -> Result<Ex, SymplexError>
pub fn try_residue(&self, var: &Ex, point: &Ex) -> Result<Ex, SymplexError>
Like residue, but returns Err if the result
contains unevaluated forms (e.g. a formal Residue node).
Sourcepub fn fourier_series(&self, var: &Ex, n_terms: u32) -> Ex
pub fn fourier_series(&self, var: &Ex, n_terms: u32) -> Ex
Compute the Fourier series of this expression over [-π, π]
with n_terms harmonics.
Returns the truncated Fourier trigonometric series:
a₀/2 + Σ_{n=1}^{N} [aₙ cos(nx) + bₙ sin(nx)]
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
let f = ctx.int(1);
let result = f.fourier_series(&x, 2);
// Fourier series of constant 1 should evaluate to ≈ 1Sourcepub fn laplace(&self, t: &Ex, s: &Ex) -> Ex
pub fn laplace(&self, t: &Ex, s: &Ex) -> Ex
Compute the Laplace transform L{self}(s) with respect to time variable t.
Uses a table-based approach supporting constants, polynomials in t,
exponentials, trigonometric and hyperbolic functions, plus linearity
and the frequency-shift property.
If the transform cannot be computed, returns a formal
LaplaceTransform(…) node (check with has_unevaluated).
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let t = ctx.symbol("t");
let s = ctx.symbol("s");
// L{exp(2t)} = 1/(s-2)
let result = (&t * 2).exp().laplace(&t, &s);Sourcepub fn try_laplace(&self, t: &Ex, s: &Ex) -> Result<Ex, SymplexError>
pub fn try_laplace(&self, t: &Ex, s: &Ex) -> Result<Ex, SymplexError>
Like laplace, but returns Err if the result
contains unevaluated forms (e.g. a formal LaplaceTransform node).
Sourcepub fn inverse_laplace(&self, s: &Ex, t: &Ex) -> Ex
pub fn inverse_laplace(&self, s: &Ex, t: &Ex) -> Ex
Compute the inverse Laplace transform L⁻¹{self}(t) with respect to
frequency variable s.
Uses partial fraction decomposition followed by table lookup for each term.
If the transform cannot be computed, returns a formal
InverseLaplaceTransform(…) node (check with has_unevaluated).
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let t = ctx.symbol("t");
let s = ctx.symbol("s");
// L⁻¹{1/s} = 1
let result = (&ctx.int(1) / &s).inverse_laplace(&s, &t);Sourcepub fn try_inverse_laplace(&self, s: &Ex, t: &Ex) -> Result<Ex, SymplexError>
pub fn try_inverse_laplace(&self, s: &Ex, t: &Ex) -> Result<Ex, SymplexError>
Like inverse_laplace, but returns Err if
the result contains unevaluated forms (e.g. a formal
InverseLaplaceTransform node).
Sourcepub fn limit(&self, var: &Ex, point: &Ex) -> Ex
pub fn limit(&self, var: &Ex, point: &Ex) -> Ex
Compute the limit of this expression as var approaches point.
Uses direct substitution, L’Hôpital’s rule (for 0/0 and ∞/∞),
and series expansion as fallbacks. If the limit cannot be
determined, returns a formal Limit node.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
// lim_{x→0} sin(x)/x = 1
let expr = &x.sin() / &x;
let result = expr.limit(&x, &ctx.int(0));
assert_eq!(format!("{result}"), "1");Sourcepub fn try_limit(&self, var: &Ex, point: &Ex) -> Result<Ex, SymplexError>
pub fn try_limit(&self, var: &Ex, point: &Ex) -> Result<Ex, SymplexError>
Like limit, but returns Err if the limit does not
exist or cannot be computed.
±∞ are legitimate limit values (Ok(oo)), matching the analytic
notion of a limit in the extended reals. When the left and right
limits differ the error reason reads
"left and right limits differ: left = …, right = …".
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
assert_eq!(format!("{}", x.exp().try_limit(&x, &ctx.infinity()).unwrap()), "oo");
assert!((1 / &x).try_limit(&x, &ctx.int(0)).is_err());Sourcepub fn expand_trig(&self) -> Ex
pub fn expand_trig(&self) -> Ex
Expand trigonometric functions with composite arguments.
Applies addition formulas:
sin(a + b)→sin(a)·cos(b) + cos(a)·sin(b)cos(a + b)→cos(a)·cos(b) - sin(a)·sin(b)
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let (x, y) = (ctx.symbol("x"), ctx.symbol("y"));
let expr = (&x + &y).sin();
let expanded = expr.expand_trig();
let s = format!("{expanded}");
assert!(s.contains("sin(x)") && s.contains("cos(y)"), "should expand: {s}");Sourcepub fn expand_log(&self) -> Ex
pub fn expand_log(&self) -> Ex
Expand logarithmic expressions.
Applies logarithm properties:
ln(a * b)→ln(a) + ln(b)ln(a^n)→n * ln(a)ln(a / b)→ln(a) - ln(b)
These rules are valid for positive real arguments.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let (x, y) = (ctx.symbol("x"), ctx.symbol("y"));
let expr = (&x * &y).ln();
let expanded = expr.expand_log();
let s = format!("{expanded}");
assert!(s.contains("ln(x)") && s.contains("ln(y)"), "should expand: {s}");Sourcepub fn log_combine(&self) -> Ex
pub fn log_combine(&self) -> Ex
Combine logarithmic terms (inverse of expand_log).
Applies: ln(a) + ln(b) → ln(a·b) and n·ln(a) → ln(aⁿ).
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let (x, y) = (ctx.symbol("x"), ctx.symbol("y"));
let expr = &x.ln() + &y.ln();
let combined = expr.log_combine();
let s = format!("{combined}");
assert!(s.contains("ln"), "should combine logs: {s}");Sourcepub fn trig_combine(&self) -> Ex
pub fn trig_combine(&self) -> Ex
Apply trigonometric product-to-sum and double-angle identities.
sin(a)·cos(b) → ½[sin(a+b) + sin(a-b)]cos(a)·cos(b) → ½[cos(a-b) + cos(a+b)]sin(a)·sin(b) → ½[cos(a-b) - cos(a+b)]cos²(x) - sin²(x) → cos(2x)
This is the inverse of expand_trig.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
let expr = &x.sin() * &x.cos();
let combined = expr.trig_combine();
let s = format!("{combined}");
assert!(s.contains("sin"), "should produce product-to-sum: {s}");Sourcepub fn pretty(&self) -> String
pub fn pretty(&self) -> String
Dedicated trigonometric simplification.
Goes beyond the pattern-based rules in simplify
by trying exhaustive Pythagorean replacements (sin²→1−cos² and
cos²→1−sin²) and picking the result with the fewest operations.
Simplify an expression using mathematical assumptions.
Unlike simplify which performs structural
rewriting, refine applies rewrites that are only valid under
certain assumptions (e.g., “x is positive”). Assumptions are
set on symbols via assume.
§Rewrites applied
| Expression | Condition | Result |
|---|---|---|
abs(x) | x ≥ 0 | x |
abs(x) | x < 0 | −x |
sign(x) | x > 0 | 1 |
sign(x) | x < 0 | −1 |
sign(x) | x = 0 | 0 |
floor(x) | x ∈ ℤ | x |
ceiling(x) | x ∈ ℤ | x |
sqrt(x²) | x > 0 | x |
sqrt(x²) | x ∈ ℝ | abs(x) |
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x").assume(Assumption::Positive);
let expr = x.abs();
let refined = expr.refine();
assert_eq!(format!("{refined}"), format!("{x}"));Render this expression as a 2D Unicode string for terminal display.
Produces multi-line output with stacked fractions, superscripts, height-matched parentheses, and graduated fraction bar weights for nested fractions.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
let expr = ctx.rational(1, 2);
let s = expr.pretty();
assert!(s.lines().count() == 3, "fraction should be 3 lines");Sourcepub fn pretty_ascii(&self) -> String
pub fn pretty_ascii(&self) -> String
Render this expression as a 2D ASCII string for terminal display.
Like pretty but uses only ASCII characters,
avoiding Unicode box-drawing and bracket pieces that may not
render correctly in all terminals.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
let expr = ctx.rational(1, 2);
let s = expr.pretty_ascii();
assert!(s.contains('-'), "ASCII fraction uses dashes");Sourcepub fn refine(&self) -> Ex
pub fn refine(&self) -> Ex
Simplify an expression using assumptions.
Unlike simplify which performs structural
rewriting, refine applies rewrites that are only valid under
certain assumptions (e.g., “x is positive”). Assumptions are
set on symbols via assume.
See refine_with for temporary assumptions.
Sourcepub fn refine_with(&self, temp_assumptions: &[(&Ex, Assumption)]) -> Ex
pub fn refine_with(&self, temp_assumptions: &[(&Ex, Assumption)]) -> Ex
Simplify an expression using temporary assumptions.
Like refine, but takes a list of
(variable, assumption) pairs that are applied temporarily
for this call only — the symbols’ stored assumptions are not
modified.
This is useful when you want to explore “what if x is positive?” without permanently changing the symbol’s properties.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
let expr = x.abs();
// x has no permanent assumptions, but refine_with treats it as positive:
let refined = expr.refine_with(&[(&x, Assumption::Positive)]);
assert_eq!(format!("{refined}"), format!("{x}"));
// Original x is unchanged — no permanent assumption was set:
assert!(x.is_positive().is_none());Sourcepub fn simplify_trig(&self) -> Ex
pub fn simplify_trig(&self) -> Ex
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
let expr = &x.sin().powi(2) + &x.cos().powi(2);
assert_eq!(format!("{}", expr.simplify_trig()), "1");Sourcepub fn fu(&self) -> Ex
pub fn fu(&self) -> Ex
Apply Fu’s trig simplification algorithm.
Tries 26+ named transforms (TR0–TR14, TRmorrie, TRpower, Pythagorean
substitutions) organized into rule lists, and picks the result with
the lowest (trig_count, op_count) measure.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
let expr = &x.sin().powi(2) + &x.cos().powi(2);
assert_eq!(format!("{}", expr.fu()), "1");Sourcepub fn trig_power_linearize(&self) -> Ex
pub fn trig_power_linearize(&self) -> Ex
Linearize trigonometric powers using Chebyshev-type expansion.
Converts sin(x)^n and cos(x)^n into linear combinations of
sin(kx) and cos(kx). For example, sin(x)^3 becomes
3/4·sin(x) - 1/4·sin(3x).
This is Fu’s TRpower transform.
Sourcepub fn trig_half_angle(&self) -> Ex
pub fn trig_half_angle(&self) -> Ex
Apply half-angle factoring to trigonometric expressions.
Converts patterns like cos(x) - 1 to -2·sin²(x/2) and
cos(x) + 1 to 2·cos²(x/2).
This is Fu’s TR14 transform.
Sourcepub fn gosper_sum(&self, var: &Ex) -> Ex
pub fn gosper_sum(&self, var: &Ex) -> Ex
Attempt closed-form evaluation of a hypergeometric sum using Gosper’s algorithm.
Given a symbolic sum Σ_{k=lower}^{upper} f(k), tries to find a
hypergeometric antidifference via Gosper’s algorithm (1978).
Returns Some(result) with the closed-form expression, or None
if the sum is not Gosper-summable.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let k = ctx.symbol("k");
let n = ctx.symbol("n");
let body = ctx.int(2).pow(&k);
let s = Ex::symbolic_sum(&body, &k, &ctx.int(0), &n);
// gosper_sum extracts bounds and body from a Sum node
let _result = s.gosper_sum(&k);Sourcepub fn try_gosper_sum(&self, var: &Ex) -> Result<Ex, SymplexError>
pub fn try_gosper_sum(&self, var: &Ex) -> Result<Ex, SymplexError>
Like gosper_sum, but returns Err if the result
contains unevaluated forms (i.e. the sum is not Gosper-summable).
Sourcepub fn simplify_combinatorial(&self) -> Ex
pub fn simplify_combinatorial(&self) -> Ex
Simplify combinatorial expressions (factorials, binomials).
Cancels common factorial terms in products, detects binomial
coefficient patterns, and simplifies ratios like n! / (n-1)! → n.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let result = ctx.int(5).factorial().eval();
let four_fact = ctx.int(4).factorial().eval();
let ratio = (&result / &four_fact).simplify_combinatorial();
assert_eq!(format!("{ratio}"), "5");Sourcepub fn simplify_numeric(&self, tolerance: f64) -> Ex
pub fn simplify_numeric(&self, tolerance: f64) -> Ex
Find a simple closed-form for a numerical expression.
Tries rational approximations (via continued fractions),
π-multiples, and square roots of small integers within the
given tolerance.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let expr = ctx.rational(333333, 1000000);
let result = expr.simplify_numeric(1e-5);
assert_eq!(format!("{result}"), "1/3");Sourcepub fn simplify_powers(&self) -> Ex
pub fn simplify_powers(&self) -> Ex
Combine like bases in products with symbolic exponents.
Extends the numeric power-merging done during canonicalization
to symbolic exponents: x^a * x^b → x^(a+b).
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let (x, a, b) = (ctx.symbol("x"), ctx.symbol("a"), ctx.symbol("b"));
let expr = &x.pow(&a) * &x.pow(&b);
let result = expr.simplify_powers();
let s = format!("{result}");
assert!(s.contains("a + b") || s.contains("b + a"), "should combine: {s}");Sourcepub fn rewrite_as_exp(&self) -> Ex
pub fn rewrite_as_exp(&self) -> Ex
Rewrite trigonometric functions as complex exponentials.
sin(x) → (exp(ix) − exp(−ix)) / (2i)cos(x) → (exp(ix) + exp(−ix)) / 2
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
let result = x.sin().rewrite_as_exp();
let s = format!("{result}");
assert!(s.contains("exp") || s.contains("E"), "should contain exponentials: {s}");Sourcepub fn rewrite_as_trig(&self) -> Ex
pub fn rewrite_as_trig(&self) -> Ex
Rewrite complex exponentials as trigonometric functions (Euler’s formula).
exp(ix) → cos(x) + i·sin(x)
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
let i = ctx.i_unit();
let expr = (&i * &x).exp();
let result = expr.rewrite_as_trig();
let s = format!("{result}");
assert!(s.contains("cos") && s.contains("sin"), "should contain trig: {s}");Sourcepub fn collect(&self, var: &Ex) -> Ex
pub fn collect(&self, var: &Ex) -> Ex
Group an expression by powers of var.
Converts the expression to a univariate polynomial in var
and rebuilds it, naturally grouping coefficients by power.
When the expression is not polynomial in var (symbolic or
negative exponents), the terms of the sum are grouped by the exact
power of var they contain instead:
y·x^a + z·x^a + x^2 → (y + z)·x^a + x^2.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let (x, y) = (ctx.symbol("x"), ctx.symbol("y"));
let expr = &x * &y + &x.powi(2) + &y;
let collected = expr.collect(&x);
// Terms are grouped by powers of x.
assert_eq!(format!("{collected}"), "x^2 + x*y + y");
// Symbolic exponents:
let (a, z) = (ctx.symbol("a"), ctx.symbol("z"));
let expr = &y * &x.pow(&a) + &z * &x.pow(&a);
assert_eq!(format!("{}", expr.collect(&x)), "x^a*(y + z)");Sourcepub fn together(&self) -> Ex
pub fn together(&self) -> Ex
Combine every fraction in the expression — at any depth — into a
single quotient numerator / denominator.
Sums are put over a common denominator (the polynomial LCM when the
denominators are univariate, otherwise the product of the distinct
denominators), products multiply numerators and denominators, and
integer powers distribute, so fractions nested inside numerators or
denominators are flattened too. Function arguments are left alone.
No common factors are cancelled — that is
ratsimp, which also normalises the result.
The pieces are available separately from
as_numer_denom; note that a purely numeric
common denominator cannot survive canonicalisation as a quotient
((3x + 2)/6 is stored as 1/2*x + 1/3), while as_numer_denom
still reports (3*x + 2, 6).
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let (x, y) = (ctx.symbol("x"), ctx.symbol("y"));
assert_eq!((1 / &x + 1 / &y).together().to_string(), "(x + y)/(x*y)");
// Nested: the inner fraction is flattened as well.
let nested = (&x + 1 / &y) / (&x - 1);
let (n, d) = nested.together().as_numer_denom();
assert_eq!((n.to_string(), d.to_string()), ("x*y + 1".to_string(), "y*(x - 1)".to_string()));Sourcepub fn cancel(&self, var: &Ex) -> Ex
pub fn cancel(&self, var: &Ex) -> Ex
Cancel common polynomial factors in a rational expression.
Decomposes the expression into numerator and denominator,
converts both to univariate polynomials in var, divides out
their GCD, and rebuilds the expression.
Returns the expression unchanged if it is not a rational
function in var or if there is no common factor.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
// (x² - 1) / (x - 1) → x + 1
let expr = (&x.powi(2) - 1) / (&x - 1);
let cancelled = expr.cancel(&x);
assert_eq!(format!("{cancelled}"), "x + 1");Sourcepub fn simplify_rational(&self) -> Ex
pub fn simplify_rational(&self) -> Ex
Rational simplification: combine fractions and cancel.
Combines every fraction over a common denominator and cancels the
common polynomial factors, in all variables at once. Since 0.3 this
is the same normal form as ratsimp.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
// 1/x + 1/x → 2/x after simplify_rational
let expr = &x.powi(-1) + &x.powi(-1);
let simplified = expr.simplify_rational();
assert_eq!(simplified, ctx.int(2) / &x);Sourcepub fn ratsimp(&self) -> Ex
pub fn ratsimp(&self) -> Ex
Rational-function normal form: a single fraction with common factors cancelled and an integer-primitive numerator and denominator.
The expression is read as P/Q with P and Q polynomials over
the free symbols and every maximal non-rational subexpression
(sin(x), π, √x, … are treated as independent indeterminates,
exactly like SymPy’s cancel). gcd(P, Q) is divided out with a
heuristic multivariate GCD, denominators are cleared so that both
parts have integer coefficients with no common integer factor, and
the leading coefficient of Q is made positive.
Expressions containing ±∞, NaN or unevaluated nodes are
returned unchanged, as is anything that is already in normal form
(the returned handle is then structurally identical to self).
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let (x, y) = (ctx.symbol("x"), ctx.symbol("y"));
// (x² − y²)/(x − y) → x + y
let e = (&x.powi(2) - &y.powi(2)) / (&x - &y);
assert_eq!(e.ratsimp(), &x + &y);
// 1/x + 1/y → (x + y)/(x y)
let e = ctx.int(1) / &x + ctx.int(1) / &y;
assert_eq!(e.ratsimp(), (&x + &y) / (&x * &y));
// Opaque subexpressions are indeterminates: sin(x)²/sin(x) → sin(x)
assert_eq!((x.sin().powi(2) / x.sin()).ratsimp(), x.sin());Sourcepub fn partial_fractions(&self, var: &Ex) -> Ex
pub fn partial_fractions(&self, var: &Ex) -> Ex
Partial fraction decomposition with respect to var.
Decomposes a rational expression into a sum of simpler fractions. Returns the expression unchanged if it’s not a rational function or if the denominator cannot be factored.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
let expr = 1 / (&x.powi(2) - 1);
let decomposed = expr.partial_fractions(&x);
let s = format!("{decomposed}");
// Should be decomposed into simpler fractions
assert!(s != format!("{expr}") || s.contains("1/"), "should decompose: {s}");Sourcepub fn factor(&self, var: &Ex) -> Ex
pub fn factor(&self, var: &Ex) -> Ex
Factor a polynomial expression into a product of linear factors.
Finds rational roots via the equation solver, extracts content (GCD of coefficients), and handles root multiplicities.
Returns the expression unchanged if it is not polynomial in var
or if no rational roots can be found.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
let expr = &x.powi(2) - 1;
let factored = expr.factor(&x);
let s = format!("{factored}");
// Should be factored into (x-1)(x+1) form
assert!(!s.contains("x^2"), "should be factored: {s}");Sourcepub fn factor_terms(&self) -> (Ex, Ex)
pub fn factor_terms(&self) -> (Ex, Ex)
Factor out the GCD of numeric coefficients from a sum.
Returns (gcd, inner) where self == gcd * inner mathematically.
The inner expression has each coefficient divided by the GCD.
Due to canonicalization (Number×Add distribution), reconstructing
gcd * inner may produce the original distributed form. Use the
returned pair directly for display or cancellation.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
let y = ctx.symbol("y");
let expr = &x * 4 + &y * 6;
let (gcd, inner) = expr.factor_terms();
assert_eq!(format!("{gcd}"), "2");
// inner is 2x + 3ySourcepub fn rationalize_denom(&self) -> Ex
pub fn rationalize_denom(&self) -> Ex
Rationalize the denominator of a fraction containing square roots.
1/√2 → √2/21/(1 + √2) → √2 - 1
Returns the expression unchanged if the denominator contains no square roots.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let expr = 1 / &ctx.int(2).sqrt();
let rationalized = expr.rationalize_denom();
let s = format!("{rationalized}");
assert!(s.contains("2"), "should rationalize: {s}");Sourcepub fn separate_vars(&self, vars: &[&Ex]) -> Vec<(Vec<Ex>, Ex)>
pub fn separate_vars(&self, vars: &[&Ex]) -> Vec<(Vec<Ex>, Ex)>
Separate variables in a multiplicative expression.
Given a list of variables, partitions the top-level factors
by which variables they depend on. Returns a vec of
(dependent_vars, product_of_factors) pairs.
- Factors that depend on none of the listed vars get an empty dependency list (i.e. they are “constant” w.r.t. the vars).
- Factors that depend on exactly one var are grouped together.
- Factors that depend on multiple vars form a “mixed” group.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let (x, y) = (ctx.symbol("x"), ctx.symbol("y"));
// 2 * x * y — each factor depends on different vars
let expr = &x * &y * 2;
let groups = expr.separate_vars(&[&x, &y]);
assert!(groups.len() >= 2, "should separate into multiple groups");Sourcepub fn degree(&self, var: &Ex) -> Option<usize>
pub fn degree(&self, var: &Ex) -> Option<usize>
Return the degree of this expression as a polynomial in var.
Returns Some(n) if the expression is a polynomial of degree n
in var, or None if it is not polynomial (e.g., contains sin(x))
or is the zero polynomial. Coefficients may be exact numbers or
arbitrary expressions free of var (symbolic parameters).
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let (x, a) = (ctx.symbol("x"), ctx.symbol("a"));
assert_eq!((&x.powi(3) + &x + 1).degree(&x), Some(3));
assert_eq!((&a * &x.powi(2) + &x * (&a + 1) + 3).degree(&x), Some(2));
assert_eq!(x.sin().degree(&x), None);Sourcepub fn coeffs(&self, var: &Ex) -> Option<Vec<Ex>>
pub fn coeffs(&self, var: &Ex) -> Option<Vec<Ex>>
Return the coefficients of this expression as a polynomial in var,
in ascending degree order: [a_0, a_1, a_2, ...].
Returns None if the expression is not polynomial in var.
Coefficients may be exact numbers or arbitrary expressions free of
var; the zero polynomial yields an empty vector.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let (x, a) = (ctx.symbol("x"), ctx.symbol("a"));
// x^2 + 3*x + 5 → coefficients [5, 3, 1]
let expr = &x.powi(2) + &x * 3 + 5;
let cs = expr.coeffs(&x).unwrap();
let strs: Vec<String> = cs.iter().map(|c| format!("{c}")).collect();
assert_eq!(strs, vec!["5", "3", "1"]);
// Symbolic parameters are collected too: a*x^2 + (a + 1)*x + 3
let expr = &a * &x.powi(2) + &x * (&a + 1) + 3;
let cs = expr.coeffs(&x).unwrap();
let strs: Vec<String> = cs.iter().map(|c| format!("{c}")).collect();
assert_eq!(strs, vec!["3", "a + 1", "a"]);Sourcepub fn coeff(&self, var: &Ex, power: usize) -> Option<Ex>
pub fn coeff(&self, var: &Ex, power: usize) -> Option<Ex>
Extract the coefficient of var^n in this expression.
Returns None if the expression is not polynomial in var.
Returns the zero expression if the coefficient is zero.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
let expr = &x.powi(2) * 3 + &x * 5 + 7;
assert_eq!(format!("{}", expr.coeff(&x, 2).unwrap()), "3");
assert_eq!(format!("{}", expr.coeff(&x, 1).unwrap()), "5");
assert_eq!(format!("{}", expr.coeff(&x, 0).unwrap()), "7");Sourcepub fn as_numer_denom(&self) -> (Ex, Ex)
pub fn as_numer_denom(&self) -> (Ex, Ex)
Decompose this expression into (numerator, denominator) with
self == numerator / denominator, the same way SymPy’s
as_numer_denom does.
- A rational literal splits into integers:
3/31→(3, 31). - A rational coefficient splits too:
2/3 * x→(2*x, 3). - A sum is combined over a common denominator at every depth
(see
together):x/2 + 1/3→(3*x + 2, 6),1/x + 1/y→(x + y, x*y). - Function arguments are opaque; anything without a denominator is
(self, 1).
Nothing is cancelled: ((x² − 1)/(x − 1)).as_numer_denom() is
(x^2 - 1, x - 1). Call ratsimp first when a
reduced fraction is wanted.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let (x, y) = (ctx.symbol("x"), ctx.symbol("y"));
let s = |(n, d): (Ex, Ex)| (n.to_string(), d.to_string());
assert_eq!(s((&x / &y).as_numer_denom()), ("x".into(), "y".into()));
assert_eq!(s(ctx.rational(3, 31).as_numer_denom()), ("3".into(), "31".into()));
assert_eq!(s((&x * 2 / 3).as_numer_denom()), ("2*x".into(), "3".into()));
assert_eq!(s((&x / 2 + ctx.rational(1, 3)).as_numer_denom()), ("3*x + 2".into(), "6".into()));
assert_eq!(s((1 / &x + 1 / &y).as_numer_denom()), ("x + y".into(), "x*y".into()));Sourcepub fn is_constant(&self) -> bool
pub fn is_constant(&self) -> bool
Returns true if this expression contains no free symbols
(i.e., it is a constant — a number, π, e, etc.).
§Examples
use symplex::prelude::*;
let ctx = Context::new();
assert!(ctx.int(5).is_constant());
assert!(ctx.pi().is_constant());
assert!(!ctx.symbol("x").is_constant());Sourcepub fn is_polynomial(&self, var: &Ex) -> bool
pub fn is_polynomial(&self, var: &Ex) -> bool
Returns true if this expression is a polynomial in var.
Equivalent to self.degree(var).is_some().
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
assert!((&x.powi(2) + 1).is_polynomial(&x));
assert!(!x.sin().is_polynomial(&x));Sourcepub fn poly_gcd(&self, other: &Ex, var: &Ex) -> Option<Ex>
pub fn poly_gcd(&self, other: &Ex, var: &Ex) -> Option<Ex>
Compute the polynomial GCD of self and other with respect to var.
Returns None if either expression is not polynomial in var.
Sourcepub fn poly_lcm(&self, other: &Ex, var: &Ex) -> Option<Ex>
pub fn poly_lcm(&self, other: &Ex, var: &Ex) -> Option<Ex>
Compute the polynomial LCM of self and other with respect to var.
Returns None if either expression is not polynomial in var.
Sourcepub fn solve(&self, var: &Ex) -> Result<Vec<Ex>, SymplexError>
pub fn solve(&self, var: &Ex) -> Result<Vec<Ex>, SymplexError>
Solve self = 0 for the given variable.
Returns the values of var that make this expression zero.
Supports polynomial equations (exact radicals through degree 4,
RootOf placeholders beyond, all n roots of a·xⁿ + b),
symbolic-coefficient linear and quadratic equations, and
transcendental equations (exp, ln, trig, hyperbolic, |·|,
change of variable, Lambert W). For periodic functions only the
principal branches are returned — use
solve_general for full solution families.
§Errors
SymplexError::InfiniteSolutionswhen the equation reduces to the identity0 = 0(every value ofvaris a solution).SymplexError::NoSolutionwhen the equation is provably unsatisfiable: it reduces to a nonzero constant (1 = 0), does not depend onvarat all, or violates a range restriction such asexp(x) = 0orsin(x) = 2(no real solution).SymplexError::ComputationFailedwhen the expression is not polynomial invarand no transcendental strategy applies.
Ok(vec![]) is reserved for genuine equations whose roots could
not be found in the searched domain.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
// Solve x² - 5x + 6 = 0
let expr = &x.powi(2) - &x * 5 + 6;
let solutions = expr.solve(&x).unwrap();
assert_eq!(solutions.len(), 2);
// 0 = 0 is an identity, 1 = 0 a contradiction
assert!(matches!(
ctx.int(0).solve(&x),
Err(SymplexError::InfiniteSolutions { .. })
));
assert!(matches!(
ctx.int(1).solve(&x),
Err(SymplexError::NoSolution { .. })
));Sourcepub fn solve_gt(&self, var: &Ex) -> SetEx
pub fn solve_gt(&self, var: &Ex) -> SetEx
Solve self > 0 for var, returning the solution as a set.
Uses the sign-chart method: finds roots, tests sign in each region, and returns a union of intervals.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
// x > 0 → (0, ∞)
let result = x.solve_gt(&x);
let s = format!("{result}");
assert!(!s.contains("EmptySet"), "x > 0 should not be empty: {s}");Sourcepub fn try_solve_gt(&self, var: &Ex) -> Result<SetEx, SymplexError>
pub fn try_solve_gt(&self, var: &Ex) -> Result<SetEx, SymplexError>
Like solve_gt, but returns Err if the result
contains unevaluated forms.
Sourcepub fn solve_ge(&self, var: &Ex) -> SetEx
pub fn solve_ge(&self, var: &Ex) -> SetEx
Solve self >= 0 for var, returning the solution as a set.
Like solve_gt, but includes the roots themselves
(where self = 0).
Sourcepub fn try_solve_ge(&self, var: &Ex) -> Result<SetEx, SymplexError>
pub fn try_solve_ge(&self, var: &Ex) -> Result<SetEx, SymplexError>
Like solve_ge, but returns Err if the result
contains unevaluated forms.
Sourcepub fn solve_lt(&self, var: &Ex) -> SetEx
pub fn solve_lt(&self, var: &Ex) -> SetEx
Solve self < 0 for var, returning the solution as a set.
Uses the sign-chart method with a strict less-than relation.
Sourcepub fn try_solve_lt(&self, var: &Ex) -> Result<SetEx, SymplexError>
pub fn try_solve_lt(&self, var: &Ex) -> Result<SetEx, SymplexError>
Like solve_lt, but returns Err if the result
contains unevaluated forms.
Sourcepub fn solve_le(&self, var: &Ex) -> SetEx
pub fn solve_le(&self, var: &Ex) -> SetEx
Solve self <= 0 for var, returning the solution as a set.
Like solve_lt, but includes the roots themselves.
Sourcepub fn try_solve_le(&self, var: &Ex) -> Result<SetEx, SymplexError>
pub fn try_solve_le(&self, var: &Ex) -> Result<SetEx, SymplexError>
Like solve_le, but returns Err if the result
contains unevaluated forms.
Sourcepub fn solve_as_set(&self, var: &Ex) -> SetEx
pub fn solve_as_set(&self, var: &Ex) -> SetEx
Solve self = 0, returning solutions as a set.
This is a set-valued variant of solve — instead of
returning a Vec<Ex>, it returns a SetEx:
- a
FiniteSetof the roots when they can be found, UniversalSetwhen the equation is the identity0 = 0,EmptySetwhen the equation is provably unsatisfiable or no roots were found.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
let poly = &x.powi(2) - &x * 5 + 6;
let result = poly.solve_as_set(&x);
let s = format!("{result}");
// Should contain {2, 3} or similar
assert!(!s.contains("EmptySet"), "solve_as_set: {s}");
assert_eq!(format!("{}", ctx.int(0).solve_as_set(&x)), "UniversalSet");
assert_eq!(format!("{}", ctx.int(1).solve_as_set(&x)), "EmptySet");Sourcepub fn solve_numeric(
&self,
var: &Ex,
initial_guess: f64,
max_iterations: usize,
tolerance: f64,
) -> Result<f64, SymplexError>
pub fn solve_numeric( &self, var: &Ex, initial_guess: f64, max_iterations: usize, tolerance: f64, ) -> Result<f64, SymplexError>
Numerical root finding via a safeguarded Newton’s method.
Finds a numerical root of self = 0 near initial_guess. The
core iteration is Newton’s x_{n+1} = x_n - f(x_n)/f'(x_n), made
robust by:
- backtracking — a step that increases
|f|is halved (up to 30 times) before being accepted; - secant fallback — when
f'(x)vanishes, the previous iterate provides a secant step (or a small perturbation on the first step); - bisection fallback — once two iterates with opposite signs of
fhave been seen, any Newton step that leaves the bracket is replaced by the bracket midpoint, guaranteeing progress.
The expression is compiled to a native closure when possible, so each iteration is cheap.
§Arguments
var— the variable to solve forinitial_guess— starting point for iterationmax_iterations— maximum number of Newton stepstolerance— convergence threshold (stop when|f(x)| < tolerance)
§Errors
Returns Err if the method doesn’t converge within max_iterations
(the message reports the final residual), or if evaluation fails
(e.g. free symbols other than var).
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
// Solve x - cos(x) = 0 near x=1
let expr = &x - &x.cos();
let root = expr.solve_numeric(&x, 1.0, 50, 1e-12).unwrap();
assert!((root - 0.7390851332).abs() < 1e-8);
// atan(x) = 0 from x = 3: undamped Newton diverges, the damped
// iteration converges to the root at 0.
let r = x.atan().solve_numeric(&x, 3.0, 100, 1e-12).unwrap();
assert!(r.abs() < 1e-8);Sourcepub fn solve_ode(&self, func: &Ex, var: &Ex) -> Ex
pub fn solve_ode(&self, func: &Ex, var: &Ex) -> Ex
Solve an ODE represented as self = 0.
self should contain formal derivative nodes (created via
formal_diff). func is the dependent
variable (e.g., y) and var is the independent variable (e.g., x).
Returns the general solution expression. If the ODE cannot be solved,
returns an unevaluated DSolve(expr, func, var) node.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
let y = ctx.symbol("y");
let dy = y.formal_diff(&x); // y'
let ode = &dy + &(&y * 2); // y' + 2y = 0
let sol = ode.solve_ode(&y, &x);
let s = format!("{sol}");
assert!(s.contains("exp"), "solution should contain exp: {s}");Sourcepub fn try_solve_ode(&self, func: &Ex, var: &Ex) -> Result<Ex, SymplexError>
pub fn try_solve_ode(&self, func: &Ex, var: &Ex) -> Result<Ex, SymplexError>
Like solve_ode, but returns Err if the result
contains unevaluated forms (i.e., the ODE could not be solved).
Sourcepub fn eval_decimal(&self, digits: u32) -> Result<String, SymplexError>
pub fn eval_decimal(&self, digits: u32) -> Result<String, SymplexError>
Numeric floating-point evaluation to the given number of decimal digits.
Returns the decimal string representation of the evaluated expression.
§Errors
Returns SymplexError::FreeSymbol if the expression contains
unbound symbols (e.g., x.eval_decimal(10) without substituting a value).
Returns SymplexError::Unevaluable if the expression contains
nodes that cannot be evaluated to a finite number (infinity, NaN,
imaginary unit, unevaluated derivatives/integrals, user functions).
Returns SymplexError::PrecisionExhausted if the requested
precision exceeds EvalConfig::max_evalf_precision, or if
intermediate computation produces NaN.
Sourcepub fn eval_f64(&self) -> Result<f64, SymplexError>
pub fn eval_f64(&self) -> Result<f64, SymplexError>
Convenience: evaluate to an f64.
Calls eval_decimal with 16 digits of precision and
parses the result to f64. This avoids the common pattern of
.eval_decimal(15).unwrap().parse::<f64>().unwrap().
§Errors
Returns the same errors as eval_decimal, plus a
SymplexError::NotImplemented if the decimal string cannot
be parsed to f64.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
let val = x.powi(2).subs_i64(&x, 3).eval_f64().unwrap();
assert!((val - 9.0).abs() < 1e-10);Sourcepub fn eval_complex64(&self) -> Result<(f64, f64), SymplexError>
pub fn eval_complex64(&self) -> Result<(f64, f64), SymplexError>
Evaluates the expression to a complex f64 pair (real, imaginary).
Uses 16 decimal digits of precision internally. Returns both the
real and imaginary parts, correctly handling complex expressions
like sqrt(-1) → (0.0, 1.0).
§Errors
Returns Err if the expression contains free symbols or if
the arbitrary-precision engine fails.
Sourcepub fn compile(&self, var_names: &[&str]) -> Result<CompiledFn, SymplexError>
pub fn compile(&self, var_names: &[&str]) -> Result<CompiledFn, SymplexError>
Compile this expression into a fast numerical function.
var_names specifies the variable-to-index mapping: the returned
function takes &[f64] where index 0 corresponds to var_names[0],
etc. The result is a CompiledFn:
Clone + Send + Sync, callable like a closure (f(&[x])) or via
f.call(&[x]) / f.try_call(&[x]), with f.arity() reporting the
expected argument count.
The expression is constant-folded (eval()) and common
subexpressions are shared before lowering to a stack-VM program.
Every numerically evaluable node is supported, including the
special functions (gamma, lgamma, digamma, erf, erfc,
lambertw, beta, factorial, binomial), Bessel functions and
orthogonal polynomials with constant integer order, fibonacci,
lucas, harmonic, factorial2, rising/falling factorials,
min/max/floor/ceiling/sign/heaviside/atan2, and
piecewise with relational and boolean conditions. DiracDelta
evaluates to 0.0 everywhere (its pointwise value away from the
support); Heaviside(0) is 0.5.
§Errors
SymplexError::FreeSymbolif a symbol is not listed invar_names.SymplexError::NotImplementedfor nodes with no numerical meaning (ImaginaryUnit, unevaluatedIntegral/Derivative/Sum, sets, user-definedApplynodes, Bessel/orthogonal-polynomial nodes whose order is not a constant integer). The message names the node.SymplexError::InvalidArgumentfor duplicate parameter names.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
let f = &x.powi(2) + 1;
let func = f.compile(&["x"]).expect("should compile");
assert!((func(&[3.0]) - 10.0).abs() < 1e-10);
assert_eq!(func.arity(), 1);
// Special functions are supported too.
let g = x.gamma().compile(&["x"]).unwrap();
assert!((g.call(&[5.0]) - 24.0).abs() < 1e-12);
// Free symbols are an error, not a silent NaN.
let y = ctx.symbol("y");
assert!(matches!((&x + &y).compile(&["x"]), Err(SymplexError::FreeSymbol { .. })));Sourcepub fn compile_many(
exprs: &[&Ex],
var_names: &[&str],
) -> Result<CompiledFnVec, SymplexError>
pub fn compile_many( exprs: &[&Ex], var_names: &[&str], ) -> Result<CompiledFnVec, SymplexError>
Compile several expressions into one vector-valued numerical function.
All expressions must belong to the same context. A single
common-subexpression-elimination pass is shared across all outputs,
so this is the efficient way to evaluate gradients, Jacobians, or any
family of expressions with overlapping structure. The returned
CompiledFnVec offers
call(&args, &mut out), call_vec(&args), try_call, arity() and
len().
An empty exprs slice yields a function with zero outputs.
§Errors
Same conditions as compile.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
let y = ctx.symbol("y");
let f = &x.powi(2) * &y + &x.sin();
let grad = Ex::compile_many(&[&f.diff(&x), &f.diff(&y)], &["x", "y"]).unwrap();
let g = grad.call_vec(&[1.0, 2.0]);
assert!((g[0] - (4.0 + 1f64.cos())).abs() < 1e-12); // 2xy + cos x
assert!((g[1] - 1.0).abs() < 1e-12); // x^2Sourcepub fn cse(&self) -> (Vec<(Ex, Ex)>, Ex)
pub fn cse(&self) -> (Vec<(Ex, Ex)>, Ex)
Perform common subexpression elimination (CSE).
Identifies repeated subexpressions and extracts them into named
temporaries (__cse_0, __cse_1, …), reducing redundant
computation when generating code.
Returns a list of (name, value) bindings and the rewritten
expression where common subexpressions are replaced by their names.
Bindings are ordered by first occurrence (post-order), so a binding
only refers to earlier bindings and the numbering is deterministic.
Trivially cheap nodes (a negated or scaled atom, x^2, x^-1) are
only extracted when used three or more times; boolean-valued nodes
are never extracted.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
let sin_x = x.sin();
let expr = &sin_x.powi(2) + &sin_x;
let (bindings, result) = expr.cse();
// sin(x) may be extracted as a common subexpression
let _ = format!("{result}");Sourcepub fn cse_many(exprs: &[&Ex]) -> (Vec<(Ex, Ex)>, Vec<Ex>)
pub fn cse_many(exprs: &[&Ex]) -> (Vec<(Ex, Ex)>, Vec<Ex>)
Common subexpression elimination across several expressions.
Temporaries are shared by all inputs, which is what code generators
and compile_many need for gradients and
Jacobians. Returns the shared (name, value) bindings and the
rewritten expressions (in input order). All expressions must belong
to the same context; an empty input yields empty outputs.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
let s = x.sin();
let (bindings, exprs) = Ex::cse_many(&[&(&s + 1), &s.powi(3)]);
assert_eq!(exprs.len(), 2);
assert_eq!(bindings.len(), 1); // sin(x) shared by both
assert_eq!(format!("{}", bindings[0].1), "sin(x)");Sourcepub fn to_rust_fn(
&self,
name: &str,
args: &[&str],
) -> Result<String, SymplexError>
pub fn to_rust_fn( &self, name: &str, args: &[&str], ) -> Result<String, SymplexError>
Generate a Rust function body as a string.
The generated function takes f64 arguments and returns f64.
Uses CSE (common subexpression elimination) for efficient code.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
let f = x.powi(2) + 1;
let code = f.to_rust_fn("my_func", &["x"]).unwrap();
assert!(code.contains("pub fn my_func"));Sourcepub fn to_rust_fn_with_options(
&self,
name: &str,
args: &[&str],
options: &CodegenOptions,
) -> Result<String, SymplexError>
pub fn to_rust_fn_with_options( &self, name: &str, args: &[&str], options: &CodegenOptions, ) -> Result<String, SymplexError>
Generate a Rust function body as a string with custom code generation options.
See CodegenOptions for available
settings (precision, math backend, annotations, CSE toggle).
§Examples
use symplex::prelude::*;
use symplex::codegen::{CodegenOptions, Precision};
let ctx = Context::new();
let x = ctx.symbol("x");
let f = x.powi(2) + 1;
let opts = CodegenOptions { precision: Precision::F32, ..Default::default() };
let code = f.to_rust_fn_with_options("my_func", &["x"], &opts).unwrap();
assert!(code.contains("f32"));Sourcepub fn to_c_fn(&self, name: &str, args: &[&str]) -> Result<String, SymplexError>
pub fn to_c_fn(&self, name: &str, args: &[&str]) -> Result<String, SymplexError>
Generate a self-contained C99 function as a string.
The output starts with #include <math.h>, followed by any
static inline symplex_* helper functions the expression needs
(Lambert W, digamma, Bessel functions, orthogonal polynomials,
integer sequences, … — everything <math.h> lacks), then the
function itself with const double tN = …; temporaries for common
subexpressions. Functions available in <math.h> (tgamma,
lgamma, erf, erfc, fma, expm1, log1p, …) are used
directly; integer powers |n| ≤ 4 of simple operands become repeated
multiplication, other powers use pow. Piecewise expressions become
ternary chains ending in NAN.
§Errors
Same conditions as to_rust_fn:
SymplexError::FreeSymbol for unbound symbols and
SymplexError::NotImplemented for nodes without numerical meaning
(the message names the node).
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
let code = (x.sin().powi(2) + x.lambertw()).to_c_fn("f", &["x"]).unwrap();
assert!(code.contains("#include <math.h>"));
assert!(code.contains("double f(double x) {"));
assert!(code.contains("static inline double symplex_lambert_w0(double x)"));Sourcepub fn to_c_fn_with_options(
&self,
name: &str,
args: &[&str],
options: &CodegenOptions,
) -> Result<String, SymplexError>
pub fn to_c_fn_with_options( &self, name: &str, args: &[&str], options: &CodegenOptions, ) -> Result<String, SymplexError>
Generate a C99 function with custom options.
Honoured CodegenOptions
fields: precision (double / float with the f-suffixed math
functions), cse, inline (static inline), use_mul_add (fma),
checked_domain (assert preconditions) and emit_runtime (set to
false and paste
CodegenOptions::c_runtime
once when several functions share a translation unit).
§Examples
use symplex::prelude::*;
use symplex::matrix::{CodegenOptions, Precision};
let ctx = Context::new();
let x = ctx.symbol("x");
let opts = CodegenOptions { precision: Precision::F32, inline: true, ..Default::default() };
let code = x.exp().to_c_fn_with_options("f", &["x"], &opts).unwrap();
assert!(code.contains("static inline float f(float x) {"));
assert!(code.contains("expf(x)"));Sourcepub fn sum_of(ctx: &Context, exprs: impl IntoIterator<Item = Ex>) -> Ex
pub fn sum_of(ctx: &Context, exprs: impl IntoIterator<Item = Ex>) -> Ex
Sum a collection of expressions.
All expressions must belong to the same context. Returns zero
if the iterator is empty (using the context of ctx).
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let terms: Vec<Ex> = (1..=4).map(|n| ctx.int(n)).collect();
let total = Ex::sum_of(&ctx, terms);
assert_eq!(format!("{total}"), "10");Sourcepub fn product_of(ctx: &Context, exprs: impl IntoIterator<Item = Ex>) -> Ex
pub fn product_of(ctx: &Context, exprs: impl IntoIterator<Item = Ex>) -> Ex
Multiply a collection of expressions.
All expressions must belong to the same context. Returns one
if the iterator is empty (using the context of ctx).
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let factors: Vec<Ex> = (1..=4).map(|n| ctx.int(n)).collect();
let total = Ex::product_of(&ctx, factors);
assert_eq!(format!("{total}"), "24");Sourcepub fn replace<F>(&self, f: F) -> Ex
pub fn replace<F>(&self, f: F) -> Ex
Walk the expression bottom-up, applying a user-provided transformation at each node.
The closure receives an ExprView for
each sub-expression — a non-locking, read-only view that supports
identity comparison with Ex but cannot acquire any locks.
Return Some(replacement) to replace it, or None to keep it
unchanged.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let (x, y) = (ctx.symbol("x"), ctx.symbol("y"));
let expr = x.powi(2);
let replaced = expr.replace(|e| if e == &x { Some(y.clone()) } else { None });
assert_eq!(format!("{replaced}"), "y^2");Sourcepub fn check_solution(&self, var: &Ex, val: &Ex) -> Option<bool>
pub fn check_solution(&self, var: &Ex, val: &Ex) -> Option<bool>
Check whether val is a solution of self = 0 for variable var.
Substitutes val for var, evaluates, and checks if the result is zero.
Returns Some(true) if the residual is zero (structurally or numerically),
Some(false) if definitely non-zero, or None if the result is ambiguous.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let ctx = &ctx;
symplex::syms!(ctx; x);
let poly = expr!(ctx, x^2 - 4);
assert_eq!(poly.check_solution(&x, &ctx.int(2)), Some(true));
assert_eq!(poly.check_solution(&x, &ctx.int(-2)), Some(true));
assert_eq!(poly.check_solution(&x, &ctx.int(3)), Some(false));Sourcepub fn classify_ode(&self, func: &Ex, var: &Ex) -> OdeType
pub fn classify_ode(&self, func: &Ex, var: &Ex) -> OdeType
Classify an ODE represented as self = 0.
self should contain formal derivative nodes (created via
formal_diff). func is the dependent
variable (e.g., y) and var is the independent variable (e.g., x).
Returns an OdeType describing the
recognized ODE class, or OdeType::Unknown
if the form is not recognized.
§Examples
use symplex::prelude::*;
use symplex::ode::OdeType;
let ctx = Context::new();
let x = ctx.symbol("x");
let y = ctx.symbol("y");
let dy = y.formal_diff(&x);
let ode = &dy - &x; // y' = x
assert_eq!(ode.classify_ode(&y, &x), OdeType::SimpleSeparable);Sourcepub fn check_ode_solution(&self, solution: &Ex, func: &Ex, var: &Ex) -> bool
pub fn check_ode_solution(&self, solution: &Ex, func: &Ex, var: &Ex) -> bool
Check whether solution satisfies the ODE self = 0.
Substitutes the solution for func and its derivative for
Derivative(func, var), then evaluates and checks whether the
residual is zero.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
let y = ctx.symbol("y");
let dy = y.formal_diff(&x);
let ode = &dy - &x; // y' - x = 0
// Solution: y = x²/2
let sol = &x.powi(2) / 2;
assert!(ode.check_ode_solution(&sol, &y, &x));Sourcepub fn closed_interval(&self, end: &Ex) -> SetEx
pub fn closed_interval(&self, end: &Ex) -> SetEx
Create a closed interval [self, end].
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let i = ctx.int(0).closed_interval(&ctx.int(1));
let s = format!("{i}");
assert!(s.contains("[") && s.contains("]"), "closed interval: {s}");Sourcepub fn open_interval(&self, end: &Ex) -> SetEx
pub fn open_interval(&self, end: &Ex) -> SetEx
Create an open interval (self, end).
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let i = ctx.int(0).open_interval(&ctx.int(1));
let s = format!("{i}");
assert!(s.contains("(") && s.contains(")"), "open interval: {s}");Sourcepub fn eval_f64_with<V: ToEx>(
&self,
subs: &[(&Ex, V)],
) -> Result<f64, SymplexError>
pub fn eval_f64_with<V: ToEx>( &self, subs: &[(&Ex, V)], ) -> Result<f64, SymplexError>
Substitute values for symbols (simultaneously) and evaluate to f64.
The values may be any ToEx type: integers,
f64 (converted exactly — 0.1 is the dyadic
3602879701896397/36028797018963968, which is what you want when
the goal is a numeric answer), BigInt, Ratio<BigInt>, or Ex.
All values in one call must have the same Rust type.
Substitution goes through subs_map (simultaneous),
then eval, then eval_f64.
§Errors
Same as eval_f64: free symbols left unbound, or a
result that is not a real number.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let (x, y) = (ctx.symbol("x"), ctx.symbol("y"));
let f = &x.powi(2) + &y;
assert_eq!(f.eval_f64_with(&[(&x, 3), (&y, 1)]).unwrap(), 10.0);
assert_eq!(f.eval_f64_with(&[(&x, 0.5), (&y, 0.25)]).unwrap(), 0.5);
assert!(f.eval_f64_with(&[(&x, 1.0)]).is_err()); // y unboundSourcepub fn eval_f64_with_rational(
&self,
subs: &[(&Ex, i64, i64)],
) -> Result<f64, SymplexError>
pub fn eval_f64_with_rational( &self, subs: &[(&Ex, i64, i64)], ) -> Result<f64, SymplexError>
Substitute multiple rational values p/q and evaluate to f64.
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
assert_eq!((&x * 4).eval_f64_with_rational(&[(&x, 1, 2)]).unwrap(), 2.0);Sourcepub fn subs_map_i64(&self, subs: &[(&Ex, i64)]) -> Ex
pub fn subs_map_i64(&self, subs: &[(&Ex, i64)]) -> Ex
Substitute multiple integer values simultaneously.
use symplex::prelude::*;
let ctx = Context::new();
let (x, y) = (ctx.symbol("x"), ctx.symbol("y"));
assert_eq!(format!("{}", (&x + &y).subs_map_i64(&[(&x, 1), (&y, 2)])), "3");Sourcepub fn subs_map_with<V: ToEx>(&self, subs: &[(&Ex, V)]) -> Ex
pub fn subs_map_with<V: ToEx>(&self, subs: &[(&Ex, V)]) -> Ex
Substitute values of any ToEx type
simultaneously (no evaluation).
f64 values are converted exactly; use
Context::from_f64_nice
first if you want 0.1 → 1/10.
use symplex::prelude::*;
let ctx = Context::new();
let (x, y) = (ctx.symbol("x"), ctx.symbol("y"));
let e = (&x * &y).subs_map_with(&[(&x, 0.5), (&y, 4.0)]);
assert_eq!(format!("{e}"), "2");Sourcepub fn bessel_i(&self, order: &Ex) -> Ex
pub fn bessel_i(&self, order: &Ex) -> Ex
Modified Bessel function of the first kind: I_order(self).
Sourcepub fn bessel_k(&self, order: &Ex) -> Ex
pub fn bessel_k(&self, order: &Ex) -> Ex
Modified Bessel function of the second kind: K_order(self).
Sourcepub fn chebyshev_t(&self, n: &Ex) -> Ex
pub fn chebyshev_t(&self, n: &Ex) -> Ex
Chebyshev polynomial of the first kind T_n(self).
Sourcepub fn chebyshev_u(&self, n: &Ex) -> Ex
pub fn chebyshev_u(&self, n: &Ex) -> Ex
Chebyshev polynomial of the second kind U_n(self).
Sourcepub fn erfi(&self) -> Ex
pub fn erfi(&self) -> Ex
Imaginary error function erfi(self) = −i·erf(i·self) = (2/√π) ∫₀ˣ e^{t²} dt.
Exact: erfi(0) = 0, odd; d/dx erfi(x) = 2e^{x²}/√π.
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
assert_eq!(format!("{}", x.erfi()), "erfi(x)");
assert_eq!(format!("{}", ctx.int(0).erfi().eval()), "0");
assert!((ctx.rational(7, 10).erfi().eval_f64().unwrap() - 0.94028293383350736168).abs() < 1e-14);Sourcepub fn erfinv(&self) -> Ex
pub fn erfinv(&self) -> Ex
Inverse error function erfinv(self): erf(erfinv(y)) = y for |y| < 1.
Exact: erfinv(0) = 0, erfinv(±1) = ±∞, odd;
d/dy erfinv(y) = (√π/2) e^{erfinv(y)²}.
Sourcepub fn erfcinv(&self) -> Ex
pub fn erfcinv(&self) -> Ex
Inverse complementary error function erfcinv(self) = erfinv(1 − self).
Exact: erfcinv(1) = 0, erfcinv(0) = ∞, erfcinv(2) = −∞.
Sourcepub fn expint(&self, n: &Ex) -> Ex
pub fn expint(&self, n: &Ex) -> Ex
Generalised exponential integral E_n(self) = ∫₁^∞ e^{−self·t} t^{−n} dt
(SymPy expint(n, x); the order comes first in the display).
Exact: E_n(0) = 1/(n−1) for n > 1, E_0(x) = e^{−x}/x,
E_n(∞) = 0; d/dx E_n(x) = −E_{n−1}(x).
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
assert_eq!(format!("{}", x.expint(&ctx.int(2))), "expint(2, x)");
assert_eq!(format!("{}", ctx.int(0).expint(&ctx.int(3)).eval()), "1/2");Sourcepub fn e1(&self) -> Ex
pub fn e1(&self) -> Ex
Exponential integral E₁(self) = expint(1, self) = ∫_self^∞ e^{−t}/t dt.
For x > 0, E₁(x) = −Ei(−x).
Sourcepub fn shi(&self) -> Ex
pub fn shi(&self) -> Ex
Hyperbolic sine integral Shi(self) = ∫₀ˣ sinh(t)/t dt.
Exact: Shi(0) = 0, odd; d/dx Shi(x) = sinh(x)/x.
Sourcepub fn chi(&self) -> Ex
pub fn chi(&self) -> Ex
Hyperbolic cosine integral Chi(self) = γ + ln x + ∫₀ˣ (cosh(t) − 1)/t dt.
Exact: Chi(0) = −∞; d/dx Chi(x) = cosh(x)/x.
Sourcepub fn fresnels(&self) -> Ex
pub fn fresnels(&self) -> Ex
Fresnel sine integral S(self) = ∫₀ˣ sin(πt²/2) dt.
Exact: S(0) = 0, S(±∞) = ±1/2, odd; d/dx S(x) = sin(πx²/2).
Sourcepub fn fresnelc(&self) -> Ex
pub fn fresnelc(&self) -> Ex
Fresnel cosine integral C(self) = ∫₀ˣ cos(πt²/2) dt.
Exact: C(0) = 0, C(±∞) = ±1/2, odd; d/dx C(x) = cos(πx²/2).
Sourcepub fn lowergamma(&self, s: &Ex) -> Ex
pub fn lowergamma(&self, s: &Ex) -> Ex
Lower incomplete gamma function γ(s, self) = ∫₀ˣ t^{s−1} e^{−t} dt
(SymPy lowergamma(s, x)).
Exact: γ(s, 0) = 0, γ(s, ∞) = Γ(s), γ(1, x) = 1 − e^{−x},
γ(1/2, x) = √π erf(√x), and closed forms for small integer and
half-integer s; ∂/∂x γ(s, x) = x^{s−1} e^{−x}.
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
assert_eq!(x.lowergamma(&ctx.int(1)).eval(), 1 - (-&x).exp());
assert_eq!(format!("{}", x.uppergamma(&ctx.int(1)).eval()), "exp(-x)");Sourcepub fn uppergamma(&self, s: &Ex) -> Ex
pub fn uppergamma(&self, s: &Ex) -> Ex
Upper incomplete gamma function Γ(s, self) = ∫_x^∞ t^{s−1} e^{−t} dt
(SymPy uppergamma(s, x)).
Exact: Γ(s, 0) = Γ(s), Γ(s, ∞) = 0, Γ(1, x) = e^{−x},
Γ(0, x) = E₁(x), Γ(1/2, x) = √π erfc(√x), and closed forms for
small integer and half-integer s; ∂/∂x Γ(s, x) = −x^{s−1} e^{−x}.
Sourcepub fn polylog(&self, s: &Ex) -> Ex
pub fn polylog(&self, s: &Ex) -> Ex
Polylogarithm Li_s(self) = Σ_{k≥1} self^k / k^s (SymPy polylog(s, z)).
Exact: Li_s(0) = 0, Li_s(1) = ζ(s), Li_s(−1) = −η(s),
Li_1(z) = −ln(1 − z), Li_0(z) = z/(1 − z), Li_{−n}(z) rational,
Li_2(1/2) = π²/12 − ln²2/2; d/dz Li_s(z) = Li_{s−1}(z)/z.
use symplex::prelude::*;
let ctx = Context::new();
let z = ctx.symbol("z");
assert_eq!(format!("{}", z.polylog(&ctx.int(2))), "polylog(2, z)");
assert_eq!(format!("{}", ctx.int(1).polylog(&ctx.int(2)).eval()), "1/6*pi^2");
assert_eq!(z.polylog(&ctx.int(-1)).eval(), &z / (1 - &z).powi(2));Sourcepub fn dirichlet_eta(&self) -> Ex
pub fn dirichlet_eta(&self) -> Ex
Dirichlet eta function η(self) = Σ (−1)^{k+1}/k^s = (1 − 2^{1−s}) ζ(s).
Exact: η(1) = ln 2, η(0) = 1/2, and η(s) rewrites through ζ(s)
whenever s is an integer (so η(2) = π²/12).
Sourcepub fn airyai(&self) -> Ex
pub fn airyai(&self) -> Ex
Airy function of the first kind Ai(self).
Exact: Ai(0) = 1/(3^{2/3} Γ(2/3)), Ai(±∞) = 0; Ai' = airyaiprime.
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
assert_eq!(format!("{}", x.airyai().diff(&x)), "airyaiprime(x)");
assert_eq!(format!("{}", x.airyaiprime().diff(&x)), "x*airyai(x)");Sourcepub fn airybi(&self) -> Ex
pub fn airybi(&self) -> Ex
Airy function of the second kind Bi(self).
Exact: Bi(0) = 1/(3^{1/6} Γ(2/3)), Bi(−∞) = 0, Bi(∞) = ∞.
Sourcepub fn airyaiprime(&self) -> Ex
pub fn airyaiprime(&self) -> Ex
Derivative of the Airy function of the first kind Ai′(self).
Exact: Ai′(0) = −1/(3^{1/3} Γ(1/3)); d/dx Ai′(x) = x·Ai(x).
Sourcepub fn airybiprime(&self) -> Ex
pub fn airybiprime(&self) -> Ex
Derivative of the Airy function of the second kind Bi′(self).
Exact: Bi′(0) = 3^{1/6}/Γ(1/3); d/dx Bi′(x) = x·Bi(x).
Sourcepub fn elliptic_k(&self) -> Ex
pub fn elliptic_k(&self) -> Ex
Complete elliptic integral of the first kind
K(m) = ∫₀^{π/2} dθ / √(1 − m sin²θ) with self = m = k².
Exact: K(0) = π/2, K(1) = z∞;
d/dm K = (E(m) − (1 − m)K(m)) / (2m(1 − m)).
use symplex::prelude::*;
let ctx = Context::new();
assert_eq!(format!("{}", ctx.int(0).elliptic_k().eval()), "1/2*pi");
// K(1/2) = Γ(1/4)² / (4√π)
assert!((ctx.rational(1, 2).elliptic_k().eval_f64().unwrap() - 1.8540746773013719184).abs() < 1e-14);Sourcepub fn elliptic_e(&self) -> Ex
pub fn elliptic_e(&self) -> Ex
Complete elliptic integral of the second kind
E(m) = ∫₀^{π/2} √(1 − m sin²θ) dθ with self = m = k².
Exact: E(0) = π/2, E(1) = 1; d/dm E = (E(m) − K(m)) / (2m).
Sourcepub fn elliptic_f(&self, m: &Ex) -> Ex
pub fn elliptic_f(&self, m: &Ex) -> Ex
Incomplete elliptic integral of the first kind
F(φ | m) = ∫₀^φ dθ / √(1 − m sin²θ) with self = φ.
Exact: F(0 | m) = 0, F(φ | 0) = φ, F(π/2 | m) = K(m);
∂/∂φ F = 1/√(1 − m sin²φ) (the m-derivative stays formal).
Sourcepub fn elliptic_pi(&self, m: &Ex) -> Ex
pub fn elliptic_pi(&self, m: &Ex) -> Ex
Complete elliptic integral of the third kind
Π(n | m) = ∫₀^{π/2} dθ / ((1 − n sin²θ) √(1 − m sin²θ)) with self = n.
Exact: Π(0 | m) = K(m), Π(n | 0) = π/(2√(1 − n)), Π(n | n) = E(n)/(1 − n),
Π(1 | m) = z∞; both partial derivatives have closed forms in K, E, Π.
Sourcepub fn gegenbauer(&self, n: &Ex, a: &Ex) -> Ex
pub fn gegenbauer(&self, n: &Ex, a: &Ex) -> Ex
Gegenbauer (ultraspherical) polynomial C_n^{(a)}(self).
Expands to an explicit polynomial under eval for integer n ≥ 0;
C_n^{(1/2)} = P_n, C_n^{(1)} = U_n; d/dx C_n^{(a)} = 2a C_{n−1}^{(a+1)}.
use symplex::prelude::*;
let ctx = Context::new();
let (x, a) = (ctx.symbol("x"), ctx.symbol("a"));
assert_eq!(format!("{}", x.gegenbauer(&ctx.int(2), &a).eval()), "2*a^2*x^2 + 2*a*x^2 - a");Sourcepub fn jacobi(&self, n: &Ex, a: &Ex, b: &Ex) -> Ex
pub fn jacobi(&self, n: &Ex, a: &Ex, b: &Ex) -> Ex
Jacobi polynomial P_n^{(a, b)}(self).
Expands to an explicit polynomial under eval for integer n ≥ 0;
P_n^{(0,0)} = P_n; d/dx P_n^{(a,b)} = (n + a + b + 1)/2 · P_{n−1}^{(a+1, b+1)}.
Sourcepub fn assoc_legendre(&self, n: &Ex, m: &Ex) -> Ex
pub fn assoc_legendre(&self, n: &Ex, m: &Ex) -> Ex
Associated Legendre function P_n^m(self) (Condon–Shortley phase,
as in SymPy: P_1^1(x) = −√(1 − x²)).
Expands under eval for integer n ≥ 0 and integer m (zero when
|m| > n); P_n^0 = P_n;
d/dx P_n^m = (n x P_n^m − (n + m) P_{n−1}^m) / (x² − 1).
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
assert_eq!(format!("{}", x.assoc_legendre(&ctx.int(2), &ctx.int(2)).eval()), "-3*x^2 + 3");Sourcepub fn assoc_laguerre(&self, n: &Ex, a: &Ex) -> Ex
pub fn assoc_laguerre(&self, n: &Ex, a: &Ex) -> Ex
Generalised (associated) Laguerre polynomial L_n^{(a)}(self).
Expands under eval for integer n ≥ 0; L_n^{(0)} = L_n;
d/dx L_n^{(a)} = −L_{n−1}^{(a+1)}.
use symplex::prelude::*;
let ctx = Context::new();
let (x, a) = (ctx.symbol("x"), ctx.symbol("a"));
assert_eq!(format!("{}", x.assoc_laguerre(&ctx.int(1), &a).eval()), "a - x + 1");Sourcepub fn betainc(&self, a: &Ex, b: &Ex, x1: &Ex) -> Ex
pub fn betainc(&self, a: &Ex, b: &Ex, x1: &Ex) -> Ex
Generalised incomplete beta function
B_{(x₁, x₂)}(a, b) = ∫_{x₁}^{x₂} t^{a−1} (1 − t)^{b−1} dt.
Argument order. self is the upper limit x₂; the node is
stored in SymPy’s order betainc(a, b, x1, x2), so
x2.betainc(&a, &b, &x1) displays as betainc(a, b, x1, x2). The
classical incomplete beta B_x(a, b) is x.betainc(&a, &b, &zero).
Exact: betainc(a, b, x, x) = 0, betainc(a, b, 0, 1) = B(a, b), and
for positive integers a, b the integrand is a polynomial, so the
node expands to an explicit polynomial in x₁, x₂;
∂/∂x₂ = x₂^{a−1}(1 − x₂)^{b−1}, ∂/∂x₁ = −x₁^{a−1}(1 − x₁)^{b−1}
(parameter derivatives stay formal). Numerically evaluated for
a, b > 0 and 0 ≤ x₁, x₂ ≤ 1.
use symplex::prelude::*;
let ctx = Context::new();
let (a, b, x) = (ctx.symbol("a"), ctx.symbol("b"), ctx.symbol("x"));
let f = x.betainc(&a, &b, &ctx.int(0));
assert_eq!(format!("{f}"), "betainc(a, b, 0, x)");
assert_eq!(format!("{}", x.betainc(&ctx.int(2), &ctx.int(3), &ctx.int(0)).eval()), "1/4*x^4 - 2/3*x^3 + 1/2*x^2");Sourcepub fn betainc_regularized(&self, a: &Ex, b: &Ex, x1: &Ex) -> Ex
pub fn betainc_regularized(&self, a: &Ex, b: &Ex, x1: &Ex) -> Ex
Regularised generalised incomplete beta function
I_{(x₁, x₂)}(a, b) = B_{(x₁, x₂)}(a, b) / B(a, b).
Argument order. As for betainc: self is the
upper limit x₂ and the node is stored in SymPy’s order
betainc_regularized(a, b, x1, x2). x.betainc_regularized(&a, &b, &zero)
is the Beta-distribution CDF I_x(a, b).
Exact: I_{(x, x)} = 0, I_{(0, 1)}(a, b) = 1, and the polynomial
expansion (divided by B(a, b)) for positive integers a, b;
∂/∂x₂ = x₂^{a−1}(1 − x₂)^{b−1} / B(a, b).
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
// Beta(2, 3) CDF
let cdf = x.betainc_regularized(&ctx.int(2), &ctx.int(3), &ctx.int(0)).eval();
assert_eq!(format!("{cdf}"), "3*x^4 - 8*x^3 + 6*x^2");Sourcepub fn fps(&self, var: &Ex, point: &Ex) -> FormalPowerSeries
pub fn fps(&self, var: &Ex, point: &Ex) -> FormalPowerSeries
Compute the formal power series of this expression about point.
Returns a FormalPowerSeries
with exact, lazily computed coefficients (Ex-valued), a closed-form
general term for elementary functions, truncation, and series
arithmetic (add, mul, compose, inverse, reversion, …).
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
let series = x.exp().fps(&x, &ctx.int(0));
assert!(series.has_closed_form());
assert_eq!(series.coefficient(3).to_string(), "1/6");Sourcepub fn fps_maclaurin(&self, var: &Ex) -> FormalPowerSeries
pub fn fps_maclaurin(&self, var: &Ex) -> FormalPowerSeries
Compute the formal power series about 0 (Maclaurin series).
Convenience shorthand for self.fps(var, &zero).
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
let series = x.sin().fps_maclaurin(&x);
assert!(series.has_closed_form());
assert_eq!(series.truncate(6).to_string(), "1/120*x^5 - 1/6*x^3 + x");Sourcepub fn differentiate_finite(&self, var: &Ex, points: &[Ex], order: usize) -> Ex
pub fn differentiate_finite(&self, var: &Ex, points: &[Ex], order: usize) -> Ex
Finite-difference approximation of the order-th derivative of this
expression with respect to var, on the stencil points, evaluated
at var itself:
d^order self / d var^order ≈ Σᵢ wᵢ · self(var → points[i])The weights wᵢ are exact rationals (or exact expressions in h)
from Fornberg’s algorithm. Formal Derivative(f, var) nodes inside
self are replaced first, each by the finite difference of its own
order on the same stencil; order = 0 then simply performs that
replacement.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
let h = ctx.symbol("h");
let stencil = [&x - &h, x.clone(), &x + &h];
// central difference of x³ is exact up to the h² term: 3x² + h²
let d = x.powi(3).differentiate_finite(&x, &stencil, 1).expand();
assert_eq!(d.to_string(), "h^2 + 3*x^2");
// replace a formal derivative node
let d = x.sin().formal_diff(&x).differentiate_finite(&x, &stencil, 0);
assert!(!d.to_string().contains("Derivative"));Sourcepub fn factorize(&self) -> Option<Vec<(BigInt, u32)>>
pub fn factorize(&self) -> Option<Vec<(BigInt, u32)>>
Factorize this integer expression into prime factors.
Evaluates the expression and, if it is an exact integer, returns its
prime factorization as (BigInt_prime, exponent) pairs. Handles
arbitrary-precision integers without any f64 or i64 truncation.
Returns None if the expression is not an integer.
§Examples
use symplex::prelude::*;
use num_bigint::BigInt;
let ctx = Context::new();
let n = ctx.int(60);
let factors = n.factorize().unwrap();
assert_eq!(
factors,
vec![(BigInt::from(2), 2), (BigInt::from(3), 1), (BigInt::from(5), 1)]
);Sourcepub fn is_prime_value(&self) -> Option<bool>
pub fn is_prime_value(&self) -> Option<bool>
Check if this expression evaluates to a prime number.
Accesses the exact Ratio<BigInt> value in the arena and uses the
BigInt primality test — no lossy f64 conversion. Returns None
if the expression cannot be evaluated to an integer.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let n = ctx.int(104729);
assert_eq!(n.is_prime_value(), Some(true));
let n = ctx.int(60);
assert_eq!(n.is_prime_value(), Some(false));
let half = ctx.rational(1, 2);
assert_eq!(half.is_prime_value(), None);Sourcepub fn textplot(&self, var: &Ex, a: f64, b: f64) -> Result<String, SymplexError>
pub fn textplot(&self, var: &Ex, a: f64, b: f64) -> Result<String, SymplexError>
Generate an ASCII-art plot of this expression over [a, b].
Compiles the expression for fast numerical evaluation, performs domain-aware adaptive sampling (singularities are detected and skipped), and renders the result as a 60×21 character grid.
§Errors
SymplexError::InvalidArgumentifvaris not a symbol, or the range is not finite witha < b.SymplexError::FreeSymbolif the expression contains a symbol other thanvar.SymplexError::ComputationFailedif the expression has no finite real value anywhere on the range.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
let plot = x.sin().textplot(&x, 0.0, 6.28).unwrap();
assert!(plot.lines().count() >= 21);
let y = ctx.symbol("y");
assert!(matches!((&x + &y).textplot(&x, 0.0, 1.0), Err(SymplexError::FreeSymbol { .. })));
assert!(x.textplot(&x, 1.0, 0.0).is_err());Sourcepub fn to_svg(&self, var: &Ex, a: f64, b: f64) -> Result<String, SymplexError>
pub fn to_svg(&self, var: &Ex, a: f64, b: f64) -> Result<String, SymplexError>
Generate an SVG plot of this expression over [a, b].
Returns a self-contained SVG document with axes, grid, and the
function curve rendered as <polyline> segments (one per
continuous branch).
§Errors
Same conditions as textplot.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
let svg = x.sin().to_svg(&x, 0.0, 6.28).unwrap();
assert!(svg.starts_with("<svg") || svg.contains("<svg"));
assert!(svg.contains("</svg>"));Sourcepub fn to_tikz(&self, var: &Ex, a: f64, b: f64) -> Result<String, SymplexError>
pub fn to_tikz(&self, var: &Ex, a: f64, b: f64) -> Result<String, SymplexError>
Generate TikZ/PGFplots code for this expression over [a, b].
Returns a complete tikzpicture environment with axis options and
coordinate data, ready to \input into a LaTeX document that loads
pgfplots.
§Errors
Same conditions as textplot.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
let tikz = x.sin().to_tikz(&x, 0.0, 6.28).unwrap();
assert!(tikz.contains("\\begin{axis}"));
assert!(tikz.contains("\\end{tikzpicture}"));Sourcepub fn plot_data(
&self,
var: &Ex,
a: f64,
b: f64,
n: usize,
) -> Result<Vec<(f64, f64)>, SymplexError>
pub fn plot_data( &self, var: &Ex, a: f64, b: f64, n: usize, ) -> Result<Vec<(f64, f64)>, SymplexError>
Generate (x, y) sample data for this expression at n
uniformly-spaced points over [a, b] (both endpoints included).
Points where the function is not a finite real number produce NaN
y-values, so the returned vector always has exactly n entries.
§Errors
SymplexError::InvalidArgumentifvaris not a symbol,n < 2, or the range is not finite witha < b.SymplexError::FreeSymbolif the expression contains a symbol other thanvar.SymplexError::ComputationFailedif every sample is non-finite (e.g.ln(x)on[-2, -1]).
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
let data = x.powi(2).plot_data(&x, 0.0, 1.0, 11).unwrap();
assert_eq!(data.len(), 11);
assert!((data[5].1 - 0.25).abs() < 1e-12); // x = 0.5
assert!(x.plot_data(&x, 0.0, 1.0, 1).is_err());
assert!(x.ln().plot_data(&x, -2.0, -1.0, 5).is_err());Sourcepub fn eval_table(
&self,
var: &Ex,
points: &[f64],
) -> Result<DataTable, SymplexError>
pub fn eval_table( &self, var: &Ex, points: &[f64], ) -> Result<DataTable, SymplexError>
Evaluate this expression at each of points and return a two-column
DataTable (x, f(x))
for export to CSV, JSON, Markdown, LaTeX, ….
Non-finite results are recorded as NaN / Inf cells; they are not
an error.
§Errors
SymplexError::InvalidArgumentifvaris not a symbol or any input point is not finite.SymplexError::FreeSymbolif the expression contains a symbol other thanvar.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
let table = x.powi(2).eval_table(&x, &[0.0, 1.0, 2.0]).unwrap();
assert_eq!(table.nrows(), 3);
assert_eq!(table.rows[2], vec!["2", "4"]);
assert!(table.to_csv().starts_with("x,f(x)\n"));Source§impl Expr<Numeric>
impl Expr<Numeric>
Sourcepub fn integrate_definite(&self, var: &Ex, lo: &Ex, hi: &Ex) -> Ex
pub fn integrate_definite(&self, var: &Ex, lo: &Ex, hi: &Ex) -> Ex
Compute the definite integral ∫_lo^hi self dvar.
Unlike a naive F(hi) − F(lo), this locates singularities of the
integrand inside the interval, treats infinite bounds and endpoint
singularities as improper integrals via one-sided limits, applies
symmetry shortcuts, resolves Abs/Sign/Heaviside/DiracDelta/
Piecewise integrands, and consults a table of classical improper
integrals when no elementary antiderivative exists.
If the integral is divergent or cannot be evaluated the result is an
unevaluated definite-integral node Integral(f, x, lo, hi)
(see definite_integral_node) —
never a wrong finite number. Use
try_integrate_definite to
distinguish “diverges” from “could not compute”.
If self is, or contains, such a node, the inner integrals are
evaluated first (innermost out), so nested integrals can be built up
with repeated calls.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
// ∫₀¹ x² dx = 1/3
let v = x.powi(2).integrate_definite(&x, &ctx.int(0), &ctx.int(1));
assert_eq!(format!("{v}"), "1/3");
// ∫₀^∞ e^{−x} dx = 1
let v = (-&x).exp().integrate_definite(&x, &ctx.int(0), &ctx.infinity());
assert_eq!(format!("{v}"), "1");
// ∫₋∞^∞ e^{−x²} dx = √π
let v = (-x.powi(2)).exp().integrate_definite(&x, &ctx.neg_infinity(), &ctx.infinity());
assert_eq!(format!("{v}"), "sqrt(pi)");
// ∫₋₁¹ x⁻² dx diverges: the result stays unevaluated, bounds intact.
let v = x.powi(-2).integrate_definite(&x, &ctx.int(-1), &ctx.int(1));
assert!(v.has_unevaluated());
assert!(v.is_definite_integral());
assert_eq!(format!("{v}"), "Integral(x^(-2), x, -1, 1)");
// ∫₀¹ xˣ dx has no closed form; the node still has a numeric value.
let v = x.pow(&x).integrate_definite(&x, &ctx.int(0), &ctx.int(1));
assert!(v.is_definite_integral());
assert!((v.eval_f64().unwrap() - 0.7834305107).abs() < 1e-8);Sourcepub fn try_integrate_definite(
&self,
var: &Ex,
lo: &Ex,
hi: &Ex,
) -> Result<Ex, SymplexError>
pub fn try_integrate_definite( &self, var: &Ex, lo: &Ex, hi: &Ex, ) -> Result<Ex, SymplexError>
Like integrate_definite, but returns
Err instead of an unevaluated form.
SymplexError::Divergent— the integral was proven to diverge,SymplexError::ComputationFailed— no closed form could be established, or the result still contains an unevaluated form (aDefiniteIntegral,Integral,Limit, … node),SymplexError::InvalidArgument—varis not a symbol.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
let r = x.powi(-2).try_integrate_definite(&x, &ctx.int(-1), &ctx.int(1));
assert!(matches!(r, Err(SymplexError::Divergent { .. })));
let v = x.ln().try_integrate_definite(&x, &ctx.int(0), &ctx.int(1)).unwrap();
assert_eq!(format!("{v}"), "-1");Sourcepub fn definite_integral_node(&self, var: &Ex, lo: &Ex, hi: &Ex) -> Ex
pub fn definite_integral_node(&self, var: &Ex, lo: &Ex, hi: &Ex) -> Ex
Build the formal, unevaluated definite integral ∫_lo^hi self dvar
without attempting to evaluate it.
Useful for display, LaTeX, and formal manipulation (differentiation
by the Leibniz rule, substitution into the bounds, numeric
evaluation by quadrature). Only the cheap structural folds are
applied: lo == hi gives 0, an integrand free of var over a
finite interval gives self · (hi − lo), and numeric bounds with
lo > hi are reordered with a sign flip. To evaluate the node
later, call eval_integrals on it (or on any
expression containing it).
The integration variable is bound inside the integrand: it is not
reported by free_symbols and is not touched
by subs; the bounds are in the enclosing scope.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
let t = ctx.symbol("t");
let node = x.sin().definite_integral_node(&x, &ctx.int(0), &t);
assert!(node.is_definite_integral());
assert!(node.has_unevaluated());
assert_eq!(format!("{node}"), "Integral(sin(x), x, 0, t)");
assert_eq!(node.to_latex(), r"\int_{0}^{t} \sin\left(x\right)\, dx");
// Only `t` is free; `x` is bound.
assert_eq!(node.free_symbols().len(), 1);
// Leibniz rule: d/dt ∫₀ᵗ sin(x) dx = sin(t).
assert_eq!(format!("{}", node.diff(&t)), "sin(t)");
// Evaluating recovers the closed form 1 − cos(t).
let v = node.eval_integrals();
assert_eq!(format!("{v}"), "-cos(t) + 1");Sourcepub fn is_definite_integral(&self) -> bool
pub fn is_definite_integral(&self) -> bool
Is this expression an unevaluated definite integral node
(Integral(f, x, lo, hi))?
Only the root node is inspected; use
has_unevaluated to search the whole tree.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
// No closed form → the node comes back.
let v = x.pow(&x).integrate_definite(&x, &ctx.int(0), &ctx.int(1));
assert!(v.is_definite_integral());
// Closed form → a number.
let v = x.integrate_definite(&x, &ctx.int(0), &ctx.int(1));
assert!(!v.is_definite_integral());Sourcepub fn eval_integrals(&self) -> Ex
pub fn eval_integrals(&self) -> Ex
Evaluate every formal definite-integral node in this expression, innermost first.
This is the “doit” operation for Integral(f, x, lo, hi) nodes (the
analogue of eval_derivatives for
Derivative): each node is run through the definite integrator and
replaced by its closed form. Nodes that still cannot be evaluated —
no closed form, or a proven divergence — are left in place, so the
result is never a wrong finite number; check with
has_unevaluated.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
let t = ctx.symbol("t");
// d/dt ∫₀¹ sin(t·x) dx = ∫₀¹ x·cos(t·x) dx, then evaluate it.
let node = (&t * &x).sin().definite_integral_node(&x, &ctx.int(0), &ctx.int(1));
let d = node.diff(&t);
assert!(d.is_definite_integral());
let v = d.eval_integrals();
assert!(!v.has_unevaluated(), "{v}");
// No closed form: the node is returned unchanged.
let n = x.pow(&x).definite_integral_node(&x, &ctx.int(0), &ctx.int(1));
assert!(n.eval_integrals().is_definite_integral());Sourcepub fn integrate_numeric(
&self,
var: &Ex,
lo: &Ex,
hi: &Ex,
) -> Result<f64, SymplexError>
pub fn integrate_numeric( &self, var: &Ex, lo: &Ex, hi: &Ex, ) -> Result<f64, SymplexError>
Numerically integrate self over [lo, hi] with adaptive
Gauss–Kronrod (G7/K15) quadrature using the default QuadOpts.
Infinite bounds are supported. The integrand is compiled with
compile, so it must contain no free symbols other
than var and only nodes the compiler supports.
§Errors
SymplexError::FreeSymbol— another free symbol is present,SymplexError::NotImplemented— the integrand contains a node that cannot be compiled tof64code,SymplexError::Unevaluable— a bound is not a real number,SymplexError::ComputationFailed— the quadrature did not reach the requested accuracy (e.g. a divergent integral).
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
let v = x.sin().integrate_numeric(&x, &ctx.int(0), &ctx.pi()).unwrap();
assert!((v - 2.0).abs() < 1e-10);
// ∫₀^∞ e^{−x²} dx = √π/2
let v = (-x.powi(2)).exp().integrate_numeric(&x, &ctx.int(0), &ctx.infinity()).unwrap();
assert!((v - std::f64::consts::PI.sqrt() / 2.0).abs() < 1e-10);
// A divergent integral is reported as an error, not a number.
assert!(x.powi(-2).integrate_numeric(&x, &ctx.int(-1), &ctx.int(1)).is_err());Conditionally convergent or slowly decaying oscillatory tails (e.g.
sin(x)/x on [0, ∞)) are beyond plain adaptive quadrature and
also return an error; use integrate_definite
for those.
Sourcepub fn integrate_numeric_with(
&self,
var: &Ex,
lo: &Ex,
hi: &Ex,
opts: &QuadOpts,
) -> Result<QuadResult, SymplexError>
pub fn integrate_numeric_with( &self, var: &Ex, lo: &Ex, hi: &Ex, opts: &QuadOpts, ) -> Result<QuadResult, SymplexError>
Numerically integrate with explicit QuadOpts, returning the
estimate and its error estimate as a QuadResult.
The error estimate is returned even if the tolerance was not met
within max_subdivisions; check it before trusting the value.
§Examples
use symplex::prelude::*;
use symplex::definite::QuadOpts;
let ctx = Context::new();
let x = ctx.symbol("x");
let opts = QuadOpts { rel_tol: 1e-6, ..QuadOpts::default() };
let r = (-x.powi(2)).exp()
.integrate_numeric_with(&x, &ctx.neg_infinity(), &ctx.infinity(), &opts)
.unwrap();
assert!((r.value - std::f64::consts::PI.sqrt()).abs() < 1e-6);
assert!(r.error < 1e-4);Sourcepub fn residue_at_infinity(&self, var: &Ex) -> Ex
pub fn residue_at_infinity(&self, var: &Ex) -> Ex
Residue of self at var = ∞, defined as
Res_{z=∞} f(z) = −Res_{t=0} f(1/t)/t².
The sum of all finite residues plus the residue at infinity is
zero for a function meromorphic on the extended plane. If the
residue cannot be computed, a formal Residue node is returned.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let z = ctx.symbol("z");
// f = 1/z has residue 1 at 0, hence −1 at ∞.
let f = &ctx.int(1) / &z;
assert_eq!(format!("{}", f.residue_at_infinity(&z)), "-1");
// Polynomials: Res_{∞} z = −Res_{t=0} 1/t³ = 0.
assert_eq!(format!("{}", z.residue_at_infinity(&z)), "0");Source§impl Expr<Numeric>
impl Expr<Numeric>
Sourcepub fn as_rational(&self) -> Option<Ratio<BigInt>>
pub fn as_rational(&self) -> Option<Ratio<BigInt>>
The exact value if this expression is a numeric literal.
Returns None for anything that is not a plain number node (symbols,
pi, sqrt(2), unevaluated sums, …) — call
eval first if you want constant folding.
use symplex::prelude::*;
use num_bigint::BigInt;
use num_rational::Ratio;
let ctx = Context::new();
let r = ctx.rational(6, 4).as_rational().unwrap();
assert_eq!(r, Ratio::new(BigInt::from(3), BigInt::from(2)));
assert!(ctx.pi().as_rational().is_none());
assert!((&ctx.int(2).sqrt() * &ctx.int(2).sqrt()).eval().as_rational().is_some());Sourcepub fn as_ratio_parts(&self) -> Option<(BigInt, BigInt)>
pub fn as_ratio_parts(&self) -> Option<(BigInt, BigInt)>
Numerator and denominator (lowest terms, denominator positive) if this
expression is a rational literal — SymPy’s Rational.p / .q.
Unlike as_numer_denom, which decomposes
any expression symbolically, this returns plain integers and only
for numbers. Call eval first to fold constant
arithmetic such as 1/3 + 1/6.
use symplex::prelude::*;
use symplex::num_bigint::BigInt;
let ctx = Context::new();
let (p, q) = ctx.rational(6, -4).as_ratio_parts().unwrap();
assert_eq!((p, q), (BigInt::from(-3), BigInt::from(2)));
assert_eq!(ctx.int(7).as_ratio_parts(), Some((BigInt::from(7), BigInt::from(1))));
assert!(ctx.symbol("x").as_ratio_parts().is_none());Sourcepub fn as_ratio_i128(&self) -> Option<(i128, i128)>
pub fn as_ratio_i128(&self) -> Option<(i128, i128)>
Numerator and denominator as machine integers, if this expression is a
rational literal whose parts fit in i128.
The convenient form for comparing with literals or feeding other
exact-arithmetic code without touching BigInt:
use symplex::prelude::*;
let ctx = Context::new();
assert_eq!(ctx.rational(3, 31).as_ratio_i128(), Some((3, 31)));
assert_eq!((ctx.rational(1, 3) + ctx.rational(1, 6)).as_ratio_i128(), Some((1, 2)));
assert_eq!(ctx.int(-4).as_ratio_i128(), Some((-4, 1)));
// Too large for i128 → None (use `as_ratio_parts`).
assert!(ctx.from_bigint(symplex::num_bigint::BigInt::from(2).pow(200)).as_ratio_i128().is_none());Sourcepub fn as_bigint(&self) -> Option<BigInt>
pub fn as_bigint(&self) -> Option<BigInt>
The exact value if this expression is an integer literal.
use symplex::prelude::*;
use num_bigint::BigInt;
let ctx = Context::new();
assert_eq!(ctx.int(-7).as_bigint(), Some(BigInt::from(-7)));
assert_eq!(ctx.rational(1, 2).as_bigint(), None);Sourcepub fn as_i64(&self) -> Option<i64>
pub fn as_i64(&self) -> Option<i64>
The value if this expression is an integer literal that fits in i64.
use symplex::prelude::*;
let ctx = Context::new();
assert_eq!(ctx.int(42).as_i64(), Some(42));
assert_eq!(ctx.from_u64(u64::MAX).as_i64(), None);
assert_eq!(ctx.symbol("n").as_i64(), None);Sourcepub fn compare_numeric(&self, other: &Ex) -> Option<Ordering>
pub fn compare_numeric(&self, other: &Ex) -> Option<Ordering>
Three-valued numeric comparison of self and other.
Decision procedure, in order:
- Both are numeric literals → exact rational comparison.
d = (self − other).eval()is a literal → exact sign ofd; ifdisoo/-oo→Greater/Less.- The assumption system knows the sign of
d(e.g.a − bwithapositive andbnegative;x² + 1for realx). - Both are constants (no free symbols) → 16-digit numeric
evaluation; decided only if the values differ by more than
1e-9relative and both are real. - Otherwise
None.
§Examples
use std::cmp::Ordering;
use symplex::prelude::*;
let ctx = Context::new();
assert_eq!(ctx.rational(1, 3).compare_numeric(&ctx.rational(1, 2)), Some(Ordering::Less));
assert_eq!(ctx.pi().compare_numeric(&ctx.int(3)), Some(Ordering::Greater));
assert_eq!(ctx.int(2).sqrt().compare_numeric(&ctx.rational(3, 2)), Some(Ordering::Less));
let x = ctx.symbol("x");
assert_eq!((&x + 1).compare_numeric(&x), Some(Ordering::Greater));
assert_eq!(x.compare_numeric(&ctx.int(0)), None);
let p = ctx.symbol_with("p", &[Assumption::Positive]);
assert_eq!(p.compare_numeric(&ctx.int(0)), Some(Ordering::Greater));Sourcepub fn is_less_than(&self, other: &Ex) -> Option<bool>
pub fn is_less_than(&self, other: &Ex) -> Option<bool>
Is self < other? Three-valued; see
compare_numeric for the decision procedure.
use symplex::prelude::*;
let ctx = Context::new();
assert_eq!(ctx.int(1).is_less_than(&ctx.int(2)), Some(true));
assert_eq!(ctx.pi().is_less_than(&ctx.int(3)), Some(false));
let x = ctx.symbol("x");
assert_eq!(x.is_less_than(&ctx.int(3)), None);
// Assumptions help: x² ≥ 0 for real x, so x² < -1 is false.
let r = ctx.symbol_with("r", &[Assumption::Real]);
assert_eq!(r.powi(2).is_less_than(&ctx.int(-1)), Some(false));Sourcepub fn is_greater_than(&self, other: &Ex) -> Option<bool>
pub fn is_greater_than(&self, other: &Ex) -> Option<bool>
Is self > other? Three-valued; see
compare_numeric.
use symplex::prelude::*;
let ctx = Context::new();
assert_eq!(ctx.e().is_greater_than(&ctx.int(2)), Some(true));
assert_eq!(ctx.int(2).is_greater_than(&ctx.int(2)), Some(false));
assert_eq!(ctx.symbol("x").is_greater_than(&ctx.int(0)), None);Sourcepub fn probably_equal(&self, other: &Ex, samples: usize) -> Option<bool>
pub fn probably_equal(&self, other: &Ex, samples: usize) -> Option<bool>
Randomized equality test: evaluate both sides at samples random
rational points and compare.
Some(false)— a concrete point was found where the two sides differ. When both sides fold to exact rationals at that point this is a proof; when transcendental functions force floating-point evaluation the sides differ by more than1e-9relative.Some(true)—equalsproved it symbolically, or every sample agreed. The latter is probabilistic: for polynomial and rational identities the chance of a false positive is negligible after a few samples, but no proof is produced.None— no sample point could be evaluated (domain errors at every point) and the symbolic test was inconclusive.
Sample points are drawn from a fixed-seed generator keyed on the two
expressions, so results are reproducible. samples == 0 is treated
as 1.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
let lhs = (&x + 1).powi(3);
let rhs = &x.powi(3) + &x.powi(2) * 3 + &x * 3 + 1;
assert_eq!(lhs.probably_equal(&rhs, 5), Some(true));
assert_eq!(x.probably_equal(&ctx.symbol("y"), 5), Some(false));
assert_eq!(x.sin().probably_equal(&x.cos(), 5), Some(false));Sourcepub fn eval_at(&self, pairs: &[(&Ex, &Ex)]) -> Ex
pub fn eval_at(&self, pairs: &[(&Ex, &Ex)]) -> Ex
Substitute (symbol, value) pairs simultaneously and evaluate.
Shorthand for self.subs_map(pairs).eval(). The result is exact
and may still be symbolic if not every symbol was bound.
use symplex::prelude::*;
let ctx = Context::new();
let (x, y) = (ctx.symbol("x"), ctx.symbol("y"));
let f = &x.powi(2) + &y;
assert_eq!(format!("{}", f.eval_at(&[(&x, &ctx.int(3)), (&y, &ctx.rational(1, 2))])), "19/2");
assert_eq!(format!("{}", f.eval_at(&[(&x, &ctx.pi())])), "y + pi^2");Source§impl Expr<Numeric>
impl Expr<Numeric>
Sourcepub fn factor_all(&self) -> Ex
pub fn factor_all(&self) -> Ex
Factor over ℤ with the variable(s) inferred from the free symbols.
With one free symbol this is factor in that symbol;
with two to four symbols the polynomial is factored as a multivariate
polynomial (Kronecker substitution). Expressions that are not
polynomial, have no free symbols, or are already irreducible are
returned unchanged.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let (x, y) = (ctx.symbol("x"), ctx.symbol("y"));
let f = (&x.powi(2) - &y.powi(2)).factor_all();
let s = format!("{f}");
assert!(s.contains("x - y") && s.contains("x + y"), "{s}");Sourcepub fn factor_list(&self, var: &Ex) -> (Ex, Vec<(Ex, u32)>)
pub fn factor_list(&self, var: &Ex) -> (Ex, Vec<(Ex, u32)>)
Factor over ℤ and return the pieces: (content, [(factor, mult), …])
with self = content · ∏ factorᵢ^multᵢ.
Non-polynomial or constant input yields (1, [(self, 1)]).
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
// 2x³ − 2x² − 2x + 2 = 2 (x − 1)² (x + 1)
let f = &x.powi(3) * 2 - &x.powi(2) * 2 - &x * 2 + 2;
let (content, factors) = f.factor_list(&x);
assert_eq!(format!("{content}"), "2");
assert_eq!(factors.len(), 2);
assert_eq!(factors.iter().map(|(_, m)| *m).max(), Some(2));Sourcepub fn factor_list_all(&self) -> (Ex, Vec<(Ex, u32)>)
pub fn factor_list_all(&self) -> (Ex, Vec<(Ex, u32)>)
Like factor_list with the variables inferred
from the free symbols (see factor_all).
Sourcepub fn resultant(&self, other: &Ex, var: &Ex) -> Option<Ex>
pub fn resultant(&self, other: &Ex, var: &Ex) -> Option<Ex>
Resultant res_var(self, other) of two polynomials in var.
The resultant vanishes exactly when the two polynomials share a
root. Returns None if either expression is not polynomial in
var.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
let f = &x.powi(2) - 1;
let g = &x - 1;
assert_eq!(format!("{}", f.resultant(&g, &x).unwrap()), "0");
let h = &x - 3;
assert_eq!(format!("{}", f.resultant(&h, &x).unwrap()), "8");Sourcepub fn discriminant(&self, var: &Ex) -> Option<Ex>
pub fn discriminant(&self, var: &Ex) -> Option<Ex>
Discriminant of self as a polynomial in var.
disc(f) = (−1)^{n(n−1)/2} · res(f, f′) / lc(f); it is zero iff the
polynomial has a repeated root. Returns None for non-polynomial
or constant input.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
// ax² + bx + c has discriminant b² − 4ac: x² + 3x + 1 → 5
let f = &x.powi(2) + &x * 3 + 1;
assert_eq!(format!("{}", f.discriminant(&x).unwrap()), "5");
// (x − 1)² has a repeated root
let g = &x.powi(2) - &x * 2 + 1;
assert_eq!(format!("{}", g.discriminant(&x).unwrap()), "0");Sourcepub fn sqf_list(&self, var: &Ex) -> Option<(Ex, Vec<(Ex, u32)>)>
pub fn sqf_list(&self, var: &Ex) -> Option<(Ex, Vec<(Ex, u32)>)>
Square-free decomposition over ℤ: (content, [(a₁, 1), (a₂, 2), …])
with self = content · ∏ aᵢ^i, each aᵢ square-free and pairwise
coprime.
Returns None for non-polynomial or constant input.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
// x⁵ − x⁴ − x + 1 = (x − 1)(x⁴ − 1) = (x − 1)² · (x + 1)(x² + 1)
let f = &x.powi(5) - &x.powi(4) - &x + 1;
let (content, parts) = f.sqf_list(&x).unwrap();
assert_eq!(format!("{content}"), "1");
let mults: Vec<u32> = parts.iter().map(|(_, m)| *m).collect();
assert_eq!(mults, vec![1, 2]);Sourcepub fn square_free_part(&self, var: &Ex) -> Option<Ex>
pub fn square_free_part(&self, var: &Ex) -> Option<Ex>
Square-free part: the product of the distinct irreducible factors of
self (primitive, positive leading coefficient).
Returns None for non-polynomial or constant input.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
let f = (&x - 1).powi(3) * (&x + 2);
let sf = f.expand().square_free_part(&x).unwrap();
assert_eq!(format!("{sf}"), "x^2 + x - 2");Sourcepub fn is_squarefree(&self, var: &Ex) -> Option<bool>
pub fn is_squarefree(&self, var: &Ex) -> Option<bool>
Is self square-free as a polynomial in var (no repeated roots)?
Returns None for non-polynomial or constant input.
Sourcepub fn is_irreducible(&self, var: &Ex) -> Option<bool>
pub fn is_irreducible(&self, var: &Ex) -> Option<bool>
Is self irreducible over ℚ as a polynomial in var?
Returns None for non-polynomial or constant input. Non-unit
content is ignored (2x + 2 is irreducible).
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
assert_eq!((&x.powi(4) + 1).is_irreducible(&x), Some(true));
assert_eq!((&x.powi(4) + 4).is_irreducible(&x), Some(false));
assert_eq!(ctx.int(3).is_irreducible(&x), None);Sourcepub fn poly_div(&self, other: &Ex, var: &Ex) -> Option<(Ex, Ex)>
pub fn poly_div(&self, other: &Ex, var: &Ex) -> Option<(Ex, Ex)>
Polynomial division with remainder: (quotient, remainder) with
self = quotient · other + remainder and
deg remainder < deg other.
Returns None if either expression is not polynomial in var or
if other is zero.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
let f = &x.powi(3) + 1;
let g = &x + 1;
let (q, r) = f.poly_div(&g, &x).unwrap();
assert_eq!(format!("{q}"), "x^2 - x + 1");
assert_eq!(format!("{r}"), "0");Sourcepub fn poly_rem(&self, other: &Ex, var: &Ex) -> Option<Ex>
pub fn poly_rem(&self, other: &Ex, var: &Ex) -> Option<Ex>
Polynomial remainder (see poly_div).
Sourcepub fn poly_gcdex(&self, other: &Ex, var: &Ex) -> Option<(Ex, Ex, Ex)>
pub fn poly_gcdex(&self, other: &Ex, var: &Ex) -> Option<(Ex, Ex, Ex)>
Extended Euclidean algorithm: (s, t, g) with
s · self + t · other = g = gcd(self, other) and g monic.
Returns None if either expression is not polynomial in var.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
let f = &x.powi(2) - 1;
let g = &x.powi(2) - &x * 2 + 1; // (x − 1)²
let (s, t, gcd) = f.poly_gcdex(&g, &x).unwrap();
assert_eq!(format!("{gcd}"), "x - 1");
let check = (&s * &f + &t * &g).expand();
assert_eq!(format!("{check}"), "x - 1");Sourcepub fn decompose(&self, var: &Ex) -> Vec<Ex> ⓘ
pub fn decompose(&self, var: &Ex) -> Vec<Ex> ⓘ
Functional decomposition self = g₁ ∘ g₂ ∘ … ∘ gₖ into
indecomposable polynomials, outermost first.
Indecomposable polynomials return vec![self]; non-polynomial or
constant input returns an empty vector.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
// x⁴ + 2x² + 1 = (x² + 2x + 1) ∘ x²
let f = &x.powi(4) + &x.powi(2) * 2 + 1;
let parts: Vec<String> = f.decompose(&x).iter().map(|p| format!("{p}")).collect();
assert_eq!(parts, vec!["x^2 + 2*x + 1", "x^2"]);Sourcepub fn content_primitive(&self, var: &Ex) -> (Ex, Ex)
pub fn content_primitive(&self, var: &Ex) -> (Ex, Ex)
Split into (content, primitive_part) where content is the
rational GCD of the coefficients (signed so that the primitive part
has a positive leading coefficient) and self = content · primitive_part.
Non-polynomial input returns (1, self).
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
let f = &x.powi(2) * -4 + &x * 6;
let (c, p) = f.content_primitive(&x);
assert_eq!(format!("{c}"), "-2");
assert_eq!(format!("{p}"), "2*x^2 - 3*x");Sourcepub fn leading_coeff(&self, var: &Ex) -> Option<Ex>
pub fn leading_coeff(&self, var: &Ex) -> Option<Ex>
Leading coefficient of self as a polynomial in var.
Returns None for non-polynomial input; the zero polynomial has
leading coefficient 0. Coefficients may be exact numbers or
arbitrary expressions free of var.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let (x, a) = (ctx.symbol("x"), ctx.symbol("a"));
let f = &x.powi(3) * 5 - &x + 2;
assert_eq!(format!("{}", f.leading_coeff(&x).unwrap()), "5");
let g = &a * &x.powi(2) + &x + 1;
assert_eq!(g.leading_coeff(&x).unwrap(), a);
assert!(x.sin().leading_coeff(&x).is_none());Sourcepub fn poly_is_nonnegative_on(&self, var: &Ex, lo: &Ex, hi: &Ex) -> Option<bool>
pub fn poly_is_nonnegative_on(&self, var: &Ex, lo: &Ex, hi: &Ex) -> Option<bool>
Is self, a univariate polynomial in var with rational
coefficients, ≥ 0 at every point of the closed interval [lo, hi]?
The decision is exact: the square-free decomposition isolates the
roots of odd multiplicity (the only places where the sign changes), a
Sturm count checks that none lies strictly inside the interval, and
the constant sign on the rest of the interval is read off at one
point. Endpoints must be exact rationals or ±∞
(Context::infinity,
Context::neg_infinity).
An empty interval (lo > hi) is vacuously Some(true); the zero
polynomial is Some(true).
Returns None for non-polynomial input, symbolic coefficients, or
endpoints that are neither rational nor infinite.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
let (ninf, inf) = (ctx.neg_infinity(), ctx.infinity());
// (x − 1)² ≥ 0 everywhere
let sq = &x.powi(2) - &x * 2 + 1;
assert_eq!(sq.poly_is_nonnegative_on(&x, &ninf, &inf), Some(true));
// x³ − x is ≥ 0 on [2, ∞) but not on [−2, ∞)
let f = &x.powi(3) - &x;
assert_eq!(f.poly_is_nonnegative_on(&x, &ctx.int(2), &inf), Some(true));
assert_eq!(f.poly_is_nonnegative_on(&x, &ctx.int(-2), &inf), Some(false));
assert_eq!(x.sin().poly_is_nonnegative_on(&x, &ninf, &inf), None);Sourcepub fn poly_is_positive_on(&self, var: &Ex, lo: &Ex, hi: &Ex) -> Option<bool>
pub fn poly_is_positive_on(&self, var: &Ex, lo: &Ex, hi: &Ex) -> Option<bool>
Is self, a univariate polynomial in var with rational
coefficients, > 0 at every point of the closed interval [lo, hi]?
Same method and conventions as
poly_is_nonnegative_on, but any
root in the closed interval (including a root at an endpoint or a
root of even multiplicity) makes the answer Some(false), and the
zero polynomial is Some(false).
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
let (ninf, inf) = (ctx.neg_infinity(), ctx.infinity());
assert_eq!((&x.powi(2) + 1).poly_is_positive_on(&x, &ninf, &inf), Some(true));
// (x − 1)² touches zero at x = 1
let sq = &x.powi(2) - &x * 2 + 1;
assert_eq!(sq.poly_is_positive_on(&x, &ninf, &inf), Some(false));
assert_eq!(sq.poly_is_positive_on(&x, &ctx.int(2), &inf), Some(true));Sourcepub fn monic(&self, var: &Ex) -> Option<Ex>
pub fn monic(&self, var: &Ex) -> Option<Ex>
Monic version of self (divide by the leading coefficient).
Returns None for non-polynomial input or the zero polynomial.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
let f = &x.powi(2) * 2 + &x * 4;
assert_eq!(format!("{}", f.monic(&x).unwrap()), "x^2 + 2*x");Sourcepub fn poly_compose(&self, other: &Ex, var: &Ex) -> Option<Ex>
pub fn poly_compose(&self, other: &Ex, var: &Ex) -> Option<Ex>
Composition self(other): substitute the polynomial other for
var and expand.
Returns None if either expression is not polynomial in var.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
let f = &x.powi(2) + 1;
let g = &x - 1;
assert_eq!(format!("{}", f.poly_compose(&g, &x).unwrap()), "x^2 - 2*x + 2");Sourcepub fn poly_shift(&self, var: &Ex, a: &Ex) -> Option<Ex>
pub fn poly_shift(&self, var: &Ex, a: &Ex) -> Option<Ex>
Taylor shift self(var + a) for a rational constant a.
Returns None if self is not polynomial in var or a is not
an exact rational number.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
let f = x.powi(2);
assert_eq!(format!("{}", f.poly_shift(&x, &ctx.int(1)).unwrap()), "x^2 + 2*x + 1");Sourcepub fn poly_reverse(&self, var: &Ex) -> Option<Ex>
pub fn poly_reverse(&self, var: &Ex) -> Option<Ex>
Reciprocal polynomial varⁿ · self(1/var) (coefficients reversed).
Returns None if self is not polynomial in var.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
let f = &x.powi(3) * 2 + &x * 3 + 5;
assert_eq!(format!("{}", f.poly_reverse(&x).unwrap()), "5*x^3 + 3*x^2 + 2");Sourcepub fn poly_interpolate(points: &[(Ex, Ex)], var: &Ex) -> Option<Ex>
pub fn poly_interpolate(points: &[(Ex, Ex)], var: &Ex) -> Option<Ex>
Lagrange interpolation: the unique polynomial in var of degree
< points.len() passing through the given (x, y) points.
All coordinates must be exact rational numbers (integers or
Context::rational). Returns None for non-rational coordinates
or duplicate abscissae.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
let pts = [(ctx.int(0), ctx.int(1)), (ctx.int(1), ctx.int(2)), (ctx.int(2), ctx.int(5))];
let p = Ex::poly_interpolate(&pts, &x).unwrap();
assert_eq!(format!("{p}"), "x^2 + 1");Sourcepub fn count_real_roots(&self, var: &Ex) -> Option<usize>
pub fn count_real_roots(&self, var: &Ex) -> Option<usize>
Number of distinct real roots of self as a polynomial in var.
Uses a Sturm sequence, so the count is exact. Returns None for
non-polynomial or constant input.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
assert_eq!((&x.powi(3) - &x).count_real_roots(&x), Some(3));
assert_eq!((&x.powi(2) + 1).count_real_roots(&x), Some(0));Sourcepub fn count_real_roots_in(&self, var: &Ex, lo: &Ex, hi: &Ex) -> Option<usize>
pub fn count_real_roots_in(&self, var: &Ex, lo: &Ex, hi: &Ex) -> Option<usize>
Number of distinct real roots in the closed interval [lo, hi]
(Sturm’s theorem; SymPy’s Poly.count_roots(inf, sup)).
lo and hi must be exact rational numbers or ±∞
(Context::infinity, Context::neg_infinity). Returns None for
non-polynomial or constant input, or non-rational bounds.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
let f = &x.powi(3) - &x; // roots −1, 0, 1
assert_eq!(f.count_real_roots_in(&x, &ctx.int(0), &ctx.int(5)), Some(2));
assert_eq!(f.count_real_roots_in(&x, &ctx.rational(1, 2), &ctx.infinity()), Some(1));Sourcepub fn real_roots_isolate(&self, var: &Ex) -> Vec<Interval<Ex>>
pub fn real_roots_isolate(&self, var: &Ex) -> Vec<Interval<Ex>>
Isolating intervals for the distinct real roots of self in var.
Each returned Interval has exact rational endpoints, contains
exactly one real root, and has width at most 1/1024. Its kind
says where the root may lie: a Sturm bisection cell is the half-open
(lo, hi] (IntervalKind::LeftOpen;
the root is never lo and, since an exact hit is reported
separately, never hi either), while a root that a bisection point
lands on exactly is the closed singleton [r, r]
(IntervalKind::Closed with
lower == upper). Intervals are sorted. Non-polynomial or
constant input yields an empty vector.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
let iv = (&x.powi(2) - 2).real_roots_isolate(&x);
assert_eq!(iv.len(), 2);
let lo = iv[1].lower.eval_f64().unwrap();
let hi = iv[1].upper.eval_f64().unwrap();
assert!(lo <= 2f64.sqrt() && 2f64.sqrt() <= hi);
assert_eq!(iv[1].kind, IntervalKind::LeftOpen);
// x³ − x: the bisection lands on the root 0 exactly, so that one is
// the point [0, 0]; ±1 are bracketed by (lo, hi] cells.
let iv = (&x.powi(3) - &x).real_roots_isolate(&x);
assert_eq!(iv.len(), 3);
assert_eq!(iv[1].kind, IntervalKind::Closed);
assert_eq!(iv[1].lower, ctx.int(0));
assert_eq!(iv[1].upper, ctx.int(0));Sourcepub fn nroots(
&self,
var: &Ex,
digits: u32,
) -> Result<Vec<(f64, f64)>, SymplexError>
pub fn nroots( &self, var: &Ex, digits: u32, ) -> Result<Vec<(f64, f64)>, SymplexError>
All complex roots of self as a polynomial in var, numerically,
as (re, im) pairs sorted by real then imaginary part. A k-fold
root appears k times.
digits requests the working precision (clamped to a sensible
range; the output is f64 so more than ~16 digits has no visible
effect). Uses Aberth–Ehrlich simultaneous iteration on each
square-free part.
§Errors
InvalidArgument if self is not a non-constant polynomial in
var.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
let roots = (&x.powi(2) + 1).nroots(&x, 15).unwrap();
assert_eq!(roots.len(), 2);
assert!(roots.iter().all(|(re, im)| re.abs() < 1e-12 && (im.abs() - 1.0).abs() < 1e-12));Source§impl Expr<Numeric>
impl Expr<Numeric>
Sourcepub fn rewrite(&self, rules: &RuleSet) -> Ex
pub fn rewrite(&self, rules: &RuleSet) -> Ex
Rewrite with rules to a fixpoint (bottom-up, at most
RewriteOpts::default().max_iterations passes).
Every rule is a value-preserving identity supplied by the caller; the engine itself never changes the value of an expression beyond what the rules state.
§Examples
use symplex::prelude::*;
use symplex::macros::{Rule, RuleSet};
let ctx = Context::new();
let (x, y, a, b) = (ctx.symbol("x"), ctx.symbol("y"), ctx.symbol("a_"), ctx.symbol("b_"));
// ln(a_) + ln(b_) → ln(a_ * b_)
let rules = RuleSet::from_rules(vec![
Rule::new("ln_add", &(&a.ln() + &b.ln()), &(&a * &b).ln()),
]);
let expr = &x.ln() + &y.ln() + &(&x + 1).ln();
assert_eq!(format!("{}", expr.rewrite(&rules)), "ln(x*y*(x + 1))");Sourcepub fn rewrite_once(&self, rules: &RuleSet) -> Ex
pub fn rewrite_once(&self, rules: &RuleSet) -> Ex
A single bottom-up pass of rules (at most one rule application
per node).
§Examples
use symplex::prelude::*;
use symplex::macros::{Rule, RuleSet};
let ctx = Context::new();
let (x, a) = (ctx.symbol("x"), ctx.symbol("a_"));
let rules = RuleSet::from_rules(vec![Rule::new("dbl", &a.sin(), &(&a.sin() * 2))]);
// One pass: sin(x) → 2*sin(x); a second pass would give 4*sin(x).
assert_eq!(format!("{}", x.sin().rewrite_once(&rules)), "2*sin(x)");Sourcepub fn rewrite_with(&self, rules: &RuleSet, opts: &RewriteOpts) -> Ex
pub fn rewrite_with(&self, rules: &RuleSet, opts: &RewriteOpts) -> Ex
Rewrite with explicit RewriteOpts (strategy and iteration cap).
§Examples
use symplex::prelude::*;
use symplex::macros::{RewriteOpts, RewriteStrategy, Rule, RuleSet};
let ctx = Context::new();
let (x, a) = (ctx.symbol("x"), ctx.symbol("a_"));
// abs(abs(a_)) style nesting: exp(exp(a_)) → exp(a_)
let rules = RuleSet::from_rules(vec![Rule::new("flatten", &a.exp().exp(), &a.exp())]);
let expr = x.exp().exp().exp().exp();
let opts = RewriteOpts::default().strategy(RewriteStrategy::TopDown);
assert_eq!(format!("{}", expr.rewrite_with(&rules, &opts)), "exp(x)");Sourcepub fn rewrite_with_traced(
&self,
rules: &RuleSet,
opts: &RewriteOpts,
) -> (Ex, Vec<Step>)
pub fn rewrite_with_traced( &self, rules: &RuleSet, opts: &RewriteOpts, ) -> (Ex, Vec<Step>)
rewrite_with plus the trace of steps.
Sourcepub fn simplify_with_rules(&self, extra: &RuleSet) -> Ex
pub fn simplify_with_rules(&self, extra: &RuleSet) -> Ex
Standard simplify interleaved with the user
rules extra, iterated to a fixpoint (at most
SimplifyOpts::default().max_iterations rounds).
§Examples
use symplex::prelude::*;
use symplex::macros::{Rule, RuleSet};
let ctx = Context::new();
let (x, a) = (ctx.symbol("x"), ctx.symbol("a_"));
// A user-supplied identity: sinh(a_) → (exp(a_) - exp(-a_)) / 2
let sinh_def = (&a.exp() - &(-&a).exp()) / 2;
let extra = RuleSet::from_rules(vec![Rule::new("sinh_def", &a.sinh(), &sinh_def)]);
let expr = &x.sinh() - &(&x.exp() - &(-&x).exp()) / 2;
assert_eq!(format!("{}", expr.simplify_with_rules(&extra)), "0");Sourcepub fn simplify_traced(&self, opts: &SimplifyOpts) -> (Ex, Vec<Step>)
pub fn simplify_traced(&self, opts: &SimplifyOpts) -> (Ex, Vec<Step>)
simplify_with that also returns the trace
of strategies and rules that changed the expression.
The trace flag of opts is switched on automatically. Each
fixpoint iteration contributes one strategy:<name> step (before /
after the whole expression) followed by the rule-level steps that
fired inside the winning strategy.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
let expr = &x.sin().powi(2) + &x.cos().powi(2);
let (result, steps) = expr.simplify_traced(&SimplifyOpts::default());
assert_eq!(format!("{result}"), "1");
assert!(steps.iter().any(|s| s.rule_name.starts_with("strategy:")));
assert!(steps.iter().any(|s| s.rule_name == "pythagorean"));Source§impl Expr<Numeric>
impl Expr<Numeric>
Sourcepub fn expand_with(&self, opts: &ExpandOpts) -> Ex
pub fn expand_with(&self, opts: &ExpandOpts) -> Ex
Expansion with explicit hints — see ExpandOpts.
expand is expand_with(&ExpandOpts::default()).
§Examples
use symplex::prelude::*;
use symplex::macros::ExpandOpts;
let ctx = Context::new();
let (x, y) = (ctx.symbol("x"), ctx.symbol("y"));
let expr = &(&x + &y).sin() * &(&x + 1);
// Only distribute the product, leave sin(x + y) alone:
let mul_only = expr.expand_with(&ExpandOpts::none().with_mul(true));
assert_eq!(format!("{mul_only}"), "x*sin(x + y) + sin(x + y)");
// Trig expansion too:
let all = expr.expand_with(&ExpandOpts::default().trig(true));
assert!(format!("{all}").contains("cos(y)"), "{all}");Sourcepub fn expand_power_base(&self, force: bool) -> Ex
pub fn expand_power_base(&self, force: bool) -> Ex
Distribute powers over products: (x·y)^e → x^e·y^e.
Only fires when the identity is guaranteed — integer e, or all
factors known non-negative — unless force is set.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let (x, y, a) = (ctx.symbol("x"), ctx.symbol("y"), ctx.symbol("a"));
let expr = (&x * &y).pow(&a);
assert_eq!(format!("{}", expr.expand_power_base(false)), "(x*y)^a");
assert_eq!(format!("{}", expr.expand_power_base(true)), "x^a*y^a");
let p = ctx.symbol_with("p", &[Assumption::Positive]);
let q = ctx.symbol_with("q", &[Assumption::Positive]);
assert_eq!(format!("{}", (&p * &q).pow(&a).expand_power_base(false)), "p^a*q^a");Sourcepub fn expand_power_exp(&self, force: bool) -> Ex
pub fn expand_power_exp(&self, force: bool) -> Ex
Split sums in exponents: x^(a+b) → x^a·x^b, exp(a+b) → exp(a)·exp(b).
Guarded (base e, positive base, same-sign numeric or integer
summands) unless force is set. Note that the canonical form
merges equal bases in a product, so x^a·x^b immediately folds
back to x^(a+b); the split is only observable for exp, whose
factors are kept apart (they are recombined by
simplify_powers / exp_mul).
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let (x, a, b) = (ctx.symbol("x"), ctx.symbol("a"), ctx.symbol("b"));
assert_eq!(format!("{}", (&a + &b).exp().expand_power_exp(false)), "exp(a)*exp(b)");
// x^a * x^b is re-merged by canonicalisation:
assert_eq!(format!("{}", x.pow(&(&a + &b)).expand_power_exp(true)), "x^(a + b)");Sourcepub fn expand_multinomial(&self) -> Ex
pub fn expand_multinomial(&self) -> Ex
Expand non-negative integer powers of sums only (multinomial theorem); products are not distributed.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let (x, y) = (ctx.symbol("x"), ctx.symbol("y"));
let expr = (&x + &y).powi(3);
assert_eq!(expr.expand_multinomial(), expr.expand());
assert_eq!(expr.expand_multinomial().term_count(), 4);
let prod = &x * &(&x + 1);
assert_eq!(format!("{}", prod.expand_multinomial()), "x*(x + 1)");Sourcepub fn expand_log_with(&self, force: bool) -> Ex
pub fn expand_log_with(&self, force: bool) -> Ex
Expand logarithms, honouring the positivity guard unless force
is set (expand_log is the forced form).
ln(a·b) → ln a + ln b and ln(a^n) → n·ln a are exact for
positive real a, b (and real n); for other arguments they can
be off by a multiple of 2πi.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let (x, y) = (ctx.symbol("x"), ctx.symbol("y"));
let expr = (&x * &y).ln();
assert_eq!(format!("{}", expr.expand_log_with(false)), "ln(x*y)");
assert_eq!(format!("{}", expr.expand_log_with(true)), "ln(x) + ln(y)");
let p = ctx.symbol_with("p", &[Assumption::Positive]);
let q = ctx.symbol_with("q", &[Assumption::Positive]);
assert_eq!(format!("{}", (&p * &q).ln().expand_log_with(false)), "ln(p) + ln(q)");Sourcepub fn log_combine_with(&self, force: bool) -> Ex
pub fn log_combine_with(&self, force: bool) -> Ex
Combine logarithms, honouring the positivity guard unless force
is set (log_combine is the forced form).
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let (x, y) = (ctx.symbol("x"), ctx.symbol("y"));
let expr = &x.ln() + &y.ln();
assert_eq!(format!("{}", expr.log_combine_with(false)), "ln(x) + ln(y)");
assert_eq!(format!("{}", expr.log_combine_with(true)), "ln(x*y)");
let p = ctx.symbol_with("p", &[Assumption::Positive]);
assert_eq!(format!("{}", (&p.ln() * 2).log_combine_with(false)), "ln(p^2)");Sourcepub fn sqrtdenest(&self) -> Ex
pub fn sqrtdenest(&self) -> Ex
Denest square roots of the form √(a + b√c) with rational a,
b, c whenever a² − b²c is a perfect square.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let sqrt2 = ctx.int(2).sqrt();
let e = (&sqrt2 * 2 + 3).sqrt(); // √(3 + 2√2)
assert_eq!(format!("{}", e.sqrtdenest()), "sqrt(2) + 1");
let f = (5 - &ctx.int(6).sqrt() * 2).sqrt(); // √(5 − 2√6)
assert_eq!(format!("{}", f.sqrtdenest()), "sqrt(3) - sqrt(2)");Sourcepub fn signsimp(&self) -> Ex
pub fn signsimp(&self) -> Ex
Sign normalisation: extract −1 from sums with a negative leading
term when they occur inside products or integer powers.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let (x, y, z) = (ctx.symbol("x"), ctx.symbol("y"), ctx.symbol("z"));
assert_eq!(format!("{}", (&y - &x).powi(2).signsimp()), "(x - y)^2");
assert_eq!(format!("{}", (&y - &x).powi(3).signsimp()), "-(x - y)^3");
assert_eq!(format!("{}", (&z * &(&y - &x)).signsimp()), "-z*(x - y)");Sourcepub fn powdenest(&self, force: bool) -> Ex
pub fn powdenest(&self, force: bool) -> Ex
Denest powers, each rewrite only when it is an identity:
| Rewrite | Condition (any of) |
|---|---|
(a·b)^e → a^e·b^e | e ∈ ℤ; all factors known non-negative; force |
(x^a)^b → x^(a·b) | b ∈ ℤ; x > 0 and a real; force |
√(x²) → x | x ≥ 0; force |
√(x²) → ∣x∣ | x real (not known non-real) |
(x^a)^b = exp(b·Log(exp(a·Log x))) equals x^(ab) exactly when
Im(a·Log x) ∈ (−π, π] — guaranteed for x > 0 and real a — or
when b is an integer. √(x²) = |x| needs x real (√(i²) = i).
With force = true every symbol is treated as positive (like SymPy’s
powdenest(force=True)).
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let (x, a, b) = (ctx.symbol("x"), ctx.symbol("a"), ctx.symbol("b"));
let nested = x.pow(&a).pow(&b);
assert_eq!(format!("{}", nested.powdenest(false)), "(x^a)^b");
assert_eq!(format!("{}", nested.powdenest(true)), "x^(a*b)");
assert_eq!(format!("{}", x.pow(&a).powi(3).powdenest(false)), "x^(3*a)");
assert_eq!(format!("{}", x.powi(2).sqrt().powdenest(false)), "abs(x)");
let p = ctx.symbol_with("p", &[Assumption::Positive]);
assert_eq!(format!("{}", p.powi(2).sqrt().powdenest(false)), "p");Sourcepub fn rcollect(&self, vars: &[&Ex]) -> Ex
pub fn rcollect(&self, vars: &[&Ex]) -> Ex
Recursively collect every sum by the given variables, in order.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let (x, y, z) = (ctx.symbol("x"), ctx.symbol("y"), ctx.symbol("z"));
let expr = &x * &y + &x * &z + &y.powi(2) * &x + &y.powi(2) * &z;
let c = expr.rcollect(&[&x, &y]);
assert_eq!(format!("{c}"), "z*y^2 + x*(y^2 + y + z)");Sourcepub fn collect_const(&self) -> Ex
pub fn collect_const(&self) -> Ex
Factor the rational content out of sums inside products and powers:
z·(2x + 4y) → 2·z·(x + 2y).
A top-level sum is returned unchanged, because the canonical form
distributes a numeric coefficient over a sum (2·(x + 2y) is not
representable); use factor_terms to obtain
the (2, x + 2y) pair.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let (x, y, z) = (ctx.symbol("x"), ctx.symbol("y"), ctx.symbol("z"));
let expr = &z * &(&x * 2 + &y * 4);
assert_eq!(format!("{}", expr.collect_const()), "2*z*(x + 2*y)");
let sq = (&x * 2 + &y * 4).powi(2);
assert_eq!(format!("{}", sq.collect_const()), "4*(x + 2*y)^2");Sourcepub fn nsimplify_with_constants(&self, constants: &[&Ex], tolerance: f64) -> Ex
pub fn nsimplify_with_constants(&self, constants: &[&Ex], tolerance: f64) -> Ex
Recognise a numerical expression as q·c, q + c or c^(p/q) for
one of the given constants c (or as a plain rational), within
tolerance. Falls back to simplify_numeric.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let pi = ctx.pi();
let e = ctx.e();
let v = ctx.rational(628318530717958, 100000000000000); // ≈ 2π
assert_eq!(format!("{}", v.nsimplify_with_constants(&[&pi, &e], 1e-9)), "2*pi");
let w = ctx.rational(3718281828459045, 1000000000000000); // ≈ 1 + e
assert_eq!(format!("{}", w.nsimplify_with_constants(&[&pi, &e], 1e-9)), "1 + E");Sourcepub fn nsimplify(&self, tolerance: f64) -> Ex
pub fn nsimplify(&self, tolerance: f64) -> Ex
nsimplify_with_constants with the
default table π, e, √2, √3, √5, ln 2, φ, γ.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let v = ctx.rational(1414213562373095, 1000000000000000); // ≈ √2
assert_eq!(format!("{}", v.nsimplify(1e-9)), "sqrt(2)");
let g = ctx.rational(5772156649015329, 10000000000000000); // ≈ γ
assert_eq!(format!("{}", g.nsimplify(1e-9)), "EulerGamma");Sourcepub fn separate_vars_additive(&self, vars: &[&Ex]) -> Vec<(Vec<Ex>, Ex)>
pub fn separate_vars_additive(&self, vars: &[&Ex]) -> Vec<(Vec<Ex>, Ex)>
Additive separation: partition the terms of a sum by the variables they depend on (terms with the same dependency set are summed).
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let (x, y) = (ctx.symbol("x"), ctx.symbol("y"));
let expr = &x.sin() + &y.powi(2) + &x * &y + 3;
let groups = expr.separate_vars_additive(&[&x, &y]);
assert_eq!(groups.len(), 4);
let mixed = groups.iter().find(|(deps, _)| deps.len() == 2).unwrap();
assert_eq!(format!("{}", mixed.1), "x*y");Sourcepub fn separate_vars_dict(&self, vars: &[&Ex]) -> Option<Vec<(Ex, Ex)>>
pub fn separate_vars_dict(&self, vars: &[&Ex]) -> Option<Vec<(Ex, Ex)>>
Multiplicative separation into one factor per variable.
Returns Some(pairs) with one (var, factor) entry per variable in
vars (factor 1 when the expression does not depend on it) such
that the product of all factors equals self; a factor free of
every variable is folded into the first variable’s entry. Returns
None when some factor depends on two or more variables. Sums are
first written as content·(…) so that x·y + x·z separates.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let (x, y) = (ctx.symbol("x"), ctx.symbol("y"));
let expr = &x.sin() * &y.powi(2) * 2;
let d = expr.separate_vars_dict(&[&x, &y]).unwrap();
assert_eq!(format!("{}", d[0].1), "2*sin(x)");
assert_eq!(format!("{}", d[1].1), "y^2");
assert!((&x + &y).separate_vars_dict(&[&x, &y]).is_none());
// x*y + x*z = x*(y + z)
let z = ctx.symbol("z");
let e = &x * &y + &x * &z;
assert!(e.separate_vars_dict(&[&x, &y, &z]).is_none(), "y + z mixes y and z");
let d = e.separate_vars_dict(&[&x, &y]).unwrap(); // z is a parameter here
assert_eq!(format!("{}", d[0].1), "x");
assert_eq!(format!("{}", d[1].1), "y + z");Sourcepub fn subs_algebraic(&self, old: &Ex, new: &Ex) -> Ex
pub fn subs_algebraic(&self, old: &Ex, new: &Ex) -> Ex
Algebraic substitution old → new: unlike subs,
old is recognised inside powers, products and sums.
self | old | result |
|---|---|---|
x^4 | x^2 | y^2 |
x^3 | x^2 | x·y |
1/x^2 | x^2 | 1/y |
2·x·y·z | x·y | 2·w·z |
a + b + c | a + b | c + d |
exp(2x) | exp(x) | t^2 |
Every rewrite is an identity in old, so substituting old back
for new recovers the value of self.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let (x, y) = (ctx.symbol("x"), ctx.symbol("y"));
assert_eq!(format!("{}", x.powi(4).subs_algebraic(&x.powi(2), &y)), "y^2");
assert_eq!(format!("{}", x.powi(3).subs_algebraic(&x.powi(2), &y)), "x*y");
assert_eq!(format!("{}", x.powi(-2).subs_algebraic(&x.powi(2), &y)), "1/y");Source§impl Expr<Numeric>
impl Expr<Numeric>
Sourcepub fn summation(&self, var: &Ex, lower: &Ex, upper: &Ex) -> Ex
pub fn summation(&self, var: &Ex, lower: &Ex, upper: &Ex) -> Ex
Evaluate Σ_{var=lower}^{upper} self symbolically.
Bounds may be concrete integers, symbolic expressions, or
ctx.infinity() / ctx.neg_infinity(). Strategies include exact
enumeration for small concrete ranges, Faulhaber’s formula (any
degree, Bernoulli numbers), partial-fraction telescoping, harmonic
numbers, geometric and arithmetico-geometric series, binomial
identities (Σ P(k)·C(n,k)·xᵏ for any polynomial P), Gosper’s
algorithm, and — for infinite sums — p-series (ζ(2m) in closed
form, ζ(2m+1) as a zeta node, Σ (−1)ᵏ/(2k+1)² = G), alternating
series, and a table of classical power series (Σ xᵏ/k! = eˣ, sin,
cos, atan, …).
Convergence of a geometric series with a symbolic ratio cannot be
decided (there is no |r| < 1 assumption), so Σ_{k≥0} rᵏ with
symbolic r stays unevaluated; numeric ratios are decided exactly.
When no closed form is known the formal Sum node is returned.
Sums that provably diverge to ±∞ evaluate to oo / -oo; for
oscillating divergence the formal Sum is kept. Use
try_summation to get an error instead.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let k = ctx.symbol("k");
let n = ctx.symbol("n");
// Σ_{k=1}^{n} k² = n³/3 + n²/2 + n/6
let s = k.powi(2).summation(&k, &ctx.int(1), &n);
assert_eq!(s.subs_i64(&n, 10).eval().to_string(), "385");
// Σ_{k=1}^{∞} 1/k² = π²/6
let basel = k.powi(-2).summation(&k, &ctx.int(1), &ctx.infinity());
assert_eq!(basel.to_string(), "1/6*pi^2");
// Σ_{k=0}^{∞} x^k/k! = e^x
let x = ctx.symbol("x");
let e = (x.pow(&k) / k.factorial()).summation(&k, &ctx.int(0), &ctx.infinity());
assert_eq!(e.to_string(), "exp(x)");
// Σ_{k=1}^{∞} 1/k³ = ζ(3)
let apery = k.powi(-3).summation(&k, &ctx.int(1), &ctx.infinity());
assert_eq!(apery.to_string(), "zeta(3)");Sourcepub fn try_summation(
&self,
var: &Ex,
lower: &Ex,
upper: &Ex,
) -> Result<Ex, SymplexError>
pub fn try_summation( &self, var: &Ex, lower: &Ex, upper: &Ex, ) -> Result<Ex, SymplexError>
Like summation, but returns Err when the sum
diverges (SymplexError::Divergent) or no closed form was found
(SymplexError::ComputationFailed).
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let k = ctx.symbol("k");
let harmonic = k.powi(-1).try_summation(&k, &ctx.int(1), &ctx.infinity());
assert!(matches!(harmonic, Err(SymplexError::Divergent { .. })));
let geometric = ctx.rational(1, 2).pow(&k).try_summation(&k, &ctx.int(0), &ctx.infinity());
assert_eq!(geometric.unwrap().to_string(), "2");Sourcepub fn product_over(&self, var: &Ex, lower: &Ex, upper: &Ex) -> Ex
pub fn product_over(&self, var: &Ex, lower: &Ex, upper: &Ex) -> Ex
Evaluate Π_{var=lower}^{upper} self symbolically.
Handles constant factors (cⁿ), Π k = n!, Π (k+a) as Gamma /
factorial ratios, Π aᶠ⁽ᵏ⁾ = a^{Σ f(k)}, products of factors, and
rational functions whose numerator and denominator factor into
linear factors over ℚ (Π (1 − 1/k²) = (n+1)/(2n)). Infinite
products are evaluated through the limit of the finite closed form
when that limit is exactly computable.
Returns the formal Product node when no closed form is known.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let k = ctx.symbol("k");
let n = ctx.symbol("n");
assert_eq!(k.product_over(&k, &ctx.int(1), &n).to_string(), "n!");
// Π_{k=1}^{n} (1 + 1/k) = n + 1
let p = (&ctx.int(1) + &k.powi(-1)).product_over(&k, &ctx.int(1), &n);
assert_eq!(p.to_string(), "n + 1");
// Π_{k=2}^{∞} (1 − 1/k²) = 1/2
let p = (&ctx.int(1) - &k.powi(-2)).product_over(&k, &ctx.int(2), &ctx.infinity());
assert_eq!(p.to_string(), "1/2");Sourcepub fn try_product_over(
&self,
var: &Ex,
lower: &Ex,
upper: &Ex,
) -> Result<Ex, SymplexError>
pub fn try_product_over( &self, var: &Ex, lower: &Ex, upper: &Ex, ) -> Result<Ex, SymplexError>
Like product_over, but returns Err when the
product diverges or no closed form was found.
Sourcepub fn hypergeometric_ratio(&self, var: &Ex) -> Option<Ex>
pub fn hypergeometric_ratio(&self, var: &Ex) -> Option<Ex>
If self is a hypergeometric term in var, return the ratio
self(var+1) / self(var) as a rational function of var.
Returns None when the ratio is not a rational function of var
(e.g. for sin(k) or k^k). Symbolic parameters other than var
are allowed.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let k = ctx.symbol("k");
let n = ctx.symbol("n");
let r = k.factorial().hypergeometric_ratio(&k).unwrap();
assert_eq!(r.to_string(), "k + 1");
let r = n.binomial(&k).hypergeometric_ratio(&k).unwrap();
assert_eq!(r.subs_i64(&n, 5).subs_i64(&k, 2).eval().to_string(), "1");
assert!(k.sin().hypergeometric_ratio(&k).is_none());Sourcepub fn is_absolutely_convergent(&self, var: &Ex) -> Option<bool>
pub fn is_absolutely_convergent(&self, var: &Ex) -> Option<bool>
Test whether Σ |self(var)| converges (absolute convergence).
Sign-alternating factors such as (−1)^k are stripped before the
convergence tests run, so conditionally convergent series like
Σ (−1)^k/k return Some(false) here but Some(true) from
is_convergent.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let k = ctx.symbol("k");
let alt = ctx.int(-1).pow(&k) / &k;
assert_eq!(alt.is_convergent(&k), Some(true));
assert_eq!(alt.is_absolutely_convergent(&k), Some(false));Sourcepub fn series_at_infinity(&self, var: &Ex, n_terms: u32) -> Ex
pub fn series_at_infinity(&self, var: &Ex, n_terms: u32) -> Ex
Asymptotic expansion of self as var → +∞, with n_terms terms
in powers of 1/var.
Internally substitutes var = 1/t, expands (Laurent-)series at
t = 0, and substitutes back. Returns a formal Series node when
the expansion cannot be computed.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
// x/(x+1) = 1 − 1/x + 1/x² − …
let s = (&x / &(&x + 1)).series_at_infinity(&x, 3);
let at_10 = s.subs_i64(&x, 10).eval_f64().unwrap();
assert!((at_10 - 0.91).abs() < 1e-12);Sourcepub fn series_at_neg_infinity(&self, var: &Ex, n_terms: u32) -> Ex
pub fn series_at_neg_infinity(&self, var: &Ex, n_terms: u32) -> Ex
Asymptotic expansion of self as var → −∞ (see
series_at_infinity).
Sourcepub fn try_series_at_infinity(
&self,
var: &Ex,
n_terms: u32,
) -> Result<Ex, SymplexError>
pub fn try_series_at_infinity( &self, var: &Ex, n_terms: u32, ) -> Result<Ex, SymplexError>
Like series_at_infinity, but returns
Err if the expansion could not be computed.
Source§impl Expr<SetValued>
impl Expr<SetValued>
Sourcepub fn simplify(&self) -> SetEx
pub fn simplify(&self) -> SetEx
Evaluate a set expression to normal form: a union of pairwise-disjoint, ascending intervals followed by one finite set of isolated points, whenever all endpoints are comparable real numbers.
Sets with symbolic endpoints stay structural, but the safe
identities (A ∪ ∅ = A, A ∩ U = A, A ∪ A = A, A \ A = ∅,
(Aᶜ)ᶜ = A, flattening, deduplication) are still applied.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let a = ctx.interval(&ctx.int(0), &ctx.int(2), IntervalKind::Closed);
let b = ctx.interval(&ctx.int(1), &ctx.int(3), IntervalKind::Closed);
assert_eq!(format!("{}", a.intersection(&b).simplify()), "[1, 2]");
assert_eq!(format!("{}", a.union(&b).simplify()), "[0, 3]");
// Solver output: (−∞,−2) ∪ (2,∞) intersected with [0, 5] → (2, 5]
let x = ctx.symbol("x");
let sol = (&x.powi(2) - 4).solve_gt(&x);
let window = ctx.interval(&ctx.int(0), &ctx.int(5), IntervalKind::Closed);
assert_eq!(format!("{}", sol.intersection(&window).simplify()), "(2, 5]");Sourcepub fn eval(&self) -> SetEx
pub fn eval(&self) -> SetEx
Exact evaluation of the numeric endpoints / elements of the set
([sqrt(4), 3] → [2, 3]). Does not perform set algebra; see
simplify for that.
Sourcepub fn difference(&self, other: &SetEx) -> SetEx
pub fn difference(&self, other: &SetEx) -> SetEx
Set difference self \ other, evaluated to normal form.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let a = ctx.interval(&ctx.int(0), &ctx.int(3), IntervalKind::Closed);
let b = ctx.interval(&ctx.int(1), &ctx.int(2), IntervalKind::Closed);
assert_eq!(format!("{}", a.difference(&b)), "[0, 1) ∪ (2, 3]");Sourcepub fn symmetric_difference(&self, other: &SetEx) -> SetEx
pub fn symmetric_difference(&self, other: &SetEx) -> SetEx
Symmetric difference (self \ other) ∪ (other \ self), evaluated to
normal form.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let a = ctx.interval(&ctx.int(0), &ctx.int(2), IntervalKind::Closed);
let b = ctx.interval(&ctx.int(1), &ctx.int(3), IntervalKind::Closed);
assert_eq!(format!("{}", a.symmetric_difference(&b)), "[0, 1) ∪ (2, 3]");Sourcepub fn absolute_complement(&self) -> SetEx
pub fn absolute_complement(&self) -> SetEx
Absolute complement ℝ \ self, evaluated to normal form.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let a = ctx.interval(&ctx.int(0), &ctx.int(1), IntervalKind::RightOpen);
assert_eq!(format!("{}", a.absolute_complement()), "(-oo, 0) ∪ [1, oo)");
assert_eq!(format!("{}", ctx.empty_set().absolute_complement()), "(-oo, oo)");Sourcepub fn contains(&self, elem: &Ex) -> Option<bool>
pub fn contains(&self, elem: &Ex) -> Option<bool>
Set membership: is elem ∈ self?
Three-valued: Some(true) / Some(false) when membership can be
decided (numeric elements in evaluable sets, exact differences such
as x ∈ [x, x + 1], substitution into ConditionSets), None
otherwise — never a guess.
For the structural “appears as a sub-expression” check, use
set.as_ex().contains(&needle).
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let a = ctx.interval(&ctx.int(0), &ctx.int(1), IntervalKind::LeftOpen); // (0, 1]
assert_eq!(a.contains(&ctx.int(0)), Some(false));
assert_eq!(a.contains(&ctx.int(1)), Some(true));
assert_eq!(a.contains(&ctx.rational(1, 2)), Some(true));
assert_eq!(a.contains(&ctx.symbol("x")), None);Sourcepub fn is_subset(&self, other: &SetEx) -> Option<bool>
pub fn is_subset(&self, other: &SetEx) -> Option<bool>
Is self ⊆ other? Three-valued.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let a = ctx.interval(&ctx.int(0), &ctx.int(1), IntervalKind::Closed);
let b = ctx.interval(&ctx.int(-1), &ctx.int(2), IntervalKind::Open);
assert_eq!(a.is_subset(&b), Some(true));
assert_eq!(b.is_subset(&a), Some(false));Sourcepub fn is_superset(&self, other: &SetEx) -> Option<bool>
pub fn is_superset(&self, other: &SetEx) -> Option<bool>
Is self ⊇ other? Three-valued.
Sourcepub fn is_disjoint(&self, other: &SetEx) -> Option<bool>
pub fn is_disjoint(&self, other: &SetEx) -> Option<bool>
Is self ∩ other = ∅? Three-valued.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let a = ctx.interval(&ctx.int(0), &ctx.int(1), IntervalKind::RightOpen); // [0, 1)
let b = ctx.interval(&ctx.int(1), &ctx.int(2), IntervalKind::Closed); // [1, 2]
assert_eq!(a.is_disjoint(&b), Some(true));Sourcepub fn is_empty(&self) -> Option<bool>
pub fn is_empty(&self) -> Option<bool>
Is the set empty? Three-valued.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let a = ctx.interval(&ctx.int(0), &ctx.int(1), IntervalKind::Closed);
let b = ctx.interval(&ctx.int(2), &ctx.int(3), IntervalKind::Closed);
assert_eq!(a.intersection(&b).is_empty(), Some(true));
assert_eq!(a.is_empty(), Some(false));Sourcepub fn inf(&self) -> Option<Ex>
pub fn inf(&self) -> Option<Ex>
Greatest lower bound (-oo when unbounded below).
Returns None for the empty set or when the set cannot be
evaluated to normal form.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let a = ctx.interval(&ctx.int(0), &ctx.int(1), IntervalKind::Open);
let b = ctx.finite_set(&[ctx.int(5)]);
let u = a.union(&b);
assert_eq!(format!("{}", u.inf().unwrap()), "0");
assert_eq!(format!("{}", u.sup().unwrap()), "5");
assert!(ctx.empty_set().inf().is_none());Sourcepub fn measure(&self) -> Option<Ex>
pub fn measure(&self) -> Option<Ex>
Lebesgue measure (total length); oo for unbounded sets, 0 for
finite sets. None when the set cannot be evaluated.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let a = ctx.interval(&ctx.int(0), &ctx.int(1), IntervalKind::Closed);
let b = ctx.interval(&ctx.int(2), &ctx.rational(5, 2), IntervalKind::Open);
assert_eq!(format!("{}", a.union(&b).measure().unwrap()), "3/2");
assert_eq!(format!("{}", ctx.reals().measure().unwrap()), "oo");Sourcepub fn boundary(&self) -> Option<SetEx>
pub fn boundary(&self) -> Option<SetEx>
Topological boundary (the finite endpoints and isolated points).
None when the set cannot be evaluated to normal form.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let a = ctx.interval(&ctx.int(0), &ctx.int(1), IntervalKind::Open);
assert_eq!(format!("{}", a.boundary().unwrap()), "{0, 1}");
assert_eq!(format!("{}", a.closure().unwrap()), "[0, 1]");
assert_eq!(format!("{}", a.closure().unwrap().interior().unwrap()), "(0, 1)");Sourcepub fn closure(&self) -> Option<SetEx>
pub fn closure(&self) -> Option<SetEx>
Topological closure. None when the set cannot be evaluated.
Sourcepub fn interior(&self) -> Option<SetEx>
pub fn interior(&self) -> Option<SetEx>
Topological interior. None when the set cannot be evaluated.
Sourcepub fn is_open(&self) -> Option<bool>
pub fn is_open(&self) -> Option<bool>
Is the set open in ℝ? Three-valued.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let a = ctx.interval(&ctx.int(0), &ctx.int(1), IntervalKind::Open);
assert_eq!(a.is_open(), Some(true));
assert_eq!(a.is_closed(), Some(false));
assert_eq!(ctx.reals().is_open(), Some(true));
assert_eq!(ctx.reals().is_closed(), Some(true));Sourcepub fn as_intervals(&self) -> Option<Vec<Interval<Ex>>>
pub fn as_intervals(&self) -> Option<Vec<Interval<Ex>>>
Normal-form accessor: the pieces of the set as Interval<Ex>s in
ascending order (an unbounded end is ±∞ and open there). Isolated
points appear as Interval::point(p). None when the set cannot
be evaluated to normal form.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
let sol = (&x.powi(2) - 4).solve_gt(&x); // (−∞, −2) ∪ (2, ∞)
let parts = sol.as_intervals().unwrap();
assert_eq!(parts.len(), 2);
assert_eq!(format!("{}", parts[0].lower), "-oo");
assert_eq!(format!("{}", parts[0].upper), "-2");
assert_eq!(parts[0].kind, IntervalKind::Open);
// and back to a set:
assert_eq!(format!("{}", parts[1].to_set()), "(2, oo)");Sourcepub fn as_finite_set(&self) -> Option<Vec<Ex>>
pub fn as_finite_set(&self) -> Option<Vec<Ex>>
The elements of a finite set (numeric elements in ascending order,
possibly followed by symbolic ones). None if the set is not
known to be finite.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
let roots = (&x.powi(2) - 4).solve_le(&x).boundary().unwrap();
let elems = roots.as_finite_set().unwrap();
assert_eq!(elems.len(), 2);
assert!(ctx.reals().as_finite_set().is_none());
assert_eq!(ctx.empty_set().as_finite_set(), Some(vec![]));Sourcepub fn to_condition(&self, var: &Ex) -> Result<BoolEx, SymplexError>
pub fn to_condition(&self, var: &Ex) -> Result<BoolEx, SymplexError>
Membership of var as a boolean expression:
(a, b] ∪ {c} becomes (x > a) ∧ (b ≥ x) ∨ (x = c).
Works for symbolic sets too; ConditionSets substitute var into
their condition. Returns Err(InvalidArgument) if self contains
a node that is not a set constructor.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
let a = ctx.interval(&ctx.int(0), &ctx.int(1), IntervalKind::LeftOpen);
let cond = a.to_condition(&x).unwrap();
assert_eq!(format!("{cond}"), "x > 0 & 1 >= x");
// and back again:
assert_eq!(format!("{}", cond.solve_for(&x).unwrap()), "(0, 1]");Sourcepub fn reduce_inequalities(
conds: &[BoolEx],
var: &Ex,
) -> Result<SetEx, SymplexError>
pub fn reduce_inequalities( conds: &[BoolEx], var: &Ex, ) -> Result<SetEx, SymplexError>
Reduce a system of univariate conditions in var to its solution
set in normal form.
Each condition may combine >, >=, <, <=, =, != atoms in
var with and / or / not. Atoms are solved with the
inequality solver (sign charts) and equation solver, then combined
with set algebra. The conditions are conjoined.
§Errors
InvalidArgument—varis not a symbol,condsis empty, or a condition contains a non-relational atom / does not involvevar.ComputationFailed— an atom could not be solved.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
let c1 = x.powi(2).gt(&ctx.int(4));
let c2 = x.lt(&ctx.int(5));
let sol = SetEx::reduce_inequalities(&[c1, c2], &x).unwrap();
assert_eq!(format!("{sol}"), "(-oo, -2) ∪ (2, 5)");Source§impl Expr<Boolean>
impl Expr<Boolean>
Sourcepub fn simplify(&self) -> BoolEx
pub fn simplify(&self) -> BoolEx
Simplify a boolean expression.
The operands of every relational atom are simplified with the
numeric engine (each operand individually, memoised), then boolean
algebra is applied: flattening of nested and/or,
duplicate removal, constant folding, negation pushed to the atoms
(De Morgan, double negation, ¬(a < b) = a ≥ b), absorption
(A ∧ (A ∨ B) = A), complements (A ∧ ¬A = false), merging of
relationals on the same operands (x > 0 ∧ x ≥ 0 = x > 0,
x > 0 ∨ x = 0 = x ≥ 0), and folding of relationals whose operands
can be ordered (2 > 1, π > 3, x + 1 > x).
The result is in negation normal form with children sorted deterministically.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
let p = x.gt(&ctx.int(0));
let q = x.ge(&ctx.int(0));
assert_eq!(p.and(&q).simplify(), p);
assert_eq!(format!("{}", p.not().simplify()), "0 >= x");
assert_eq!(format!("{}", p.or(&p.not()).simplify()), "True");Sourcepub fn eval(&self) -> BoolEx
pub fn eval(&self) -> BoolEx
Evaluate a boolean expression.
Numeric sub-expressions are evaluated exactly and relationals with
numeric operands are folded (5 > 3 → True). In addition,
relationals whose truth follows from the assumption system are
folded: x > 0 is True when x was declared Positive.
Connectives are constant-folded.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let t = ctx.symbol_with("t", &[Assumption::Positive]);
assert_eq!(format!("{}", t.gt(&ctx.int(0)).eval()), "True");
assert_eq!(format!("{}", t.le(&ctx.int(0)).eval()), "False");
let x = ctx.symbol("x");
assert_eq!(format!("{}", x.gt(&ctx.int(0)).eval()), "x > 0");Sourcepub fn to_nnf(&self) -> BoolEx
pub fn to_nnf(&self) -> BoolEx
Negation normal form: negations pushed onto the atoms
(¬(A ∧ B) → ¬A ∨ ¬B, ¬(a > b) → b ≥ a), decidable
relationals folded.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
let y = ctx.symbol("y");
let e = x.gt(&ctx.int(0)).and(&y.gt(&ctx.int(0))).not();
assert_eq!(format!("{}", e.to_nnf()), "0 >= x | 0 >= y");Sourcepub fn to_cnf(&self) -> BoolEx
pub fn to_cnf(&self) -> BoolEx
Conjunctive normal form (a conjunction of disjunctions of literals), simplified. If distribution would produce more than a few thousand clauses the simplified NNF is returned instead.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let p = ctx.symbol("p").gt(&ctx.int(0));
let q = ctx.symbol("q").gt(&ctx.int(0));
let r = ctx.symbol("r").gt(&ctx.int(0));
let e = p.or(&q.and(&r));
assert_eq!(format!("{}", e.to_cnf()), "(p > 0 | q > 0) & (p > 0 | r > 0)");Sourcepub fn to_dnf(&self) -> BoolEx
pub fn to_dnf(&self) -> BoolEx
Disjunctive normal form (a disjunction of conjunctions of
literals), simplified. Same size guard as to_cnf.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let p = ctx.symbol("p").gt(&ctx.int(0));
let q = ctx.symbol("q").gt(&ctx.int(0));
let r = ctx.symbol("r").gt(&ctx.int(0));
let e = p.and(&q.or(&r));
assert_eq!(format!("{}", e.to_dnf()), "p > 0 & q > 0 | p > 0 & r > 0");Sourcepub fn is_tautology(&self) -> Option<bool>
pub fn is_tautology(&self) -> Option<bool>
Is this formula true under every assignment? Three-valued.
A propositional proof (each relational pair is a three-valued
variable, opaque atoms are two-valued; DPLL with unit propagation,
up to 24 variables) gives Some(true). A propositional
counter-example is trusted only when the atoms are independent
(each relational is linear in its own symbol). If neither applies
and every atom is a relational in one common free symbol, the
question is decided exactly through the inequality solver.
Otherwise None.
Declared assumptions are respected: relationals they decide are folded first, and symbols carrying restricting assumptions are not treated as ranging over all of ℝ.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
let p = x.gt(&ctx.int(0));
assert_eq!(p.or(&p.not()).is_tautology(), Some(true));
// x > 1 → x > 0 is not propositional, but exact on the real line:
assert_eq!(x.gt(&ctx.int(1)).implies(&p).is_tautology(), Some(true));
assert_eq!(p.is_tautology(), Some(false));
// with an assumption, t > 0 holds everywhere t is defined:
let t = ctx.symbol_with("t", &[Assumption::Positive]);
assert_eq!(t.gt(&ctx.int(0)).is_tautology(), Some(true));Sourcepub fn is_contradiction(&self) -> Option<bool>
pub fn is_contradiction(&self) -> Option<bool>
Is this formula false under every assignment? Three-valued; see
is_tautology for the decision procedure.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
let e = x.gt(&ctx.int(1)).and(&x.lt(&ctx.int(0)));
assert_eq!(e.is_contradiction(), Some(true));
assert_eq!(e.satisfiable(), Some(false));Sourcepub fn satisfiable(&self) -> Option<bool>
pub fn satisfiable(&self) -> Option<bool>
Does some assignment make this formula true? Three-valued
(None when the question cannot be decided — e.g. relationals
that share variables non-trivially and are propositionally
consistent).
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
let y = ctx.symbol("y");
assert_eq!(x.gt(&ctx.int(0)).satisfiable(), Some(true));
// independent linear atoms: exact
assert_eq!(x.gt(&ctx.int(0)).and(&y.gt(&ctx.int(0))).satisfiable(), Some(true));
// x > y and x > 0 share x: undecided rather than guessed
assert_eq!(x.gt(&y).and(&x.gt(&ctx.int(0))).satisfiable(), None);Sourcepub fn atoms(&self) -> Vec<BoolEx> ⓘ
pub fn atoms(&self) -> Vec<BoolEx> ⓘ
The distinct atomic sub-formulas (relationals and opaque propositions), in post-order. Constants are excluded.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
let p = x.gt(&ctx.int(0));
let q = x.lt(&ctx.int(5));
let e = p.and(&q).or(&p.not());
assert_eq!(e.atoms().len(), 2);Sourcepub fn truth_table(
&self,
vars: &[BoolEx],
) -> Result<Vec<(Vec<bool>, bool)>, SymplexError>
pub fn truth_table( &self, vars: &[BoolEx], ) -> Result<Vec<(Vec<bool>, bool)>, SymplexError>
Truth table over the given variables (at most 8).
Each row is (values, result) with values in the order of
vars, most significant variable first (false before true).
Relational variables and their negations are recognised as the same
variable; rows whose variable values are mutually inconsistent
(x > 0 and x < 0 both true) are omitted.
§Errors
InvalidArgument for more than 8 variables or constant variables;
ComputationFailed if the formula is not determined by vars.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let p = ctx.symbol("p").gt(&ctx.int(0));
let q = ctx.symbol("q").gt(&ctx.int(0));
let rows = p.implies(&q).truth_table(&[p.clone(), q.clone()]).unwrap();
assert_eq!(rows.len(), 4);
assert_eq!(rows[2], (vec![true, false], false)); // p ∧ ¬q falsifies p → qSourcepub fn solve_for(&self, var: &Ex) -> Result<SetEx, SymplexError>
pub fn solve_for(&self, var: &Ex) -> Result<SetEx, SymplexError>
Solve this condition for var as a set (method form of
SetEx::reduce_inequalities).
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
let cond = x.ge(&ctx.int(0)).or(&x.lt(&ctx.int(-3)));
assert_eq!(format!("{}", cond.solve_for(&x).unwrap()), "(-oo, -3) ∪ [0, oo)");
let cond = x.powi(2).le(&ctx.int(1)).not();
assert_eq!(format!("{}", cond.solve_for(&x).unwrap()), "(-oo, -1) ∪ (1, oo)");Source§impl Expr<Numeric>
impl Expr<Numeric>
Sourcepub fn is_in(&self, set: &SetEx) -> Option<bool>
pub fn is_in(&self, set: &SetEx) -> Option<bool>
Set membership self ∈ set; alias of SetEx::contains.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
let sol = (&x.powi(2) - 4).solve_gt(&x); // (−∞, −2) ∪ (2, ∞)
assert_eq!(ctx.int(3).is_in(&sol), Some(true));
assert_eq!(ctx.int(0).is_in(&sol), Some(false));
assert_eq!(x.is_in(&sol), None);Sourcepub fn piecewise_simplify(&self) -> Ex
pub fn piecewise_simplify(&self) -> Ex
Simplify every Piecewise node in this expression.
Conditions are simplified with boolean algebra; false branches
are dropped; evaluation stops at the first true branch; a branch
repeating an earlier condition is unreachable and dropped; adjacent
branches with identical values are merged; a single remaining branch
with condition true collapses to its value.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
let pos = x.gt(&ctx.int(0));
let nonpos = x.le(&ctx.int(0));
let never = ctx.int(1).gt(&ctx.int(2));
let pw = Ex::piecewise(&[(&x, &never), (&x, &pos), (&x, &nonpos)]);
assert_eq!(pw.piecewise_simplify(), x);Source§impl Expr<Numeric>
impl Expr<Numeric>
Sourcepub fn solve_general(&self, var: &Ex) -> Result<GeneralSolution, SymplexError>
pub fn solve_general(&self, var: &Ex) -> Result<GeneralSolution, SymplexError>
Solve self = 0 for var, returning general solution families.
Unlike solve, which returns only principal branches,
this expresses periodic solutions with a fresh integer parameter
(n, or n1, n2, … if n is already in use), exposed through
GeneralSolution::parameters:
sin(x) = c→asin(c) + 2πn,π − asin(c) + 2πncos(x) = c→±acos(c) + 2πntan(x) = c→atan(c) + πn
Linear arguments (sin(a·x + b) = c) and change-of-variable forms
(sin²x − sin x = 0) are supported. Non-periodic equations return
the same solutions as solve with an empty parameter list.
§Errors
Same as solve: InfiniteSolutions for identities,
NoSolution for contradictions, ComputationFailed when nothing
applies.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
let eq = &x.sin() - &ctx.rational(1, 2);
let fam = eq.solve_general(&x).unwrap();
assert_eq!(fam.solutions.len(), 2);
assert_eq!(fam.parameters.len(), 1);
// Every member of every family satisfies the equation.
for k in -2..=2 {
for s in fam.instance(k) {
let residual = eq.subs(&x, &s).eval_f64().unwrap();
assert!(residual.abs() < 1e-12);
}
}Source§impl Expr<Numeric>
impl Expr<Numeric>
Sourcepub fn solve_ode_ivp(
&self,
func: &Ex,
var: &Ex,
ics: &[(usize, Ex, Ex)],
) -> Result<Ex, SymplexError>
pub fn solve_ode_ivp( &self, func: &Ex, var: &Ex, ics: &[(usize, Ex, Ex)], ) -> Result<Ex, SymplexError>
Solve the ODE self = 0 for func(var) subject to initial
conditions.
Each initial condition is (k, x0, value) meaning
d^k func / d var^k (x0) = value (k = 0 is func(x0) = value).
The general solution is found with solve_ode,
then the integration constants C1, C2, … are determined by
substituting the conditions and solving the resulting (usually
linear) system with linsolve; nonlinear constant equations are
handled one at a time with solve. Constants not
pinned down by the conditions remain in the result.
§Errors
SymplexError::ComputationFailedif the ODE cannot be solved or the constants cannot be determined.SymplexError::NoSolutionif the initial conditions are contradictory.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let (x, y) = (ctx.symbol("x"), ctx.symbol("y"));
// y'' + y = 0, y(0) = 0, y'(0) = 1 → y = sin(x)
let ode = &y.formal_diff(&x).formal_diff(&x) + &y;
let sol = ode
.solve_ode_ivp(&y, &x, &[(0, ctx.int(0), ctx.int(0)), (1, ctx.int(0), ctx.int(1))])
.unwrap();
assert_eq!(format!("{}", sol.simplify()), "sin(x)");Source§impl Expr<Numeric>
impl Expr<Numeric>
Sourcepub fn solve_riccati(
&self,
func: &Ex,
var: &Ex,
particular: &Ex,
) -> Result<Ex, SymplexError>
pub fn solve_riccati( &self, func: &Ex, var: &Ex, particular: &Ex, ) -> Result<Ex, SymplexError>
Solve the Riccati equation self = 0, i.e.
y' = q₀(x) + q₁(x)·y + q₂(x)·y², given a known particular
solution particular.
The substitution y = y_p + 1/v reduces the equation to the linear
ODE v' + (q₁ + 2·q₂·y_p)·v = −q₂; the result is y_p + 1/v with the
integration constant C1.
§Errors
SymplexError::InvalidArgumentifselfis not a Riccati equation infuncorparticulardoes not satisfy it.SymplexError::ComputationFailedif the linear equation forvcannot be solved in closed form.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let (x, y) = (ctx.symbol("x"), ctx.symbol("y"));
// y' = y² - 2/x² has the particular solution y = 1/x
let ode = &y.formal_diff(&x) - &y.powi(2) + &(&ctx.int(2) / &x.powi(2));
let sol = ode.solve_riccati(&y, &x, &(&ctx.int(1) / &x)).unwrap();
assert!(sol.contains(&ctx.symbol("C1")));
assert!(ode.check_ode_solution(&sol, &y, &x));Source§impl Expr<Numeric>
impl Expr<Numeric>
Sourcepub fn limit_dir(&self, var: &Ex, point: &Ex, dir: Direction) -> Ex
pub fn limit_dir(&self, var: &Ex, point: &Ex, dir: Direction) -> Ex
Compute the limit of this expression as var approaches point
from the given Direction.
Direction::Right:x → a⁺(values slightly larger thana)Direction::Left:x → a⁻(values slightly smaller thana)Direction::Both(the default): two-sided; both one-sided limits must exist and agree.
For point = ±∞ the direction is irrelevant.
If the limit does not exist or cannot be determined, the formal
two-sided Limit(expr, var, point) node is returned (check with
has_unevaluated). The node has no
direction slot, so an uncomputable one-sided limit is represented by
the same node as the two-sided one.
Pathological inputs are cut off by an internal work budget and likewise come back as the unevaluated node.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
let zero = ctx.int(0);
// e^{1/x} → ∞ from the right, → 0 from the left
let f = (1 / &x).exp();
assert_eq!(format!("{}", f.limit_right(&x, &zero)), "oo");
assert_eq!(format!("{}", f.limit_left(&x, &zero)), "0");
// ⌊x⌋ at an integer
assert_eq!(format!("{}", x.floor().limit_left(&x, &ctx.int(2))), "1");
assert_eq!(format!("{}", x.floor().limit_right(&x, &ctx.int(2))), "2");Sourcepub fn try_limit_dir(
&self,
var: &Ex,
point: &Ex,
dir: Direction,
) -> Result<Ex, SymplexError>
pub fn try_limit_dir( &self, var: &Ex, point: &Ex, dir: Direction, ) -> Result<Ex, SymplexError>
Like limit_dir, but returns Err if the limit
does not exist or cannot be computed.
±∞ are legitimate limit values and are returned as Ok. When the
two one-sided limits of a Direction::Both request differ, the
error is ComputationFailed with a reason of the form
"left and right limits differ: left = …, right = …".
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
let f = x.sign();
// sign(x) has different one-sided limits at 0
let err = f.try_limit(&x, &ctx.int(0)).unwrap_err();
assert!(err.to_string().contains("differ"));Sourcepub fn limit_left(&self, var: &Ex, point: &Ex) -> Ex
pub fn limit_left(&self, var: &Ex, point: &Ex) -> Ex
Left-hand limit lim_{var → point⁻}. Shorthand for
limit_dir with Direction::Left.
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
let f = &x.abs() / &x;
assert_eq!(format!("{}", f.limit_left(&x, &ctx.int(0))), "-1");Sourcepub fn limit_right(&self, var: &Ex, point: &Ex) -> Ex
pub fn limit_right(&self, var: &Ex, point: &Ex) -> Ex
Right-hand limit lim_{var → point⁺}. Shorthand for
limit_dir with Direction::Right.
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
let f = &x * x.ln();
assert_eq!(format!("{}", f.limit_right(&x, &ctx.int(0))), "0");
assert_eq!(format!("{}", x.ln().limit_right(&x, &ctx.int(0))), "-oo");Sourcepub fn try_limit_left(&self, var: &Ex, point: &Ex) -> Result<Ex, SymplexError>
pub fn try_limit_left(&self, var: &Ex, point: &Ex) -> Result<Ex, SymplexError>
Fallible left-hand limit. See try_limit_dir.
Sourcepub fn try_limit_right(&self, var: &Ex, point: &Ex) -> Result<Ex, SymplexError>
pub fn try_limit_right(&self, var: &Ex, point: &Ex) -> Result<Ex, SymplexError>
Fallible right-hand limit. See try_limit_dir.
Source§impl Expr<Numeric>
impl Expr<Numeric>
Sourcepub fn fourier_transform(&self, t: &Ex, omega: &Ex) -> Result<Ex, SymplexError>
pub fn fourier_transform(&self, t: &Ex, omega: &Ex) -> Result<Ex, SymplexError>
Fourier transform F(ω) = ∫_{−∞}^{∞} f(t) e^{−iωt} dt of this
expression (a function of t) as a function of omega.
This is the non-unitary angular-frequency convention; see
fourier_transform_with for the
others. The transform is computed from a table (δ, constants,
H(t), sign(t), 1/t, |t|, rectangular windows, e^{−a|t|},
Gaussians, tⁿ e^{−at} H(t), cos/sin, sinc) together with
linearity, time shift, modulation, scaling, the derivative rule and
t·f(t) → i F′(ω).
Symbols other than t and omega are treated as real
parameters. Conditions such as a > 0 in e^{−a|t|} are checked
through the assumption system (declare a with
Assumption::Positive); an unprovable condition is an error.
§Errors
InvalidArgumentift/omegaare not distinct symbols.ComputationFailedif no rule applies, a required sign assumption is missing, or the result would need a distribution that cannot be represented (e.g.δ′). There is no unevaluated node for Fourier transforms, so this API isResult-only.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let t = ctx.symbol("t");
let w = ctx.symbol("w");
let a = ctx.symbol_with("a", &[Assumption::Positive]);
// e^{-a|t|} → 2a/(a² + ω²)
let f = (-&a * t.abs()).exp().fourier_transform(&t, &w).unwrap();
assert_eq!(format!("{f}"), "2*a/(a^2 + w^2)");
// Rectangular window H(t + 1) − H(t − 1) → 2 sin(ω)/ω
let rect = (&t + 1).heaviside() - (&t - 1).heaviside();
let r = rect.fourier_transform(&t, &w).unwrap();
assert_eq!(format!("{r}"), "2*sin(w)/w");
// e^{-2t} H(t) → 1/(iω + 2)
let g = ((&t * -2).exp() * t.heaviside()).fourier_transform(&t, &w).unwrap();
assert_eq!(g, 1 / (ctx.i_unit() * &w + 2));
// Unknown sign → Err rather than a guess.
let b = ctx.symbol("b");
assert!((-&b * t.abs()).exp().fourier_transform(&t, &w).is_err());Sourcepub fn fourier_transform_with(
&self,
t: &Ex,
omega: &Ex,
convention: FourierConvention,
) -> Result<Ex, SymplexError>
pub fn fourier_transform_with( &self, t: &Ex, omega: &Ex, convention: FourierConvention, ) -> Result<Ex, SymplexError>
Fourier transform in the given FourierConvention.
| convention | F = |
|---|---|
NonUnitaryAngular | ∫ f(t) e^{−iωt} dt |
UnitaryAngular | (1/√(2π)) ∫ f(t) e^{−iωt} dt |
Ordinary | ∫ f(t) e^{−2πiνt} dt (omega is ν) |
use symplex::prelude::*;
use symplex::fourier_transform::FourierConvention;
let ctx = Context::new();
let t = ctx.symbol("t");
let nu = ctx.symbol("nu");
// Gaussian e^{-πt²} is its own transform in the ordinary convention.
let g = (-(ctx.pi() * t.powi(2))).exp();
let f = g
.fourier_transform_with(&t, &nu, FourierConvention::Ordinary)
.unwrap();
assert_eq!(f, (-(ctx.pi() * nu.powi(2))).exp());Sourcepub fn inverse_fourier_transform(
&self,
omega: &Ex,
t: &Ex,
) -> Result<Ex, SymplexError>
pub fn inverse_fourier_transform( &self, omega: &Ex, t: &Ex, ) -> Result<Ex, SymplexError>
Inverse Fourier transform f(t) = (1/2π) ∫ F(ω) e^{iωt} dω of this
expression (a function of omega) as a function of t, in the
non-unitary angular convention.
Handles δ(ω − ω₀), constants, H(ω), sign(ω), 1/ω,
1/(iω − a)ⁿ, 1/(ω² + a²), ω/(ω² + a²), Gaussians, sin(aω)/ω,
cos(aω)/sin(aω), rectangular windows in ω, plus linearity,
shift, modulation, scaling and ωⁿ G(ω) → (−i)ⁿ g⁽ⁿ⁾(t).
§Errors
As for fourier_transform.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let t = ctx.symbol("t");
let w = ctx.symbol("w");
// 1/(iω + 3) → e^{-3t} H(t)
let big_f = 1 / (ctx.i_unit() * &w + 3);
let f = big_f.inverse_fourier_transform(&w, &t).unwrap();
assert_eq!(format!("{f}"), "exp(-3*t)*H(t)");
// 2/(ω² + 1) → e^{-|t|}
let g = (2 / (w.powi(2) + 1)).inverse_fourier_transform(&w, &t).unwrap();
assert_eq!(format!("{g}"), "exp(-abs(t))");Sourcepub fn inverse_fourier_transform_with(
&self,
omega: &Ex,
t: &Ex,
convention: FourierConvention,
) -> Result<Ex, SymplexError>
pub fn inverse_fourier_transform_with( &self, omega: &Ex, t: &Ex, convention: FourierConvention, ) -> Result<Ex, SymplexError>
Inverse Fourier transform in the given FourierConvention
(see fourier_transform_with).
use symplex::prelude::*;
use symplex::fourier_transform::FourierConvention;
let ctx = Context::new();
let t = ctx.symbol("t");
let w = ctx.symbol("w");
let f = (-t.abs()).exp();
let big_f = f
.fourier_transform_with(&t, &w, FourierConvention::UnitaryAngular)
.unwrap();
let back = big_f
.inverse_fourier_transform_with(&w, &t, FourierConvention::UnitaryAngular)
.unwrap();
assert_eq!(format!("{back}"), "exp(-abs(t))");Source§impl Expr<Numeric>
impl Expr<Numeric>
Sourcepub fn laplace_initial_value(&self, s: &Ex) -> Result<Ex, SymplexError>
pub fn laplace_initial_value(&self, s: &Ex) -> Result<Ex, SymplexError>
Initial value theorem: f(0⁺) = lim_{s→∞} s·F(s) for this Laplace
transform F(s).
An infinite initial value (e.g. f = 1/√t, F = √(π/s)) is returned
as the extended-real value oo.
§Errors
ComputationFailed if the limit does not exist or cannot be computed.
use symplex::prelude::*;
let ctx = Context::new();
let s = ctx.symbol("s");
// F(s) = (s + 2)/(s² + 3s + 5) → f(0⁺) = 1
let f = (&s + 2) / (s.powi(2) + 3 * &s + 5);
assert_eq!(format!("{}", f.laplace_initial_value(&s).unwrap()), "1");
// F(s) = 1/(s² + 1) (f = sin t) → f(0⁺) = 0
assert_eq!(format!("{}", (1 / (s.powi(2) + 1)).laplace_initial_value(&s).unwrap()), "0");Sourcepub fn laplace_final_value(&self, s: &Ex) -> Result<Ex, SymplexError>
pub fn laplace_final_value(&self, s: &Ex) -> Result<Ex, SymplexError>
Final value theorem: lim_{t→∞} f(t) = lim_{s→0⁺} s·F(s) for this
Laplace transform F(s).
The theorem only holds when every pole of s·F(s) lies in the open
left half-plane. When s·F(s) is a rational function whose poles can
be located, this is verified and a violation is reported as
Divergent instead of returning a meaningless number; for
non-rational transforms (e.g. delays e^{−as}F(s)) the caller is
responsible for the precondition.
§Errors
Divergentifs·F(s)has a pole with non-negative real part.ComputationFailedif the poles cannot be located or the limit cannot be computed.
use symplex::prelude::*;
let ctx = Context::new();
let s = ctx.symbol("s");
// Step response of a stable first-order system: F(s) = 3/(s(s + 2)) → 3/2
let f = 3 / (&s * (&s + 2));
assert_eq!(format!("{}", f.laplace_final_value(&s).unwrap()), "3/2");
// e^{t} has no final value: the pole at s = 1 is detected.
assert!(matches!(
(1 / (&s - 1)).laplace_final_value(&s),
Err(SymplexError::Divergent { .. })
));Source§impl Expr<Numeric>
impl Expr<Numeric>
Sourcepub fn fourier_series_on(
&self,
var: &Ex,
lower: &Ex,
upper: &Ex,
n_terms: u32,
) -> Result<FourierSeries, SymplexError>
pub fn fourier_series_on( &self, var: &Ex, lower: &Ex, upper: &Ex, n_terms: u32, ) -> Result<FourierSeries, SymplexError>
Fourier series of this expression in var on the interval
[lower, upper], with the first n_terms harmonics computed.
Coefficients are exact definite integrals
(integrate_definite), so piecewise,
|x|, sign and Heaviside inputs (square, sawtooth and triangle
waves) work. Returns the FourierSeries with a0, an, bn,
period and the truncate /
coefficient_c helpers.
§Errors
InvalidArgumentifvaris not a symbol or the interval is degenerate.ComputationFailedif a coefficient integral has no closed form.
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
// Square wave sign(x) on [-π, π]: b_k = 4/(kπ) for odd k
let sq = x.sign().fourier_series_on(&x, &(-ctx.pi()), &ctx.pi(), 3).unwrap();
assert_eq!(sq.coefficient_b(1), 4 / ctx.pi());
assert_eq!(sq.coefficient_b(3), 4 / (3 * ctx.pi()));
assert!(sq.coefficient_b(2).is_zero_structural());
// Triangle wave |x| on [-π, π]: a₀ = π, a_k = -4/(k²π) for odd k
let tri = x.abs().fourier_series_on(&x, &(-ctx.pi()), &ctx.pi(), 2).unwrap();
assert_eq!(format!("{}", tri.a0), "pi");
assert_eq!(format!("{}", tri.coefficient_a(1)), "-4/pi");
assert_eq!(format!("{}", tri.coefficient_a(2)), "0");Source§impl Expr<Numeric>
impl Expr<Numeric>
Sourcepub fn mellin_transform(
&self,
x: &Ex,
s: &Ex,
) -> Result<(Ex, BoolEx), SymplexError>
pub fn mellin_transform( &self, x: &Ex, s: &Ex, ) -> Result<(Ex, BoolEx), SymplexError>
Mellin transform M{f}(s) = ∫₀^∞ x^{s−1} f(x) dx of this expression
(a function of x) as a function of s, together with the
fundamental strip a < Re(s) < b on which the integral converges,
returned as a boolean condition on Re(s).
Table: e^{−x} → Γ(s), e^{−x²} → Γ(s/2)/2, 1/(1+x) → π/sin(πs),
1/(1+x)^ν → B(s, ν−s), H(1−x)·x^a → 1/(s+a), H(x−1)·x^a → −1/(s+a), H(1−x)(1−x)^b → B(s, b+1), sin x → Γ(s) sin(πs/2),
cos x → Γ(s) cos(πs/2), ln(1+x) → π/(s sin πs); rules: linearity,
x^a f → F(s+a), f(ax) → a^{−s}F(s), f(x^b) → F(s/b)/|b|,
f′ → −(s−1)F(s−1), ln(x) f → F′(s).
Parameters are treated as real; sign conditions (a > 0 in
e^{−ax}) are checked through the assumption system and an
unprovable condition is an error.
§Errors
InvalidArgumentifx/sare not distinct symbols.ComputationFailedif no rule applies, a sign assumption is missing, or the strips of two summands do not overlap.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
let s = ctx.symbol("s");
let (f, strip) = (-&x).exp().mellin_transform(&x, &s).unwrap();
assert_eq!(f, s.gamma());
assert_eq!(format!("{strip}"), "re(s) > 0");
// x² e^{-3x}: power rule and scaling rule
let (g, strip) = (x.powi(2) * (&x * -3).exp()).mellin_transform(&x, &s).unwrap();
assert_eq!(g, ctx.int(3).pow(&(-&s - 2)) * (&s + 2).gamma());
assert_eq!(format!("{strip}"), "re(s) > -2");
// 1/(1+x)³ → B(s, 3 − s) on 0 < Re s < 3
let (h, strip) = (1 / (1 + &x).powi(3)).mellin_transform(&x, &s).unwrap();
assert_eq!(h, s.beta(&(3 - &s)));
assert_eq!(format!("{strip}"), "re(s) > 0 & 3 > re(s)");Sourcepub fn inverse_mellin_transform(
&self,
s: &Ex,
x: &Ex,
) -> Result<Ex, SymplexError>
pub fn inverse_mellin_transform( &self, s: &Ex, x: &Ex, ) -> Result<Ex, SymplexError>
Inverse Mellin transform of this expression (a function of s) as a
function of x, by table lookup (Γ(s/b) → b e^{−x^b},
π/sin(πs) → 1/(1+x), B(s, ν−s) → (1+x)^{−ν}, B(s, b+1) → H(1−x)(1−x)^b, 1/(s+a) → x^a H(1−x), Γ(s) sin(πs/2) → sin x, …)
with the shift (G(s+a) → x^a g) and scaling (a^{−s}G → g(ax))
rules applied in reverse.
A Mellin transform determines its function only together with a
strip. Where the table entry is ambiguous (1/(s + a) is the
transform of x^a H(1−x) on Re s > −a and of −x^a H(x−1) on
Re s < −a) the strip to the right of the pole is chosen.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
let s = ctx.symbol("s");
assert_eq!(s.gamma().inverse_mellin_transform(&s, &x).unwrap(), (-&x).exp());
let f = (ctx.pi() / (ctx.pi() * &s).sin()).inverse_mellin_transform(&s, &x).unwrap();
assert_eq!(f, 1 / (&x + 1));
// round trip through the shift and scaling rules
let g = x.powi(2) * (&x * -3).exp();
let (big_g, _) = g.mellin_transform(&x, &s).unwrap();
assert_eq!(big_g.inverse_mellin_transform(&s, &x).unwrap(), g);Source§impl Expr<Numeric>
impl Expr<Numeric>
Sourcepub fn as_poly(&self, gens: &[&Ex]) -> Option<Poly>
pub fn as_poly(&self, gens: &[&Ex]) -> Option<Poly>
View this expression as a Poly in gens; see Poly::new.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let (x, a) = (ctx.symbol("x"), ctx.symbol("a"));
let p = (&a * &x.powi(2) + 1).as_poly(&[&x]).unwrap();
assert_eq!(p.degree_in(&x), Some(2));
assert_eq!(p.leading_coeff(), a);
assert!(x.sin().as_poly(&[&x]).is_none());Source§impl Expr<Numeric>
impl Expr<Numeric>
Sourcepub fn z_transform(&self, n: &Ex, z: &Ex) -> Result<Ex, SymplexError>
pub fn z_transform(&self, n: &Ex, z: &Ex) -> Result<Ex, SymplexError>
Unilateral Z-transform X(z) = Σ_{n≥0} x[n] z^{−n} of this sequence
(a function of the integer index n).
Table: constants, aⁿ, nᵏ aⁿ (via Z{n x[n]} = −z X′(z)),
sin(ωn), cos(ωn), aⁿ sin(ωn), aⁿ cos(ωn), H(n − k),
δ[n − k], C(n, k), 1/n!; rules: linearity, scaling
aⁿ x[n] → X(z/a), delay x[n − k] H(n − k) → z^{−k} X(z).
§Errors
ComputationFailed if n/z are not symbols or no rule applies.
There is no unevaluated Z-transform node, so this API is
Result-only.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let n = ctx.symbol("n");
let z = ctx.symbol("z");
let half = ctx.rational(1, 2);
// Z{(1/2)^n} = z/(z − 1/2)
let result = half.pow(&n).z_transform(&n, &z).unwrap();
assert_eq!(result, &z / (&z - half));
// n² → z(z + 1)/(z − 1)³
let x = n.powi(2).z_transform(&n, &z).unwrap();
let expected = &z * (&z + 1) / (&z - 1).powi(3);
assert!((&x - &expected).simplify().is_zero_structural(), "{x}");
// δ[n − 3] → z⁻³
assert_eq!((&n - 3).dirac_delta().z_transform(&n, &z).unwrap(), z.powi(-3));Sourcepub fn inverse_z_transform(&self, z: &Ex, n: &Ex) -> Result<Ex, SymplexError>
pub fn inverse_z_transform(&self, z: &Ex, n: &Ex) -> Result<Ex, SymplexError>
Inverse (unilateral) Z-transform of this expression (a function of
z) as a sequence in n.
Rational X(z) is handled through partial fractions in z
(z/(z − a)ᵐ → C(n, m−1) a^{n−m+1}, 1/(z − a)ᵐ through the delay
rule), together with constants (δ[n]), z^{−k} (δ[n − k]),
z^{−k} X(z) (x[n−k] H(n−k)), e^{1/z} (1/n!) and the
trigonometric forms.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let n = ctx.symbol("n");
let z = ctx.symbol("z");
// Z⁻¹{z/(z−2)} = 2ⁿ
let xz = &z / &(&z - 2);
assert_eq!(format!("{}", xz.inverse_z_transform(&z, &n).unwrap()), "2^n");
// Z⁻¹{z⁻²} = δ[n − 2]
let d = (1 / z.powi(2)).inverse_z_transform(&z, &n).unwrap();
assert_eq!(format!("{d}"), "KroneckerDelta(2, n)");Source§impl Expr<Numeric>
impl Expr<Numeric>
Sourcepub fn prove_nonnegative_on_box(
&self,
bounds: &[BoxBound],
degree: u32,
) -> Result<BoxOutcome, SymplexError>
pub fn prove_nonnegative_on_box( &self, bounds: &[BoxBound], degree: u32, ) -> Result<BoxOutcome, SymplexError>
prove_nonnegative_on_box as a method.
Source§impl Expr<Numeric>
impl Expr<Numeric>
Sourcepub fn find_root_bracket(
&self,
var: &Ex,
a: f64,
b: f64,
) -> Result<f64, SymplexError>
pub fn find_root_bracket( &self, var: &Ex, a: f64, b: f64, ) -> Result<f64, SymplexError>
Numerically find a root of this expression in var inside the
bracket [a, b] by brent_root with default RootOpts.
The expression is compiled with compile first, so
evaluation is fast and the usual compile-time checks apply.
§Errors
SymplexError::InvalidArgumentifvaris not a symbol, or the bracket is invalid (non-finite, or no sign change).SymplexError::FreeSymbolif the expression contains a symbol other thanvar.SymplexError::NotImplementedif the expression cannot be compiled tof64arithmetic.SymplexError::ComputationFailedif the iteration does not converge or meets a non-finite value.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
let r = (&x.powi(2) - 2).find_root_bracket(&x, 0.0, 2.0).unwrap();
assert!((r - 2f64.sqrt()).abs() < 1e-12);
// A transcendental equation: cos x = x.
let r = (x.cos() - &x).find_root_bracket(&x, 0.0, 1.0).unwrap();
assert!((r - 0.739_085_133_215_160_6).abs() < 1e-12);
// Another free symbol → FreeSymbol, not a silent NaN.
let a = ctx.symbol("a");
assert!(matches!(
(&x.powi(2) - &a).find_root_bracket(&x, 0.0, 2.0),
Err(SymplexError::FreeSymbol { .. })
));Sourcepub fn find_root_bracket_with(
&self,
var: &Ex,
a: f64,
b: f64,
opts: &RootOpts,
) -> Result<f64, SymplexError>
pub fn find_root_bracket_with( &self, var: &Ex, a: f64, b: f64, opts: &RootOpts, ) -> Result<f64, SymplexError>
find_root_bracket with explicit
RootOpts.
§Examples
use symplex::optimize::RootOpts;
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
let opts = RootOpts { xtol: 1e-6, ..RootOpts::default() };
let r = (x.exp() - 3).find_root_bracket_with(&x, 0.0, 2.0, &opts).unwrap();
assert!((r - 3f64.ln()).abs() < 1e-6);Sourcepub fn minimize_numeric(
&self,
vars: &[&Ex],
x0: &[f64],
) -> Result<MinimizeResult, SymplexError>
pub fn minimize_numeric( &self, vars: &[&Ex], x0: &[f64], ) -> Result<MinimizeResult, SymplexError>
Minimise this expression numerically over vars from the starting
point x0 by nelder_mead with default MinimizeOpts.
x0[i] is the initial value of vars[i].
§Errors
SymplexError::InvalidArgumentifvarsis empty, a variable is not a symbol, orx0.len() != vars.len().SymplexError::FreeSymbolif the expression contains a symbol not listed invars.SymplexError::NotImplementedif the expression cannot be compiled tof64arithmetic.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let (x, y) = (ctx.symbol("x"), ctx.symbol("y"));
let bowl = (&x - 1).powi(2) + (&y + 2).powi(2);
let r = bowl.minimize_numeric(&[&x, &y], &[0.0, 0.0]).unwrap();
assert!(r.converged);
assert!((r.x[0] - 1.0).abs() < 1e-6 && (r.x[1] + 2.0).abs() < 1e-6);
assert!(r.fun < 1e-12);Sourcepub fn minimize_numeric_with(
&self,
vars: &[&Ex],
x0: &[f64],
opts: &MinimizeOpts,
) -> Result<MinimizeResult, SymplexError>
pub fn minimize_numeric_with( &self, vars: &[&Ex], x0: &[f64], opts: &MinimizeOpts, ) -> Result<MinimizeResult, SymplexError>
minimize_numeric with explicit
MinimizeOpts.
§Examples
use symplex::optimize::MinimizeOpts;
use symplex::prelude::*;
let ctx = Context::new();
let (x, y) = (ctx.symbol("x"), ctx.symbol("y"));
let rosen = (1 - &x).powi(2) + 100 * (&y - &x.powi(2)).powi(2);
let opts = MinimizeOpts { max_iter: 2000, ..MinimizeOpts::default() };
let r = rosen.minimize_numeric_with(&[&x, &y], &[-1.2, 1.0], &opts).unwrap();
assert!((r.x[0] - 1.0).abs() < 1e-4 && (r.x[1] - 1.0).abs() < 1e-4);Sourcepub fn minimize_scalar_numeric(
&self,
var: &Ex,
a: f64,
b: f64,
) -> Result<ScalarMinimum, SymplexError>
pub fn minimize_scalar_numeric( &self, var: &Ex, a: f64, b: f64, ) -> Result<ScalarMinimum, SymplexError>
Minimise this expression in the single variable var over [a, b]
by Brent’s method (minimize_scalar) with default
MinimizeOpts. Returns the minimiser and the value there as a
ScalarMinimum.
§Errors
As for find_root_bracket plus the
interval rules of minimize_scalar.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
// x·ln x has its minimum −1/e at x = 1/e.
let m = (&x * x.ln()).minimize_scalar_numeric(&x, 0.1, 2.0).unwrap();
assert!((m.x - (-1.0f64).exp()).abs() < 1e-6);
assert!((m.value + (-1.0f64).exp()).abs() < 1e-12);Sourcepub fn minimize_global_numeric(
&self,
vars: &[&Ex],
bounds: &[Interval<f64>],
opts: &DeOpts,
) -> Result<MinimizeResult, SymplexError>
pub fn minimize_global_numeric( &self, vars: &[&Ex], bounds: &[Interval<f64>], opts: &DeOpts, ) -> Result<MinimizeResult, SymplexError>
Globally minimise this expression over the box bounds (one closed
Interval per entry of vars) by differential_evolution.
§Errors
As for minimize_numeric, with
bounds.len() playing the role of x0.len(), plus the option and
bound rules of differential_evolution.
§Examples
use symplex::optimize::DeOpts;
use symplex::prelude::*;
let ctx = Context::new();
let (x, y) = (ctx.symbol("x"), ctx.symbol("y"));
// Himmelblau's function has four global minima with f = 0.
let h = (&x.powi(2) + &y - 11).powi(2) + (&x + &y.powi(2) - 7).powi(2);
let box_ = [Interval::closed(-5.0, 5.0), Interval::closed(-5.0, 5.0)];
let r = h.minimize_global_numeric(&[&x, &y], &box_, &DeOpts::default()).unwrap();
assert!(r.fun < 1e-8, "f = {}", r.fun);Sourcepub fn poly_fit_points(
ctx: &Context,
points: &[(Ex, Ex)],
var: &Ex,
degree: usize,
) -> Result<Ex, SymplexError>
pub fn poly_fit_points( ctx: &Context, points: &[(Ex, Ex)], var: &Ex, degree: usize, ) -> Result<Ex, SymplexError>
Exact least-squares polynomial of degree degree in var through
the rational points (x, y).
Each coordinate is constant-folded with eval and
must then be a rational literal (ctx.int, ctx.rational,
sqrt(4), …). The fit is computed by poly_fit_exact, so the
result is the exact least-squares polynomial — the interpolating
polynomial when degree + 1 == points.len() or the data are
consistent.
§Errors
SymplexError::InvalidArgumentif a coordinate is not a rational literal after evaluation, ordegree >= points.len().SymplexError::ComputationFailedif the normal equations are singular (fewer thandegree + 1distinct abscissae).
§Panics
Panics if var or a point belongs to a different context than
ctx (the standard cross-context guard).
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
// Five samples of x²/3 − x/2 + 1/7.
let pts = [
(ctx.int(0), ctx.rational(1, 7)),
(ctx.int(1), ctx.rational(-1, 42)),
(ctx.int(2), ctx.rational(10, 21)),
(ctx.int(3), ctx.rational(23, 14)),
(ctx.int(4), ctx.rational(73, 21)),
];
let p = Ex::poly_fit_points(&ctx, &pts, &x, 2).unwrap();
let expected = &x.powi(2) * ctx.rational(1, 3) - &x * ctx.rational(1, 2) + ctx.rational(1, 7);
assert!((&p - &expected).expand().is_zero_structural(), "{p}");
// A symbolic coordinate is rejected.
let a = ctx.symbol("a");
assert!(Ex::poly_fit_points(&ctx, &[(ctx.int(0), a), (ctx.int(1), ctx.int(1))], &x, 1).is_err());Source§impl<S: Sort> Expr<S>
impl<S: Sort> Expr<S>
Sourcepub fn to_python(&self) -> Result<String, SymplexError>
pub fn to_python(&self) -> Result<String, SymplexError>
A Python 3 expression using the math module (SymPy: pycode).
Integer powers print as x**2, rationals as (1/2), x^(1/2) as
math.sqrt(x), constants as math.pi/math.e; relations and
connectives (BoolEx) as x > 0,
and, or, not; piecewise as (v if c else …). Symbols print by
name; the caller needs import math. See
codegen for the function table.
§Errors
SymplexError::NotImplemented for a node Python’s math module
cannot express (Bessel functions, digamma, LambertW, zeta,
unevaluated integrals, sets, I) — never a silently wrong formula.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let (x, y) = (ctx.symbol("x"), ctx.symbol("y"));
assert_eq!((&x.sin().powi(2) + &x.exp()).to_python().unwrap(), "math.sin(x)**2 + math.exp(x)");
assert_eq!((&x.powi(2) + 1).to_python().unwrap(), "x**2 + 1");
assert_eq!((&x / 2).to_python().unwrap(), "x/2");
assert_eq!((&x * 2 / 3).to_python().unwrap(), "2*x/3");
assert_eq!((&x + 1).sqrt().to_python().unwrap(), "math.sqrt(x + 1)");
assert_eq!((-&x * &y).to_python().unwrap(), "-x*y");
assert_eq!((&x - &y).powi(3).to_python().unwrap(), "(x - y)**3");
assert_eq!(x.gt(&ctx.int(0)).and(&x.lt(&ctx.int(1))).to_python().unwrap(), "x > 0 and 1 > x");
assert!(x.bessel_j(&ctx.int(0)).to_python().is_err());Sourcepub fn to_numpy(&self) -> Result<String, SymplexError>
pub fn to_numpy(&self) -> Result<String, SymplexError>
A vectorised Python expression using numpy. (SymPy:
NumPyPrinter().doprint).
Same layout as to_python with numpy.sin,
numpy.sqrt, numpy.pi, numpy.greater, numpy.logical_and,
numpy.select for piecewise, numpy.minimum/numpy.maximum. The
caller needs import numpy.
§Errors
SymplexError::NotImplemented for nodes NumPy itself lacks
(gamma, erf, factorial need SciPy) or that have no numerical
meaning.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
assert_eq!(x.sin().to_numpy().unwrap(), "numpy.sin(x)");
assert_eq!((&x.sin().powi(2) + &x.exp()).to_numpy().unwrap(), "numpy.sin(x)**2 + numpy.exp(x)");
assert_eq!((&x * ctx.pi()).sqrt().to_numpy().unwrap(), "numpy.sqrt(x*numpy.pi)");
assert_eq!(x.gt(&ctx.int(0)).to_numpy().unwrap(), "numpy.greater(x, 0)");
assert!(x.gamma().to_numpy().is_err());Sourcepub fn to_julia(&self) -> Result<String, SymplexError>
pub fn to_julia(&self) -> Result<String, SymplexError>
A Julia expression (SymPy: julia_code).
sin, exp, sqrt, cbrt, abs, ^ for powers, pi, ℯ,
Inf, NaN, &&/||/!, (c ? v : …) for piecewise. Base
Julia only: gamma/erf (SpecialFunctions.jl) are refused.
§Errors
SymplexError::NotImplemented for nodes base Julia cannot express.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
assert_eq!((&x.sin().powi(2) + &x.exp()).to_julia().unwrap(), "sin(x)^2 + exp(x)");
assert_eq!((&x.powi(2) + ctx.pi()).to_julia().unwrap(), "x^2 + pi");
assert_eq!((ctx.pi() * &x / 2).to_julia().unwrap(), "x*pi/2");
assert_eq!((ctx.e() * &x).to_julia().unwrap(), "x*ℯ");Sourcepub fn to_python_fn(
&self,
name: &str,
args: &[&str],
) -> Result<String, SymplexError>
pub fn to_python_fn( &self, name: &str, args: &[&str], ) -> Result<String, SymplexError>
A Python function def name(args): … with common subexpressions
hoisted into t0, t1, … (SymPy: pycode + cse).
§Errors
SymplexError::FreeSymbol for a symbol not in args;
SymplexError::NotImplemented as for to_python.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let (x, y) = (ctx.symbol("x"), ctx.symbol("y"));
let f = &x.sin().powi(2) + &x.sin() * &y;
assert_eq!(
f.to_python_fn("f", &["x", "y"]).unwrap(),
"def f(x, y):\n t0 = math.sin(x)\n return t0**2 + t0*y\n"
);
assert!(matches!(f.to_python_fn("f", &["x"]), Err(SymplexError::FreeSymbol { .. })));Sourcepub fn to_numpy_fn(
&self,
name: &str,
args: &[&str],
) -> Result<String, SymplexError>
pub fn to_numpy_fn( &self, name: &str, args: &[&str], ) -> Result<String, SymplexError>
A NumPy function def name(args): … with CSE temporaries; see
to_numpy.
§Errors
SymplexError::FreeSymbol for a symbol not in args;
SymplexError::NotImplemented as for to_numpy.
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
assert_eq!(
x.exp().to_numpy_fn("g", &["x"]).unwrap(),
"def g(x):\n return numpy.exp(x)\n"
);Sourcepub fn to_julia_fn(
&self,
name: &str,
args: &[&str],
) -> Result<String, SymplexError>
pub fn to_julia_fn( &self, name: &str, args: &[&str], ) -> Result<String, SymplexError>
A Julia function function name(args) … end with CSE temporaries;
see to_julia.
§Errors
SymplexError::FreeSymbol for a symbol not in args;
SymplexError::NotImplemented as for to_julia.
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
assert_eq!(
(&x.powi(2) + 1).to_julia_fn("h", &["x"]).unwrap(),
"function h(x)\n return x^2 + 1\nend\n"
);Source§impl<S: Sort> Expr<S>
impl<S: Sort> Expr<S>
Sourcepub fn to_latex(&self) -> String
pub fn to_latex(&self) -> String
Render this expression as a LaTeX math string (no delimiters).
Uses direct ExprNode matching for robustness.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
assert_eq!(x.powi(2).to_latex(), r"x^{2}");
assert_eq!(x.sin().to_latex(), r"\sin\left(x\right)");Sourcepub fn to_latex_inline(&self) -> String
pub fn to_latex_inline(&self) -> String
Render as an inline LaTeX expression with $ delimiters.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
assert_eq!(x.to_latex_inline(), "$x$");Sourcepub fn to_latex_display(&self) -> String
pub fn to_latex_display(&self) -> String
Render as a display LaTeX expression with $$ delimiters.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
assert_eq!(x.to_latex_display(), "$$x$$");Source§impl<S: Sort> Expr<S>
impl<S: Sort> Expr<S>
Sourcepub fn to_lean(&self) -> Result<String, SymplexError>
pub fn to_lean(&self) -> Result<String, SymplexError>
Render this expression as a Lean 4 / Mathlib term (type ℝ, or
Prop for a BoolEx) with Mathlib’s
spacing conventions.
See the lean module docs for the exact conventions.
§Errors
SymplexError::NotImplemented for nodes without a standard Mathlib
spelling (special functions beyond the elementary ones, unevaluated
integrals/limits, sets, …).
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let (j, x) = (ctx.symbol("j"), ctx.symbol("x"));
assert_eq!((2 * &j + 1).to_lean().unwrap(), "2 * j + 1");
assert_eq!((&j.powi(2) - &j / 2).to_lean().unwrap(), "j ^ 2 - j / 2");
assert_eq!(((&j - 1) / (2 * &j)).to_lean().unwrap(), "(j - 1) / (2 * j)");
assert_eq!(ctx.rational(3, 31).to_lean().unwrap(), "(3 / 31 : ℝ)");
assert_eq!((&x.sin().powi(2) + &x.exp()).to_lean().unwrap(), "Real.sin x ^ 2 + Real.exp x");
assert_eq!((&x * 2).sqrt().to_lean().unwrap(), "Real.sqrt (2 * x)");
assert_eq!(x.gt(&ctx.int(0)).to_lean().unwrap(), "0 < x");
assert!(x.bessel_j(&ctx.int(0)).to_lean().is_err());Sourcepub fn to_lean_with(&self, opts: &LeanOpts) -> Result<String, SymplexError>
pub fn to_lean_with(&self, opts: &LeanOpts) -> Result<String, SymplexError>
Source§impl<S: Sort> Expr<S>
impl<S: Sort> Expr<S>
Sourcepub fn to_mathml(&self) -> Result<String, SymplexError>
pub fn to_mathml(&self) -> Result<String, SymplexError>
Render this expression as Presentation MathML, wrapped in a
<math xmlns="http://www.w3.org/1998/Math/MathML"> element
(SymPy: mathml(expr, printer='presentation')).
Layout follows to_latex: <mfrac> for
quotients, <msqrt>/<mroot> for roots, <msup> for powers,
<mi>sin</mi><mo>⁡</mo> for function application, Greek
symbol names as character references, explicit <mo>(</mo> for
parentheses. Relations and connectives
(BoolEx) render with >, ≥,
∧, ∨, ¬. See the mathml
module docs.
§Errors
SymplexError::NotImplemented for nodes without a standard
presentation (Series, DSolve, RootOf, RootSum).
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
assert_eq!(
(&x.powi(2) + 1).to_mathml().unwrap(),
"<math xmlns=\"http://www.w3.org/1998/Math/MathML\">\
<mrow><msup><mi>x</mi><mn>2</mn></msup><mo>+</mo><mn>1</mn></mrow></math>"
);
assert_eq!(
(&x / 2).to_mathml().unwrap(),
"<math xmlns=\"http://www.w3.org/1998/Math/MathML\">\
<mfrac><mi>x</mi><mn>2</mn></mfrac></math>"
);
assert_eq!(
x.sin().to_mathml().unwrap(),
"<math xmlns=\"http://www.w3.org/1998/Math/MathML\">\
<mrow><mi>sin</mi><mo>⁡</mo><mrow><mo>(</mo><mi>x</mi><mo>)</mo></mrow></mrow></math>"
);
assert_eq!(
x.gt(&ctx.int(0)).to_mathml().unwrap(),
"<math xmlns=\"http://www.w3.org/1998/Math/MathML\">\
<mrow><mi>x</mi><mo>></mo><mn>0</mn></mrow></math>"
);Source§impl<S: Sort> Expr<S>
impl<S: Sort> Expr<S>
Sourcepub fn to_srepr(&self) -> String
pub fn to_srepr(&self) -> String
SymPy-srepr-style constructor form: an unambiguous, parseable-by-eye
rendering of the exact tree, derived from to_tree
so it is total (SymPy: srepr(expr)).
Atoms print as Integer(2), Rational(1, 2), Symbol('x'), pi,
E, I, oo; compound nodes as Head(child, …) with SymPy’s
heads where they exist (Add, Mul, Pow, sin, log, Abs,
StrictGreaterThan, Interval(a, b, false, true), …) and symplex’s
otherwise (Neg, DefiniteIntegral(f, x, a, b)). Library and
user functions print as name(args). Children appear in the
arena’s canonical order (numbers first in a sum), not display order:
this is the exact tree, as to_tree/to_json see it.
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
assert_eq!((2 * &x + 1).to_srepr(), "Add(Integer(1), Mul(Integer(2), Symbol('x')))");
assert_eq!((&x / 2).to_srepr(), "Mul(Rational(1, 2), Symbol('x'))");
assert_eq!(x.sin().powi(2).to_srepr(), "Pow(sin(Symbol('x')), Integer(2))");
assert_eq!(x.gt(&ctx.int(0)).to_srepr(), "StrictGreaterThan(Symbol('x'), Integer(0))");Sourcepub fn to_dot(&self) -> String
pub fn to_dot(&self) -> String
Graphviz DOT source for the expression tree (SymPy: dotprint).
One node per tree position (labelled with the node kind and, for
atoms, the value), one edge per child, ids n0, n1, … assigned
in pre-order so the output is deterministic. Render with
dot -Tsvg.
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
assert_eq!(
(2 * &x + 1).to_dot(),
"digraph {\n\
\x20 ordering=out;\n\
\x20 rankdir=TD;\n\
\x20 n0 [label=\"Add\"];\n\
\x20 n1 [label=\"Integer(1)\"];\n\
\x20 n2 [label=\"Mul\"];\n\
\x20 n3 [label=\"Integer(2)\"];\n\
\x20 n4 [label=\"Symbol('x')\"];\n\
\x20 n0 -> n1;\n\
\x20 n0 -> n2;\n\
\x20 n2 -> n3;\n\
\x20 n2 -> n4;\n\
}\n"
);Trait Implementations§
Source§impl AddAssign<&Expr<Numeric>> for Ex
impl AddAssign<&Expr<Numeric>> for Ex
Source§fn add_assign(&mut self, rhs: &Ex)
fn add_assign(&mut self, rhs: &Ex)
+= operation. Read moreSource§impl<S: Sort> Debug for Expr<S>
Debug prints the handle type and the pretty form: Ex(x^2 + 1).
impl<S: Sort> Debug for Expr<S>
Debug prints the handle type and the pretty form: Ex(x^2 + 1).
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
assert_eq!(format!("{:?}", &x + 1), "Ex(x + 1)");Source§impl Div<&Expr<Numeric>> for &Quaternion
impl Div<&Expr<Numeric>> for &Quaternion
Source§type Output = Quaternion
type Output = Quaternion
/ operator.Source§impl Div<&Expr<Numeric>> for Quaternion
impl Div<&Expr<Numeric>> for Quaternion
Source§type Output = Quaternion
type Output = Quaternion
/ operator.Source§impl Div<Expr<Numeric>> for &Quaternion
impl Div<Expr<Numeric>> for &Quaternion
Source§type Output = Quaternion
type Output = Quaternion
/ operator.Source§impl Div<Expr<Numeric>> for Quaternion
impl Div<Expr<Numeric>> for Quaternion
Source§type Output = Quaternion
type Output = Quaternion
/ operator.Source§impl DivAssign<&Expr<Numeric>> for Ex
impl DivAssign<&Expr<Numeric>> for Ex
Source§fn div_assign(&mut self, rhs: &Ex)
fn div_assign(&mut self, rhs: &Ex)
/= operation. Read moreimpl<S: Sort> Eq for Expr<S>
Source§impl Mul<&Expr<Numeric>> for &Quaternion
impl Mul<&Expr<Numeric>> for &Quaternion
Source§type Output = Quaternion
type Output = Quaternion
* operator.Source§impl Mul<&Expr<Numeric>> for Quaternion
impl Mul<&Expr<Numeric>> for Quaternion
Source§type Output = Quaternion
type Output = Quaternion
* operator.Source§impl Mul<&Expr<Numeric>> for Dimensionless
impl Mul<&Expr<Numeric>> for Dimensionless
Source§type Output = Dimensionless
type Output = Dimensionless
* operator.Source§impl Mul<&Expr<Numeric>> for &Dimensionless
impl Mul<&Expr<Numeric>> for &Dimensionless
Source§type Output = Dimensionless
type Output = Dimensionless
* operator.Source§impl Mul<&Expr<Numeric>> for Temperature
impl Mul<&Expr<Numeric>> for Temperature
Source§type Output = Temperature
type Output = Temperature
* operator.Source§impl Mul<&Expr<Numeric>> for &Temperature
impl Mul<&Expr<Numeric>> for &Temperature
Source§type Output = Temperature
type Output = Temperature
* operator.Source§impl Mul<&Expr<Numeric>> for Acceleration
impl Mul<&Expr<Numeric>> for Acceleration
Source§type Output = Acceleration
type Output = Acceleration
* operator.Source§impl Mul<&Expr<Numeric>> for &Acceleration
impl Mul<&Expr<Numeric>> for &Acceleration
Source§type Output = Acceleration
type Output = Acceleration
* operator.Source§impl Mul<&Expr<Numeric>> for AngularVelocity
impl Mul<&Expr<Numeric>> for AngularVelocity
Source§type Output = AngularVelocity
type Output = AngularVelocity
* operator.Source§impl Mul<&Expr<Numeric>> for &AngularVelocity
impl Mul<&Expr<Numeric>> for &AngularVelocity
Source§type Output = AngularVelocity
type Output = AngularVelocity
* operator.Source§impl Mul<&Expr<Numeric>> for AngularAcceleration
impl Mul<&Expr<Numeric>> for AngularAcceleration
Source§type Output = AngularAcceleration
type Output = AngularAcceleration
* operator.Source§impl Mul<&Expr<Numeric>> for &AngularAcceleration
impl Mul<&Expr<Numeric>> for &AngularAcceleration
Source§type Output = AngularAcceleration
type Output = AngularAcceleration
* operator.Source§impl Mul<&Expr<Numeric>> for AngularMomentum
impl Mul<&Expr<Numeric>> for AngularMomentum
Source§type Output = AngularMomentum
type Output = AngularMomentum
* operator.Source§impl Mul<&Expr<Numeric>> for &AngularMomentum
impl Mul<&Expr<Numeric>> for &AngularMomentum
Source§type Output = AngularMomentum
type Output = AngularMomentum
* operator.Source§impl Mul<&Expr<Numeric>> for MomentOfInertia
impl Mul<&Expr<Numeric>> for MomentOfInertia
Source§type Output = MomentOfInertia
type Output = MomentOfInertia
* operator.Source§impl Mul<&Expr<Numeric>> for &MomentOfInertia
impl Mul<&Expr<Numeric>> for &MomentOfInertia
Source§type Output = MomentOfInertia
type Output = MomentOfInertia
* operator.Source§impl Mul<&Expr<Numeric>> for Resistance
impl Mul<&Expr<Numeric>> for Resistance
Source§type Output = Resistance
type Output = Resistance
* operator.Source§impl Mul<&Expr<Numeric>> for &Resistance
impl Mul<&Expr<Numeric>> for &Resistance
Source§type Output = Resistance
type Output = Resistance
* operator.Source§impl Mul<&Expr<Numeric>> for Inductance
impl Mul<&Expr<Numeric>> for Inductance
Source§type Output = Inductance
type Output = Inductance
* operator.Source§impl Mul<&Expr<Numeric>> for &Inductance
impl Mul<&Expr<Numeric>> for &Inductance
Source§type Output = Inductance
type Output = Inductance
* operator.Source§impl Mul<&Expr<Numeric>> for Capacitance
impl Mul<&Expr<Numeric>> for Capacitance
Source§type Output = Capacitance
type Output = Capacitance
* operator.Source§impl Mul<&Expr<Numeric>> for &Capacitance
impl Mul<&Expr<Numeric>> for &Capacitance
Source§type Output = Capacitance
type Output = Capacitance
* operator.Source§impl Mul<&Expr<Numeric>> for MagneticFlux
impl Mul<&Expr<Numeric>> for MagneticFlux
Source§type Output = MagneticFlux
type Output = MagneticFlux
* operator.Source§impl Mul<&Expr<Numeric>> for &MagneticFlux
impl Mul<&Expr<Numeric>> for &MagneticFlux
Source§type Output = MagneticFlux
type Output = MagneticFlux
* operator.Source§impl Mul<Expr<Numeric>> for &Quaternion
impl Mul<Expr<Numeric>> for &Quaternion
Source§type Output = Quaternion
type Output = Quaternion
* operator.Source§impl Mul<Expr<Numeric>> for Quaternion
impl Mul<Expr<Numeric>> for Quaternion
Source§type Output = Quaternion
type Output = Quaternion
* operator.Source§impl MulAssign<&Expr<Numeric>> for Ex
impl MulAssign<&Expr<Numeric>> for Ex
Source§fn mul_assign(&mut self, rhs: &Ex)
fn mul_assign(&mut self, rhs: &Ex)
*= operation. Read moreSource§impl<S: Sort> PartialEq for Expr<S>
Structural identity comparison.
impl<S: Sort> PartialEq for Expr<S>
Structural identity comparison.
Two expressions are == if and only if they share the same arena node
(same ExprId in the same Context). Because the arena uses hash-consing,
canonically equivalent expressions (e.g., x + 1 and 1 + x) do share
the same node and will compare equal.
Important: This is not mathematical equality. Expressions that are
mathematically equal but structurally different (e.g., (x+1)^2 and
x^2 + 2*x + 1) will compare as not equal because they have different
canonical forms. Use Expr::equals() for mathematical equality testing,
or Expr::expand() to normalize before comparing.
Source§impl Product<Expr<Numeric>> for Option<Ex>
None on an empty iterator, otherwise Some(product).
impl Product<Expr<Numeric>> for Option<Ex>
None on an empty iterator, otherwise Some(product).
Source§impl SubAssign<&Expr<Numeric>> for Ex
impl SubAssign<&Expr<Numeric>> for Ex
Source§fn sub_assign(&mut self, rhs: &Ex)
fn sub_assign(&mut self, rhs: &Ex)
-= operation. Read moreSource§impl Sum<Expr<Numeric>> for Option<Ex>
None on an empty iterator, otherwise Some(sum).
impl Sum<Expr<Numeric>> for Option<Ex>
None on an empty iterator, otherwise Some(sum).
use symplex::prelude::*;
let ctx = Context::new();
let xs = ctx.symbols_indexed("x", 3);
let s: Option<Ex> = xs.iter().sum();
assert_eq!(format!("{}", s.unwrap()), "x0 + x1 + x2");
let e: Option<Ex> = Vec::<Ex>::new().into_iter().sum();
assert!(e.is_none());Auto Trait Implementations§
impl<S> !RefUnwindSafe for Expr<S>
impl<S> !UnwindSafe for Expr<S>
impl<S> Freeze for Expr<S>where
PhantomData<S>: Freeze,
impl<S> Send for Expr<S>where
PhantomData<S>: Send,
impl<S> Sync for Expr<S>where
PhantomData<S>: Sync,
impl<S> Unpin for Expr<S>where
PhantomData<S>: Unpin,
impl<S> UnsafeUnpin for Expr<S>where
PhantomData<S>: UnsafeUnpin,
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
Source§fn in_current_span(self) -> Instrumented<Self> ⓘ
fn in_current_span(self) -> Instrumented<Self> ⓘ
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more