Skip to main content

Expr

Struct Expr 

Source
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 on Expr<Numeric>)
  • bool_expr + 1 won’t compile (Add is only on Expr<Numeric>)
  • numeric.and(other) won’t compile (and is only on Expr<Boolean>)

Implementations§

Source§

impl<S: Sort> Expr<S>

Source

pub fn context(&self) -> Context

Returns a Context handle that shares this expression’s arena and assumption cache.

Useful when you need to create new expressions (constants, rationals) guaranteed to live in the same context as an existing expression.

Source§

impl<S: Sort> Expr<S>

Source

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.

Source

pub fn ctx_id(&self) -> CtxId

Returns the CtxId that this expression belongs to.

Source

pub fn is_zero_structural(&self) -> bool

Returns true if this expression is structurally zero (O(1)).

Source

pub fn is_one_structural(&self) -> bool

Returns true if this expression is structurally one (O(1)).

Source

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.

Source

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.

Source

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 + Pow
Source

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);
Source

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);
Source

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);
Source

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 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.

Source

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");
Source

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.

Source

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");
Source

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");
Source

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"));
Source

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\""));
Source

pub fn to_json_pretty(&self) -> Result<String, SymplexError>

Serialize this expression to a pretty-printed JSON string.

Source

pub fn apply_until_stable<F>( &self, max_iterations: usize, f: F, ) -> (Expr<S>, usize)
where F: Fn(&Expr<S>) -> Expr<S>,

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 iteration
Source§

impl Expr<Numeric>

Source

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.

Source

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)1
  • cos(0)1, cos(π)-1
  • exp(0)1, ln(1)0
  • sqrt(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");
Source

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>

Source

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.

Source

pub fn and(&self, other: &BoolEx) -> BoolEx

Logical conjunction: self & other.

Source

pub fn or(&self, other: &BoolEx) -> BoolEx

Logical disjunction: self | other.

Source

pub fn not(&self) -> BoolEx

Logical negation: !self.

Source

pub fn xor(&self, other: &BoolEx) -> BoolEx

Exclusive or: self ⊕ other = (self ∧ ¬other) ∨ (¬self ∧ other).

Source

pub fn implies(&self, other: &BoolEx) -> BoolEx

Logical implication: self → other = ¬self ∨ other.

Source

pub fn equivalent(&self, other: &BoolEx) -> BoolEx

Logical biconditional: self ↔ other = (self → other) ∧ (other → self).

Source

pub fn nand(&self, other: &BoolEx) -> BoolEx

NAND gate: ¬(self ∧ other).

Source

pub fn nor(&self, other: &BoolEx) -> BoolEx

NOR gate: ¬(self ∨ other).

Source

pub fn ite(&self, then_: &BoolEx, else_: &BoolEx) -> BoolEx

If-then-else: if self then a else b = (self ∧ a) ∨ (¬self ∧ b).

Source

pub fn into_ex(self) -> Ex

Convert to untyped numeric expression (escape hatch).

Source

pub fn as_ex(&self) -> Ex

Borrow as untyped numeric expression.

Source§

impl Expr<SetValued>

Source

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}");
Source

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");
Source

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

pub fn into_ex(self) -> Ex

Convert to untyped numeric expression (escape hatch).

This allows set-valued expressions to be embedded in contexts that expect Ex. The underlying arena node is unchanged.

Source

pub fn as_ex(&self) -> Ex

Borrow as untyped numeric expression.

Source§

impl Expr<Numeric>

Source

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());
Source

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());
Source

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));
Source

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)]);
Source

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());
Source

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());
Source

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());
Source

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());
Source

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));
Source

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>

Source

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
  • InvalidArgumentvar is not a symbol.
  • ComputationFailed — the zeros of some source cannot be found exactly (the solver fails on g = 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 in domain cannot be decided. Plain (non-periodic) zeros whose membership in domain is undecided do not error: the result is then Ok with the intersection {p, …} ∩ domain left 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}");
Source

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
  • InvalidArgumentvar is not a symbol.
  • ComputationFailed — the derivative is a formal Derivative, the zeros of the derivative cannot be found exactly, or more than four sign factors 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}");
Source

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
  • InvalidArgumentvar is not a symbol, or domain is empty or not a union of intervals.
  • ComputationFailedself has 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");
Source

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");
Source

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));
Source

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));
Source

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 ( is strictly increasing on ℝ although f'(0) = 0, and so is 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 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));
Source

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));
Source

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 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));
Source

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));
Source

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) when self does not depend on var.
  • sin(a·x + b), cos(a·x + b)2π/|a|; tan(a·x + b)π/|a|; sec, csc, cot (which are built from sin/cos) follow, with products sin(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 their var-dependent parts; the lcm needs pairwise rational ratios.
  • None when a var-dependent part is not recognised as periodic (, sin(x²), sin(x) + x, sin(√2·x) + sin(x)).

The expression is simplified first (sin²x + cos²x1Some(0)); the original form is tried if the simplified one is not recognised. Note that SymPy reports 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);
Source

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
  • InvalidArgumentvar is not a symbol, or domain is not a union of intervals (the empty domain gives the empty set).
  • ComputationFailed — as for maximum.
§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>

Source

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);
Source

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)");
Source

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);
Source

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 unknown
Source

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)");
Source

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)");
Source

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);
Source

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)");
Source

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>

Source

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);
Source

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);
Source

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");
Source

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)");
Source

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);
Source

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);
Source

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>

Source

pub fn pow(&self, exp: &Ex) -> Ex

Raise to a symbolic power: self ^ exp.

Source

pub fn powi(&self, n: i64) -> Ex

Raise to an integer power: self ^ n.

Source

pub fn sin(&self) -> Ex

Sine: sin(self).

Source

pub fn cos(&self) -> Ex

Cosine: cos(self).

Source

pub fn tan(&self) -> Ex

Tangent: tan(self).

Source

pub fn exp(&self) -> Ex

Natural exponential: e^self.

Source

pub fn ln(&self) -> Ex

Natural logarithm: ln(self).

Source

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}");
Source

pub fn sqrt(&self) -> Ex

Principal square root: √self.

Source

pub fn cbrt(&self) -> Ex

Cube root: ∛self.

Source

pub fn nthroot(&self, n: i64) -> Ex

Nth root: self^(1/n).

Source

pub fn abs(&self) -> Ex

Absolute value (or complex modulus): |self|.

Source

pub fn asin(&self) -> Ex

Inverse sine (arcsin): asin(self).

Source

pub fn acos(&self) -> Ex

Inverse cosine (arccos): acos(self).

Source

pub fn atan(&self) -> Ex

Inverse tangent (arctan): atan(self).

Source

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.

Source

pub fn sinh(&self) -> Ex

Hyperbolic sine: sinh(self).

Source

pub fn cosh(&self) -> Ex

Hyperbolic cosine: cosh(self).

Source

pub fn tanh(&self) -> Ex

Hyperbolic tangent: tanh(self).

Source

pub fn asinh(&self) -> Ex

Inverse hyperbolic sine: asinh(self).

Source

pub fn acosh(&self) -> Ex

Inverse hyperbolic cosine: acosh(self).

Source

pub fn atanh(&self) -> Ex

Inverse hyperbolic tangent: atanh(self).

Source

pub fn sec(&self) -> Ex

Secant: sec(x) = 1/cos(x).

Source

pub fn csc(&self) -> Ex

Cosecant: csc(x) = 1/sin(x).

Source

pub fn cot(&self) -> Ex

Cotangent: cot(x) = cos(x)/sin(x).

Source

pub fn acot(&self) -> Ex

Inverse cotangent: acot(x) = atan(1/x).

Source

pub fn asec(&self) -> Ex

Inverse secant: asec(x) = acos(1/x).

Source

pub fn acsc(&self) -> Ex

Inverse cosecant: acsc(x) = asin(1/x).

Source

pub fn coth(&self) -> Ex

Hyperbolic cotangent: coth(x) = cosh(x)/sinh(x).

Source

pub fn sech(&self) -> Ex

Hyperbolic secant: sech(x) = 1/cosh(x).

Source

pub fn csch(&self) -> Ex

Hyperbolic cosecant: csch(x) = 1/sinh(x).

Source

pub fn acoth(&self) -> Ex

Inverse hyperbolic cotangent: acoth(x) = atanh(1/x).

Source

pub fn asech(&self) -> Ex

Inverse hyperbolic secant: asech(x) = acosh(1/x).

Source

pub fn acsch(&self) -> Ex

Inverse hyperbolic cosecant: acsch(x) = asinh(1/x).

Source

pub fn sinc(&self) -> Ex

Cardinal sine: sinc(x) = sin(x)/x, with sinc(0) = 1 (requires limit).

Source

pub fn sign(&self) -> Ex

Sign function: 1 if positive, -1 if negative, 0 if zero.

Source

pub fn floor(&self) -> Ex

Floor function: ⌊self⌋ (greatest integer ≤ self).

Source

pub fn ceiling(&self) -> Ex

Ceiling function: ⌈self⌉ (least integer ≥ self).

Source

pub fn frac(&self) -> Ex

Fractional part: self - floor(self).

Source

pub fn rem(&self, other: &Ex) -> Ex

Remainder: self - other * floor(self / other).

Source

pub fn min_with(&self, other: &Ex) -> Ex

Binary minimum: min(self, other).

Source

pub fn max_with(&self, other: &Ex) -> Ex

Binary maximum: max(self, other).

Source

pub fn min_of(ctx: &Context, exprs: impl IntoIterator<Item = Ex>) -> Ex

N-ary minimum of a collection of expressions.

Source

pub fn max_of(ctx: &Context, exprs: impl IntoIterator<Item = Ex>) -> Ex

N-ary maximum of a collection of expressions.

Source

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.

Source

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.

Source

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));
Source

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)/2
Source

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");
Source

pub fn log_gamma(&self) -> Ex

Log-gamma function: ln(Γ(self)).

For positive integer arguments, .eval() computes ln((n-1)!).

Source

pub fn digamma(&self) -> Ex

Digamma function: ψ(self) = Γ’(self)/Γ(self).

Source

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");
Source

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");
Source

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");
Source

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");
Source

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");
Source

pub fn factorial2(&self) -> Ex

Double factorial: self!!.

For non-negative integer arguments, .eval() computes the exact value. 0!! = 1, 1!! = 1, (-1)!! = 1.

Source

pub fn subfactorial(&self) -> Ex

Subfactorial (derangement count): !self.

For non-negative integer arguments, .eval() computes the exact value.

Source

pub fn rising_factorial(&self, n: &Ex) -> Ex

Rising factorial (Pochhammer symbol): (self)_n.

rising_factorial(x, n) = x * (x+1) * ... * (x+n-1).

Source

pub fn falling_factorial(&self, n: &Ex) -> Ex

Falling factorial: self^(n) = self * (self-1) * ... * (self-n+1).

Source

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).

Source

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).

Source

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.

Source

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.

Source

pub fn catalan_number(&self) -> Ex

Catalan number: C(self) = (2n)! / ((n+1)! * n!).

For non-negative integer arguments, .eval() computes the exact value.

Source

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.

Source

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.

Source

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");
Source

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");
Source

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");
Source

pub fn heaviside(&self) -> Ex

Heaviside step function: 0 for x<0, 1/2 for x=0, 1 for x>0.

Source

pub fn dirac_delta(&self) -> Ex

Dirac delta distribution: 0 for x≠0, symbolic at x=0.

Source

pub fn lambertw(&self) -> Ex

Lambert W function (principal branch): W(x)·exp(W(x)) = x.

Source

pub fn gt(&self, other: &Ex) -> BoolEx

Greater than: self > other.

Source

pub fn ge(&self, other: &Ex) -> BoolEx

Greater than or equal: self >= other.

Source

pub fn lt(&self, other: &Ex) -> BoolEx

Less than: self < other (implemented as other > self).

Source

pub fn le(&self, other: &Ex) -> BoolEx

Less than or equal: self <= other (implemented as other >= self).

Source

pub fn eq_expr(&self, other: &Ex) -> BoolEx

Mathematical equality test (boolean-valued): self == other.

Source

pub fn ne_expr(&self, other: &Ex) -> BoolEx

Not-equal test (boolean-valued): self != other.

Source

pub fn piecewise(pairs: &[(&Ex, &BoolEx)]) -> Ex

Piecewise function from (value, condition) pairs.

Returns the value of the first pair whose condition is true.

Source

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.

Source

pub fn is_zero(&self) -> Option<bool>

Query whether this expression is zero.

Uses layered detection:

  1. Structural identity with 0 (O(1))
  2. Assumption system query

Returns Some(true) if provably zero, Some(false) if provably nonzero, or None if unknown.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

pub fn is_nonnegative(&self) -> Option<bool>

Returns Some(true) if this expression is known to be ≥ 0.

Source

pub fn is_nonpositive(&self) -> Option<bool>

Returns Some(true) if this expression is known to be ≤ 0.

Source

pub fn is_imaginary(&self) -> Option<bool>

Returns Some(true) if this expression is known to be imaginary.

Source

pub fn is_complex(&self) -> Option<bool>

Returns Some(true) if this expression is known to be complex.

Source

pub fn is_rational(&self) -> Option<bool>

Returns Some(true) if this expression is known to be rational.

Source

pub fn is_even(&self) -> Option<bool>

Returns whether this expression is known to be even.

Source

pub fn is_odd(&self) -> Option<bool>

Returns whether this expression is known to be odd.

Source

pub fn is_prime(&self) -> Option<bool>

Returns whether this expression is known to be prime.

Source

pub fn is_composite(&self) -> Option<bool>

Returns whether this expression is known to be composite.

Source

pub fn is_algebraic(&self) -> Option<bool>

Returns whether this expression is known to be algebraic.

Source

pub fn is_transcendental(&self) -> Option<bool>

Returns whether this expression is known to be transcendental.

Source

pub fn is_irrational(&self) -> Option<bool>

Returns whether this expression is known to be irrational.

Source

pub fn is_hermitian(&self) -> Option<bool>

Returns whether this expression is known to be hermitian.

Source

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));
Source

pub fn equals(&self, other: &Ex) -> Option<bool>

Mathematical equality: attempts to determine whether self == other as mathematical objects.

Three-valued:

  • Some(true)self − other is structurally zero, or becomes zero after expand or simplify, 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² + 1 for real x), or both sides are constants (no free symbols) whose 16-digit numeric values differ by more than 1e-9 relative.
  • None — undetermined. In particular x.equals(&y) for distinct free symbols is None, not Some(false); use probably_equal for 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);
Source

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");
Source

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).

Source

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}");
Source

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/dx
Source

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.

Source

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");
Source

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");
Source

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).

Source

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");
Source

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).

Source

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());
Source

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).

Source

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");
Source

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).

Source

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 ≈ 1
Source

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);
Source

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).

Source

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);
Source

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).

Source

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");
Source

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());
Source

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}");
Source

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}");
Source

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}");
Source

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}");
Source

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
ExpressionConditionResult
abs(x)x ≥ 0x
abs(x)x < 0−x
sign(x)x > 01
sign(x)x < 0−1
sign(x)x = 00
floor(x)x ∈ ℤx
ceiling(x)x ∈ ℤx
sqrt(x²)x > 0x
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");
Source

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");
Source

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.

Source

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());
Source

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");
Source

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");
Source

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.

Source

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.

Source

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);
Source

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).

Source

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");
Source

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");
Source

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}");
Source

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}");
Source

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}");
Source

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)");
Source

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()));
Source

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");
Source

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);
Source

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());
Source

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}");
Source

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}");
Source

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 + 3y
Source

pub fn rationalize_denom(&self) -> Ex

Rationalize the denominator of a fraction containing square roots.

  • 1/√2 → √2/2
  • 1/(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}");
Source

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");
Source

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);
Source

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"]);
Source

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");
Source

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()));
Source

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());
Source

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));
Source

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.

Source

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.

Source

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::InfiniteSolutions when the equation reduces to the identity 0 = 0 (every value of var is a solution).
  • SymplexError::NoSolution when the equation is provably unsatisfiable: it reduces to a nonzero constant (1 = 0), does not depend on var at all, or violates a range restriction such as exp(x) = 0 or sin(x) = 2 (no real solution).
  • SymplexError::ComputationFailed when the expression is not polynomial in var and 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 { .. })
));
Source

pub fn solve_or_empty(&self, var: &Ex) -> Vec<Ex>

Solve self = 0 for var, returning an empty vector on failure.

This is a convenience wrapper around solve that returns vec![] whenever solve returns an error — including the identity (0 = 0) and contradiction (1 = 0) cases, which have no finite list of roots. Use solve for diagnostic information.

Source

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}");
Source

pub fn try_solve_gt(&self, var: &Ex) -> Result<SetEx, SymplexError>

Like solve_gt, but returns Err if the result contains unevaluated forms.

Source

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).

Source

pub fn try_solve_ge(&self, var: &Ex) -> Result<SetEx, SymplexError>

Like solve_ge, but returns Err if the result contains unevaluated forms.

Source

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.

Source

pub fn try_solve_lt(&self, var: &Ex) -> Result<SetEx, SymplexError>

Like solve_lt, but returns Err if the result contains unevaluated forms.

Source

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.

Source

pub fn try_solve_le(&self, var: &Ex) -> Result<SetEx, SymplexError>

Like solve_le, but returns Err if the result contains unevaluated forms.

Source

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 FiniteSet of the roots when they can be found,
  • UniversalSet when the equation is the identity 0 = 0,
  • EmptySet when 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");
Source

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 f have 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 for
  • initial_guess — starting point for iteration
  • max_iterations — maximum number of Newton steps
  • tolerance — 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);
Source

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}");
Source

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).

Source

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.

Source

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);
Source

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.

Source

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
§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 { .. })));
Source

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^2
Source

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}");
Source

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)");
Source

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"));
Source

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"));
Source

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)"));
Source

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)"));
Source

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");
Source

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");
Source

pub fn replace<F>(&self, f: F) -> Ex
where F: Fn(ExprView<'_>) -> Option<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");
Source

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));
Source

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);
Source

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));
Source

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}");
Source

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}");
Source

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 exactly0.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 unbound
Source

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);
Source

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");
Source

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");
Source

pub fn bessel_j(&self, order: &Ex) -> Ex

Bessel function of the first kind: J_order(self).

Source

pub fn bessel_y(&self, order: &Ex) -> Ex

Bessel function of the second kind: Y_order(self).

Source

pub fn bessel_i(&self, order: &Ex) -> Ex

Modified Bessel function of the first kind: I_order(self).

Source

pub fn bessel_k(&self, order: &Ex) -> Ex

Modified Bessel function of the second kind: K_order(self).

Source

pub fn legendre(&self, n: &Ex) -> Ex

Legendre polynomial P_n(self).

Source

pub fn chebyshev_t(&self, n: &Ex) -> Ex

Chebyshev polynomial of the first kind T_n(self).

Source

pub fn chebyshev_u(&self, n: &Ex) -> Ex

Chebyshev polynomial of the second kind U_n(self).

Source

pub fn hermite(&self, n: &Ex) -> Ex

Hermite polynomial H_n(self) (physicist’s convention).

Source

pub fn laguerre(&self, n: &Ex) -> Ex

Laguerre polynomial L_n(self).

Source

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);
Source

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)²}.

Source

pub fn erfcinv(&self) -> Ex

Inverse complementary error function erfcinv(self) = erfinv(1 − self).

Exact: erfcinv(1) = 0, erfcinv(0) = ∞, erfcinv(2) = −∞.

Source

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");
Source

pub fn e1(&self) -> Ex

Exponential integral E₁(self) = expint(1, self) = ∫_self^∞ e^{−t}/t dt.

For x > 0, E₁(x) = −Ei(−x).

Source

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.

Source

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.

Source

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).

Source

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).

Source

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)");
Source

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}.

Source

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));
Source

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).

Source

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)");
Source

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(∞) = ∞.

Source

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).

Source

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).

Source

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);
Source

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).

Source

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).

Source

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, Π.

Source

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");
Source

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)}.

Source

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");
Source

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");
Source

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");
Source

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");
Source

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");
Source

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");
Source

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"));
Source

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)]
);
Source

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);
Source

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
§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());
Source

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>"));
Source

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}"));
Source

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
§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());
Source

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
§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>

Source

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);
Source

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.

§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");
Source

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");
Source

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());
Source

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());
Source

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
§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.

Source

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);
Source

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>

Source

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());
Source

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());
Source

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());
Source

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);
Source

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);
Source

pub fn compare_numeric(&self, other: &Ex) -> Option<Ordering>

Three-valued numeric comparison of self and other.

Decision procedure, in order:

  1. Both are numeric literals → exact rational comparison.
  2. d = (self − other).eval() is a literal → exact sign of d; if d is oo / -ooGreater / Less.
  3. The assumption system knows the sign of d (e.g. a − b with a positive and b negative; x² + 1 for real x).
  4. Both are constants (no free symbols) → 16-digit numeric evaluation; decided only if the values differ by more than 1e-9 relative and both are real.
  5. 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));
Source

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));
Source

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);
Source

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 than 1e-9 relative.
  • Some(true)equals proved 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));
Source

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>

Source

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}");
Source

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));
Source

pub fn factor_list_all(&self) -> (Ex, Vec<(Ex, u32)>)

Like factor_list with the variables inferred from the free symbols (see factor_all).

Source

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");
Source

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");
Source

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]);
Source

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");
Source

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.

Source

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);
Source

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");
Source

pub fn poly_quo(&self, other: &Ex, var: &Ex) -> Option<Ex>

Polynomial quotient (see poly_div).

Source

pub fn poly_rem(&self, other: &Ex, var: &Ex) -> Option<Ex>

Polynomial remainder (see poly_div).

Source

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");
Source

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"]);
Source

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");
Source

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());
Source

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);
Source

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));
Source

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");
Source

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");
Source

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");
Source

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");
Source

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");
Source

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));
Source

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));
Source

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));
Source

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>

Source

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))");
Source

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)");
Source

pub fn rewrite_traced(&self, rules: &RuleSet) -> (Ex, Vec<Step>)

Like rewrite, also returning every rule application as a Step in the order it happened.

Source

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)");
Source

pub fn rewrite_with_traced( &self, rules: &RuleSet, opts: &RewriteOpts, ) -> (Ex, Vec<Step>)

rewrite_with plus the trace of steps.

Source

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");
Source

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>

Source

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}");
Source

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");
Source

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)");
Source

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)");
Source

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)");
Source

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)");
Source

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)");
Source

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)");
Source

pub fn powdenest(&self, force: bool) -> Ex

Denest powers, each rewrite only when it is an identity:

RewriteCondition (any of)
(a·b)^e → a^e·b^ee ∈ ℤ; all factors known non-negative; force
(x^a)^b → x^(a·b)b ∈ ℤ; x > 0 and a real; force
√(x²) → xx ≥ 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");
Source

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)");
Source

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");
Source

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");
Source

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");
Source

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");
Source

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");
Source

pub fn subs_algebraic(&self, old: &Ex, new: &Ex) -> Ex

Algebraic substitution old → new: unlike subs, old is recognised inside powers, products and sums.

selfoldresult
x^4x^2y^2
x^3x^2x·y
1/x^2x^21/y
2·x·y·zx·y2·w·z
a + b + ca + bc + 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>

Source

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)");
Source

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");
Source

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");
Source

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.

Source

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());
Source

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));
Source

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);
Source

pub fn series_at_neg_infinity(&self, var: &Ex, n_terms: u32) -> Ex

Asymptotic expansion of self as var → −∞ (see series_at_infinity).

Source

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>

Source

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]");
Source

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.

Source

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]");
Source

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]");
Source

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)");
Source

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);
Source

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));
Source

pub fn is_superset(&self, other: &SetEx) -> Option<bool>

Is self ⊇ other? Three-valued.

Source

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));
Source

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));
Source

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());
Source

pub fn sup(&self) -> Option<Ex>

Least upper bound (oo when unbounded above). See inf.

Source

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");
Source

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)");
Source

pub fn closure(&self) -> Option<SetEx>

Topological closure. None when the set cannot be evaluated.

Source

pub fn interior(&self) -> Option<SetEx>

Topological interior. None when the set cannot be evaluated.

Source

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));
Source

pub fn is_closed(&self) -> Option<bool>

Is the set closed in ℝ? Three-valued.

Source

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)");
Source

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![]));
Source

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]");
Source

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
  • InvalidArgumentvar is not a symbol, conds is empty, or a condition contains a non-relational atom / does not involve var.
  • 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>

Source

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");
Source

pub fn eval(&self) -> BoolEx

Evaluate a boolean expression.

Numeric sub-expressions are evaluated exactly and relationals with numeric operands are folded (5 > 3True). 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");
Source

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");
Source

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)");
Source

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");
Source

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));
Source

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));
Source

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);
Source

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);
Source

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 → q
Source

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>

Source

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);
Source

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>

Source

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) = casin(c) + 2πn, π − asin(c) + 2πn
  • cos(x) = c±acos(c) + 2πn
  • tan(x) = catan(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>

Source

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
§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>

Source

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
§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>

Source

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 than a)
  • Direction::Left: x → a⁻ (values slightly smaller than a)
  • 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");
Source

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"));
Source

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");
Source

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");
Source

pub fn try_limit_left(&self, var: &Ex, point: &Ex) -> Result<Ex, SymplexError>

Fallible left-hand limit. See try_limit_dir.

Source

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>

Source

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
  • InvalidArgument if t/omega are not distinct symbols.
  • ComputationFailed if 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 is Result-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());
Source

pub fn fourier_transform_with( &self, t: &Ex, omega: &Ex, convention: FourierConvention, ) -> Result<Ex, SymplexError>

Fourier transform in the given FourierConvention.

conventionF =
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());
Source

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))");
Source

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>

Source

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");
Source

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
  • Divergent if s·F(s) has a pole with non-negative real part.
  • ComputationFailed if 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>

Source

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
  • InvalidArgument if var is not a symbol or the interval is degenerate.
  • ComputationFailed if 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>

Source

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
  • InvalidArgument if x/s are not distinct symbols.
  • ComputationFailed if 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)");
Source

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>

Source

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>

Source

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));
Source

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>

Source

pub fn prove_nonnegative_on_box( &self, bounds: &[BoxBound], degree: u32, ) -> Result<BoxOutcome, SymplexError>

Source§

impl Expr<Numeric>

Source

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
§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 { .. })
));
Source

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);
Source

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
§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);
Source

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);
Source

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);
Source

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);
Source

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
§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>

Source

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());
Source

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());
Source

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*ℯ");
Source

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 { .. })));
Source

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"
);
Source

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>

Source

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)");
Source

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$");
Source

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>

Source

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());
Source

pub fn to_lean_with(&self, opts: &LeanOpts) -> Result<String, SymplexError>

to_lean with explicit LeanOpts.

use symplex::prelude::*;
use symplex::lean::LeanOpts;

let ctx = Context::new();
let j = ctx.symbol("j");
let opts = LeanOpts::default().with_ascribe_integers(true);
assert_eq!((2 * &j + 1).to_lean_with(&opts).unwrap(), "(2 : ℝ) * j + (1 : ℝ)");
Source§

impl<S: Sort> Expr<S>

Source

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>&#x2061;</mo> for function application, Greek symbol names as character references, explicit <mo>(</mo> for parentheses. Relations and connectives (BoolEx) render with &gt;, &#x2265;, &#x2227;, &#x2228;, &#xAC;. 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>&#x2061;</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>&gt;</mo><mn>0</mn></mrow></math>"
);
Source§

impl<S: Sort> Expr<S>

Source

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))");
Source

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 Add<&Expr<Numeric>> for Ex

Source§

type Output = Expr<Numeric>

The resulting type after applying the + operator.
Source§

fn add(self, rhs: &Ex) -> Ex

Performs the + operation. Read more
Source§

impl Add<&Expr<Numeric>> for &Ex

Source§

type Output = Expr<Numeric>

The resulting type after applying the + operator.
Source§

fn add(self, rhs: &Ex) -> Ex

Performs the + operation. Read more
Source§

impl Add<&Expr<Numeric>> for i64

Source§

type Output = Expr<Numeric>

The resulting type after applying the + operator.
Source§

fn add(self, rhs: &Ex) -> Ex

Performs the + operation. Read more
Source§

impl Add<&Expr<Numeric>> for f64

Source§

type Output = Expr<Numeric>

The resulting type after applying the + operator.
Source§

fn add(self, rhs: &Ex) -> Ex

Performs the + operation. Read more
Source§

impl Add<&Expr<Numeric>> for BigInt

Source§

type Output = Expr<Numeric>

The resulting type after applying the + operator.
Source§

fn add(self, rhs: &Ex) -> Ex

Performs the + operation. Read more
Source§

impl Add<&Expr<Numeric>> for Ratio<BigInt>

Source§

type Output = Expr<Numeric>

The resulting type after applying the + operator.
Source§

fn add(self, rhs: &Ex) -> Ex

Performs the + operation. Read more
Source§

impl Add<Expr<Numeric>> for &Ex

Source§

type Output = Expr<Numeric>

The resulting type after applying the + operator.
Source§

fn add(self, rhs: Ex) -> Ex

Performs the + operation. Read more
Source§

impl Add<Expr<Numeric>> for i64

Source§

type Output = Expr<Numeric>

The resulting type after applying the + operator.
Source§

fn add(self, rhs: Ex) -> Ex

Performs the + operation. Read more
Source§

impl Add<Expr<Numeric>> for f64

Source§

type Output = Expr<Numeric>

The resulting type after applying the + operator.
Source§

fn add(self, rhs: Ex) -> Ex

Performs the + operation. Read more
Source§

impl Add<Expr<Numeric>> for BigInt

Source§

type Output = Expr<Numeric>

The resulting type after applying the + operator.
Source§

fn add(self, rhs: Ex) -> Ex

Performs the + operation. Read more
Source§

impl Add<Expr<Numeric>> for Ratio<BigInt>

Source§

type Output = Expr<Numeric>

The resulting type after applying the + operator.
Source§

fn add(self, rhs: Ex) -> Ex

Performs the + operation. Read more
Source§

impl AddAssign<&Expr<Numeric>> for Ex

Source§

fn add_assign(&mut self, rhs: &Ex)

Performs the += operation. Read more
Source§

impl AsRef<Expr<Numeric>> for Ex

Source§

fn as_ref(&self) -> &Ex

Converts this type into a shared reference of the (usually inferred) input type.
Source§

impl<D> AsRef<Expr<Numeric>> for Qty<D>

Source§

fn as_ref(&self) -> &Ex

Converts this type into a shared reference of the (usually inferred) input type.
Source§

impl AsRef<Expr<Numeric>> for Dimensionless

Source§

fn as_ref(&self) -> &Ex

Converts this type into a shared reference of the (usually inferred) input type.
Source§

impl AsRef<Expr<Numeric>> for Angle

Source§

fn as_ref(&self) -> &Ex

Converts this type into a shared reference of the (usually inferred) input type.
Source§

impl AsRef<Expr<Numeric>> for Length

Source§

fn as_ref(&self) -> &Ex

Converts this type into a shared reference of the (usually inferred) input type.
Source§

impl AsRef<Expr<Numeric>> for Mass

Source§

fn as_ref(&self) -> &Ex

Converts this type into a shared reference of the (usually inferred) input type.
Source§

impl AsRef<Expr<Numeric>> for Time

Source§

fn as_ref(&self) -> &Ex

Converts this type into a shared reference of the (usually inferred) input type.
Source§

impl AsRef<Expr<Numeric>> for Current

Source§

fn as_ref(&self) -> &Ex

Converts this type into a shared reference of the (usually inferred) input type.
Source§

impl AsRef<Expr<Numeric>> for Temperature

Source§

fn as_ref(&self) -> &Ex

Converts this type into a shared reference of the (usually inferred) input type.
Source§

impl AsRef<Expr<Numeric>> for Area

Source§

fn as_ref(&self) -> &Ex

Converts this type into a shared reference of the (usually inferred) input type.
Source§

impl AsRef<Expr<Numeric>> for Volume

Source§

fn as_ref(&self) -> &Ex

Converts this type into a shared reference of the (usually inferred) input type.
Source§

impl AsRef<Expr<Numeric>> for Velocity

Source§

fn as_ref(&self) -> &Ex

Converts this type into a shared reference of the (usually inferred) input type.
Source§

impl AsRef<Expr<Numeric>> for Acceleration

Source§

fn as_ref(&self) -> &Ex

Converts this type into a shared reference of the (usually inferred) input type.
Source§

impl AsRef<Expr<Numeric>> for AngularVelocity

Source§

fn as_ref(&self) -> &Ex

Converts this type into a shared reference of the (usually inferred) input type.
Source§

impl AsRef<Expr<Numeric>> for AngularAcceleration

Source§

fn as_ref(&self) -> &Ex

Converts this type into a shared reference of the (usually inferred) input type.
Source§

impl AsRef<Expr<Numeric>> for Frequency

Source§

fn as_ref(&self) -> &Ex

Converts this type into a shared reference of the (usually inferred) input type.
Source§

impl AsRef<Expr<Numeric>> for Force

Source§

fn as_ref(&self) -> &Ex

Converts this type into a shared reference of the (usually inferred) input type.
Source§

impl AsRef<Expr<Numeric>> for Energy

Source§

fn as_ref(&self) -> &Ex

Converts this type into a shared reference of the (usually inferred) input type.
Source§

impl AsRef<Expr<Numeric>> for Torque

Source§

fn as_ref(&self) -> &Ex

Converts this type into a shared reference of the (usually inferred) input type.
Source§

impl AsRef<Expr<Numeric>> for Power

Source§

fn as_ref(&self) -> &Ex

Converts this type into a shared reference of the (usually inferred) input type.
Source§

impl AsRef<Expr<Numeric>> for Momentum

Source§

fn as_ref(&self) -> &Ex

Converts this type into a shared reference of the (usually inferred) input type.
Source§

impl AsRef<Expr<Numeric>> for AngularMomentum

Source§

fn as_ref(&self) -> &Ex

Converts this type into a shared reference of the (usually inferred) input type.
Source§

impl AsRef<Expr<Numeric>> for MomentOfInertia

Source§

fn as_ref(&self) -> &Ex

Converts this type into a shared reference of the (usually inferred) input type.
Source§

impl AsRef<Expr<Numeric>> for Pressure

Source§

fn as_ref(&self) -> &Ex

Converts this type into a shared reference of the (usually inferred) input type.
Source§

impl AsRef<Expr<Numeric>> for Stiffness

Source§

fn as_ref(&self) -> &Ex

Converts this type into a shared reference of the (usually inferred) input type.
Source§

impl AsRef<Expr<Numeric>> for Damping

Source§

fn as_ref(&self) -> &Ex

Converts this type into a shared reference of the (usually inferred) input type.
Source§

impl AsRef<Expr<Numeric>> for Voltage

Source§

fn as_ref(&self) -> &Ex

Converts this type into a shared reference of the (usually inferred) input type.
Source§

impl AsRef<Expr<Numeric>> for Resistance

Source§

fn as_ref(&self) -> &Ex

Converts this type into a shared reference of the (usually inferred) input type.
Source§

impl AsRef<Expr<Numeric>> for Inductance

Source§

fn as_ref(&self) -> &Ex

Converts this type into a shared reference of the (usually inferred) input type.
Source§

impl AsRef<Expr<Numeric>> for Capacitance

Source§

fn as_ref(&self) -> &Ex

Converts this type into a shared reference of the (usually inferred) input type.
Source§

impl AsRef<Expr<Numeric>> for Charge

Source§

fn as_ref(&self) -> &Ex

Converts this type into a shared reference of the (usually inferred) input type.
Source§

impl AsRef<Expr<Numeric>> for MagneticFlux

Source§

fn as_ref(&self) -> &Ex

Converts this type into a shared reference of the (usually inferred) input type.
Source§

impl<S: Clone + Sort> Clone for Expr<S>

Source§

fn clone(&self) -> Self

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

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§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl<S: Sort> Display for Expr<S>

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Div<&Expr<Numeric>> for Ex

Source§

type Output = Expr<Numeric>

The resulting type after applying the / operator.
Source§

fn div(self, rhs: &Ex) -> Ex

Performs the / operation. Read more
Source§

impl Div<&Expr<Numeric>> for &Ex

Source§

type Output = Expr<Numeric>

The resulting type after applying the / operator.
Source§

fn div(self, rhs: &Ex) -> Ex

Performs the / operation. Read more
Source§

impl Div<&Expr<Numeric>> for i64

Source§

type Output = Expr<Numeric>

The resulting type after applying the / operator.
Source§

fn div(self, rhs: &Ex) -> Ex

Performs the / operation. Read more
Source§

impl Div<&Expr<Numeric>> for f64

Source§

type Output = Expr<Numeric>

The resulting type after applying the / operator.
Source§

fn div(self, rhs: &Ex) -> Ex

Performs the / operation. Read more
Source§

impl Div<&Expr<Numeric>> for BigInt

Source§

type Output = Expr<Numeric>

The resulting type after applying the / operator.
Source§

fn div(self, rhs: &Ex) -> Ex

Performs the / operation. Read more
Source§

impl Div<&Expr<Numeric>> for Ratio<BigInt>

Source§

type Output = Expr<Numeric>

The resulting type after applying the / operator.
Source§

fn div(self, rhs: &Ex) -> Ex

Performs the / operation. Read more
Source§

impl Div<&Expr<Numeric>> for &Matrix

Source§

type Output = Matrix

The resulting type after applying the / operator.
Source§

fn div(self, rhs: &Ex) -> Matrix

Performs the / operation. Read more
Source§

impl Div<&Expr<Numeric>> for Matrix

Source§

type Output = Matrix

The resulting type after applying the / operator.
Source§

fn div(self, rhs: &Ex) -> Matrix

Performs the / operation. Read more
Source§

impl Div<&Expr<Numeric>> for &Quaternion

Source§

type Output = Quaternion

The resulting type after applying the / operator.
Source§

fn div(self, rhs: &Ex) -> Quaternion

Performs the / operation. Read more
Source§

impl Div<&Expr<Numeric>> for Quaternion

Source§

type Output = Quaternion

The resulting type after applying the / operator.
Source§

fn div(self, rhs: &Ex) -> Quaternion

Performs the / operation. Read more
Source§

impl<D> Div<&Expr<Numeric>> for Qty<D>

Source§

type Output = Qty<D>

The resulting type after applying the / operator.
Source§

fn div(self, rhs: &Ex) -> Qty<D>

Performs the / operation. Read more
Source§

impl<D> Div<&Expr<Numeric>> for &Qty<D>

Source§

type Output = Qty<D>

The resulting type after applying the / operator.
Source§

fn div(self, rhs: &Ex) -> Qty<D>

Performs the / operation. Read more
Source§

impl Div<Expr<Numeric>> for &Ex

Source§

type Output = Expr<Numeric>

The resulting type after applying the / operator.
Source§

fn div(self, rhs: Ex) -> Ex

Performs the / operation. Read more
Source§

impl Div<Expr<Numeric>> for i64

Source§

type Output = Expr<Numeric>

The resulting type after applying the / operator.
Source§

fn div(self, rhs: Ex) -> Ex

Performs the / operation. Read more
Source§

impl Div<Expr<Numeric>> for f64

Source§

type Output = Expr<Numeric>

The resulting type after applying the / operator.
Source§

fn div(self, rhs: Ex) -> Ex

Performs the / operation. Read more
Source§

impl Div<Expr<Numeric>> for BigInt

Source§

type Output = Expr<Numeric>

The resulting type after applying the / operator.
Source§

fn div(self, rhs: Ex) -> Ex

Performs the / operation. Read more
Source§

impl Div<Expr<Numeric>> for Ratio<BigInt>

Source§

type Output = Expr<Numeric>

The resulting type after applying the / operator.
Source§

fn div(self, rhs: Ex) -> Ex

Performs the / operation. Read more
Source§

impl Div<Expr<Numeric>> for &Matrix

Source§

type Output = Matrix

The resulting type after applying the / operator.
Source§

fn div(self, rhs: Ex) -> Matrix

Performs the / operation. Read more
Source§

impl Div<Expr<Numeric>> for Matrix

Source§

type Output = Matrix

The resulting type after applying the / operator.
Source§

fn div(self, rhs: Ex) -> Matrix

Performs the / operation. Read more
Source§

impl Div<Expr<Numeric>> for &Quaternion

Source§

type Output = Quaternion

The resulting type after applying the / operator.
Source§

fn div(self, rhs: Ex) -> Quaternion

Performs the / operation. Read more
Source§

impl Div<Expr<Numeric>> for Quaternion

Source§

type Output = Quaternion

The resulting type after applying the / operator.
Source§

fn div(self, rhs: Ex) -> Quaternion

Performs the / operation. Read more
Source§

impl DivAssign<&Expr<Numeric>> for Ex

Source§

fn div_assign(&mut self, rhs: &Ex)

Performs the /= operation. Read more
Source§

impl<S: Sort> Eq for Expr<S>

Source§

impl<S: Sort> Hash for Expr<S>

Source§

fn hash<H: Hasher>(&self, state: &mut H)

Feeds this value into the given Hasher. Read more
1.3.0 · Source§

fn hash_slice<H>(data: &[Self], state: &mut H)
where H: Hasher, Self: Sized,

Feeds a slice of this type into the given Hasher. Read more
Source§

impl Mul<&Expr<Numeric>> for Ex

Source§

type Output = Expr<Numeric>

The resulting type after applying the * operator.
Source§

fn mul(self, rhs: &Ex) -> Ex

Performs the * operation. Read more
Source§

impl Mul<&Expr<Numeric>> for &Ex

Source§

type Output = Expr<Numeric>

The resulting type after applying the * operator.
Source§

fn mul(self, rhs: &Ex) -> Ex

Performs the * operation. Read more
Source§

impl Mul<&Expr<Numeric>> for i64

Source§

type Output = Expr<Numeric>

The resulting type after applying the * operator.
Source§

fn mul(self, rhs: &Ex) -> Ex

Performs the * operation. Read more
Source§

impl Mul<&Expr<Numeric>> for f64

Source§

type Output = Expr<Numeric>

The resulting type after applying the * operator.
Source§

fn mul(self, rhs: &Ex) -> Ex

Performs the * operation. Read more
Source§

impl Mul<&Expr<Numeric>> for BigInt

Source§

type Output = Expr<Numeric>

The resulting type after applying the * operator.
Source§

fn mul(self, rhs: &Ex) -> Ex

Performs the * operation. Read more
Source§

impl Mul<&Expr<Numeric>> for Ratio<BigInt>

Source§

type Output = Expr<Numeric>

The resulting type after applying the * operator.
Source§

fn mul(self, rhs: &Ex) -> Ex

Performs the * operation. Read more
Source§

impl Mul<&Expr<Numeric>> for &Matrix

Source§

type Output = Matrix

The resulting type after applying the * operator.
Source§

fn mul(self, rhs: &Ex) -> Matrix

Performs the * operation. Read more
Source§

impl Mul<&Expr<Numeric>> for Matrix

Source§

type Output = Matrix

The resulting type after applying the * operator.
Source§

fn mul(self, rhs: &Ex) -> Matrix

Performs the * operation. Read more
Source§

impl Mul<&Expr<Numeric>> for &Quaternion

Source§

type Output = Quaternion

The resulting type after applying the * operator.
Source§

fn mul(self, rhs: &Ex) -> Quaternion

Performs the * operation. Read more
Source§

impl Mul<&Expr<Numeric>> for Quaternion

Source§

type Output = Quaternion

The resulting type after applying the * operator.
Source§

fn mul(self, rhs: &Ex) -> Quaternion

Performs the * operation. Read more
Source§

impl<D> Mul<&Expr<Numeric>> for Qty<D>

Source§

type Output = Qty<D>

The resulting type after applying the * operator.
Source§

fn mul(self, rhs: &Ex) -> Qty<D>

Performs the * operation. Read more
Source§

impl<D> Mul<&Expr<Numeric>> for &Qty<D>

Source§

type Output = Qty<D>

The resulting type after applying the * operator.
Source§

fn mul(self, rhs: &Ex) -> Qty<D>

Performs the * operation. Read more
Source§

impl Mul<&Expr<Numeric>> for Dimensionless

Source§

type Output = Dimensionless

The resulting type after applying the * operator.
Source§

fn mul(self, rhs: &Ex) -> Dimensionless

Performs the * operation. Read more
Source§

impl Mul<&Expr<Numeric>> for &Dimensionless

Source§

type Output = Dimensionless

The resulting type after applying the * operator.
Source§

fn mul(self, rhs: &Ex) -> Dimensionless

Performs the * operation. Read more
Source§

impl Mul<&Expr<Numeric>> for Angle

Source§

type Output = Angle

The resulting type after applying the * operator.
Source§

fn mul(self, rhs: &Ex) -> Angle

Performs the * operation. Read more
Source§

impl Mul<&Expr<Numeric>> for &Angle

Source§

type Output = Angle

The resulting type after applying the * operator.
Source§

fn mul(self, rhs: &Ex) -> Angle

Performs the * operation. Read more
Source§

impl Mul<&Expr<Numeric>> for Length

Source§

type Output = Length

The resulting type after applying the * operator.
Source§

fn mul(self, rhs: &Ex) -> Length

Performs the * operation. Read more
Source§

impl Mul<&Expr<Numeric>> for &Length

Source§

type Output = Length

The resulting type after applying the * operator.
Source§

fn mul(self, rhs: &Ex) -> Length

Performs the * operation. Read more
Source§

impl Mul<&Expr<Numeric>> for Mass

Source§

type Output = Mass

The resulting type after applying the * operator.
Source§

fn mul(self, rhs: &Ex) -> Mass

Performs the * operation. Read more
Source§

impl Mul<&Expr<Numeric>> for &Mass

Source§

type Output = Mass

The resulting type after applying the * operator.
Source§

fn mul(self, rhs: &Ex) -> Mass

Performs the * operation. Read more
Source§

impl Mul<&Expr<Numeric>> for Time

Source§

type Output = Time

The resulting type after applying the * operator.
Source§

fn mul(self, rhs: &Ex) -> Time

Performs the * operation. Read more
Source§

impl Mul<&Expr<Numeric>> for &Time

Source§

type Output = Time

The resulting type after applying the * operator.
Source§

fn mul(self, rhs: &Ex) -> Time

Performs the * operation. Read more
Source§

impl Mul<&Expr<Numeric>> for Current

Source§

type Output = Current

The resulting type after applying the * operator.
Source§

fn mul(self, rhs: &Ex) -> Current

Performs the * operation. Read more
Source§

impl Mul<&Expr<Numeric>> for &Current

Source§

type Output = Current

The resulting type after applying the * operator.
Source§

fn mul(self, rhs: &Ex) -> Current

Performs the * operation. Read more
Source§

impl Mul<&Expr<Numeric>> for Temperature

Source§

type Output = Temperature

The resulting type after applying the * operator.
Source§

fn mul(self, rhs: &Ex) -> Temperature

Performs the * operation. Read more
Source§

impl Mul<&Expr<Numeric>> for &Temperature

Source§

type Output = Temperature

The resulting type after applying the * operator.
Source§

fn mul(self, rhs: &Ex) -> Temperature

Performs the * operation. Read more
Source§

impl Mul<&Expr<Numeric>> for Area

Source§

type Output = Area

The resulting type after applying the * operator.
Source§

fn mul(self, rhs: &Ex) -> Area

Performs the * operation. Read more
Source§

impl Mul<&Expr<Numeric>> for &Area

Source§

type Output = Area

The resulting type after applying the * operator.
Source§

fn mul(self, rhs: &Ex) -> Area

Performs the * operation. Read more
Source§

impl Mul<&Expr<Numeric>> for Volume

Source§

type Output = Volume

The resulting type after applying the * operator.
Source§

fn mul(self, rhs: &Ex) -> Volume

Performs the * operation. Read more
Source§

impl Mul<&Expr<Numeric>> for &Volume

Source§

type Output = Volume

The resulting type after applying the * operator.
Source§

fn mul(self, rhs: &Ex) -> Volume

Performs the * operation. Read more
Source§

impl Mul<&Expr<Numeric>> for Velocity

Source§

type Output = Velocity

The resulting type after applying the * operator.
Source§

fn mul(self, rhs: &Ex) -> Velocity

Performs the * operation. Read more
Source§

impl Mul<&Expr<Numeric>> for &Velocity

Source§

type Output = Velocity

The resulting type after applying the * operator.
Source§

fn mul(self, rhs: &Ex) -> Velocity

Performs the * operation. Read more
Source§

impl Mul<&Expr<Numeric>> for Acceleration

Source§

type Output = Acceleration

The resulting type after applying the * operator.
Source§

fn mul(self, rhs: &Ex) -> Acceleration

Performs the * operation. Read more
Source§

impl Mul<&Expr<Numeric>> for &Acceleration

Source§

type Output = Acceleration

The resulting type after applying the * operator.
Source§

fn mul(self, rhs: &Ex) -> Acceleration

Performs the * operation. Read more
Source§

impl Mul<&Expr<Numeric>> for AngularVelocity

Source§

type Output = AngularVelocity

The resulting type after applying the * operator.
Source§

fn mul(self, rhs: &Ex) -> AngularVelocity

Performs the * operation. Read more
Source§

impl Mul<&Expr<Numeric>> for &AngularVelocity

Source§

type Output = AngularVelocity

The resulting type after applying the * operator.
Source§

fn mul(self, rhs: &Ex) -> AngularVelocity

Performs the * operation. Read more
Source§

impl Mul<&Expr<Numeric>> for AngularAcceleration

Source§

type Output = AngularAcceleration

The resulting type after applying the * operator.
Source§

fn mul(self, rhs: &Ex) -> AngularAcceleration

Performs the * operation. Read more
Source§

impl Mul<&Expr<Numeric>> for &AngularAcceleration

Source§

type Output = AngularAcceleration

The resulting type after applying the * operator.
Source§

fn mul(self, rhs: &Ex) -> AngularAcceleration

Performs the * operation. Read more
Source§

impl Mul<&Expr<Numeric>> for Frequency

Source§

type Output = Frequency

The resulting type after applying the * operator.
Source§

fn mul(self, rhs: &Ex) -> Frequency

Performs the * operation. Read more
Source§

impl Mul<&Expr<Numeric>> for &Frequency

Source§

type Output = Frequency

The resulting type after applying the * operator.
Source§

fn mul(self, rhs: &Ex) -> Frequency

Performs the * operation. Read more
Source§

impl Mul<&Expr<Numeric>> for Force

Source§

type Output = Force

The resulting type after applying the * operator.
Source§

fn mul(self, rhs: &Ex) -> Force

Performs the * operation. Read more
Source§

impl Mul<&Expr<Numeric>> for &Force

Source§

type Output = Force

The resulting type after applying the * operator.
Source§

fn mul(self, rhs: &Ex) -> Force

Performs the * operation. Read more
Source§

impl Mul<&Expr<Numeric>> for Energy

Source§

type Output = Energy

The resulting type after applying the * operator.
Source§

fn mul(self, rhs: &Ex) -> Energy

Performs the * operation. Read more
Source§

impl Mul<&Expr<Numeric>> for &Energy

Source§

type Output = Energy

The resulting type after applying the * operator.
Source§

fn mul(self, rhs: &Ex) -> Energy

Performs the * operation. Read more
Source§

impl Mul<&Expr<Numeric>> for Torque

Source§

type Output = Torque

The resulting type after applying the * operator.
Source§

fn mul(self, rhs: &Ex) -> Torque

Performs the * operation. Read more
Source§

impl Mul<&Expr<Numeric>> for &Torque

Source§

type Output = Torque

The resulting type after applying the * operator.
Source§

fn mul(self, rhs: &Ex) -> Torque

Performs the * operation. Read more
Source§

impl Mul<&Expr<Numeric>> for Power

Source§

type Output = Power

The resulting type after applying the * operator.
Source§

fn mul(self, rhs: &Ex) -> Power

Performs the * operation. Read more
Source§

impl Mul<&Expr<Numeric>> for &Power

Source§

type Output = Power

The resulting type after applying the * operator.
Source§

fn mul(self, rhs: &Ex) -> Power

Performs the * operation. Read more
Source§

impl Mul<&Expr<Numeric>> for Momentum

Source§

type Output = Momentum

The resulting type after applying the * operator.
Source§

fn mul(self, rhs: &Ex) -> Momentum

Performs the * operation. Read more
Source§

impl Mul<&Expr<Numeric>> for &Momentum

Source§

type Output = Momentum

The resulting type after applying the * operator.
Source§

fn mul(self, rhs: &Ex) -> Momentum

Performs the * operation. Read more
Source§

impl Mul<&Expr<Numeric>> for AngularMomentum

Source§

type Output = AngularMomentum

The resulting type after applying the * operator.
Source§

fn mul(self, rhs: &Ex) -> AngularMomentum

Performs the * operation. Read more
Source§

impl Mul<&Expr<Numeric>> for &AngularMomentum

Source§

type Output = AngularMomentum

The resulting type after applying the * operator.
Source§

fn mul(self, rhs: &Ex) -> AngularMomentum

Performs the * operation. Read more
Source§

impl Mul<&Expr<Numeric>> for MomentOfInertia

Source§

type Output = MomentOfInertia

The resulting type after applying the * operator.
Source§

fn mul(self, rhs: &Ex) -> MomentOfInertia

Performs the * operation. Read more
Source§

impl Mul<&Expr<Numeric>> for &MomentOfInertia

Source§

type Output = MomentOfInertia

The resulting type after applying the * operator.
Source§

fn mul(self, rhs: &Ex) -> MomentOfInertia

Performs the * operation. Read more
Source§

impl Mul<&Expr<Numeric>> for Pressure

Source§

type Output = Pressure

The resulting type after applying the * operator.
Source§

fn mul(self, rhs: &Ex) -> Pressure

Performs the * operation. Read more
Source§

impl Mul<&Expr<Numeric>> for &Pressure

Source§

type Output = Pressure

The resulting type after applying the * operator.
Source§

fn mul(self, rhs: &Ex) -> Pressure

Performs the * operation. Read more
Source§

impl Mul<&Expr<Numeric>> for Stiffness

Source§

type Output = Stiffness

The resulting type after applying the * operator.
Source§

fn mul(self, rhs: &Ex) -> Stiffness

Performs the * operation. Read more
Source§

impl Mul<&Expr<Numeric>> for &Stiffness

Source§

type Output = Stiffness

The resulting type after applying the * operator.
Source§

fn mul(self, rhs: &Ex) -> Stiffness

Performs the * operation. Read more
Source§

impl Mul<&Expr<Numeric>> for Damping

Source§

type Output = Damping

The resulting type after applying the * operator.
Source§

fn mul(self, rhs: &Ex) -> Damping

Performs the * operation. Read more
Source§

impl Mul<&Expr<Numeric>> for &Damping

Source§

type Output = Damping

The resulting type after applying the * operator.
Source§

fn mul(self, rhs: &Ex) -> Damping

Performs the * operation. Read more
Source§

impl Mul<&Expr<Numeric>> for Voltage

Source§

type Output = Voltage

The resulting type after applying the * operator.
Source§

fn mul(self, rhs: &Ex) -> Voltage

Performs the * operation. Read more
Source§

impl Mul<&Expr<Numeric>> for &Voltage

Source§

type Output = Voltage

The resulting type after applying the * operator.
Source§

fn mul(self, rhs: &Ex) -> Voltage

Performs the * operation. Read more
Source§

impl Mul<&Expr<Numeric>> for Resistance

Source§

type Output = Resistance

The resulting type after applying the * operator.
Source§

fn mul(self, rhs: &Ex) -> Resistance

Performs the * operation. Read more
Source§

impl Mul<&Expr<Numeric>> for &Resistance

Source§

type Output = Resistance

The resulting type after applying the * operator.
Source§

fn mul(self, rhs: &Ex) -> Resistance

Performs the * operation. Read more
Source§

impl Mul<&Expr<Numeric>> for Inductance

Source§

type Output = Inductance

The resulting type after applying the * operator.
Source§

fn mul(self, rhs: &Ex) -> Inductance

Performs the * operation. Read more
Source§

impl Mul<&Expr<Numeric>> for &Inductance

Source§

type Output = Inductance

The resulting type after applying the * operator.
Source§

fn mul(self, rhs: &Ex) -> Inductance

Performs the * operation. Read more
Source§

impl Mul<&Expr<Numeric>> for Capacitance

Source§

type Output = Capacitance

The resulting type after applying the * operator.
Source§

fn mul(self, rhs: &Ex) -> Capacitance

Performs the * operation. Read more
Source§

impl Mul<&Expr<Numeric>> for &Capacitance

Source§

type Output = Capacitance

The resulting type after applying the * operator.
Source§

fn mul(self, rhs: &Ex) -> Capacitance

Performs the * operation. Read more
Source§

impl Mul<&Expr<Numeric>> for Charge

Source§

type Output = Charge

The resulting type after applying the * operator.
Source§

fn mul(self, rhs: &Ex) -> Charge

Performs the * operation. Read more
Source§

impl Mul<&Expr<Numeric>> for &Charge

Source§

type Output = Charge

The resulting type after applying the * operator.
Source§

fn mul(self, rhs: &Ex) -> Charge

Performs the * operation. Read more
Source§

impl Mul<&Expr<Numeric>> for MagneticFlux

Source§

type Output = MagneticFlux

The resulting type after applying the * operator.
Source§

fn mul(self, rhs: &Ex) -> MagneticFlux

Performs the * operation. Read more
Source§

impl Mul<&Expr<Numeric>> for &MagneticFlux

Source§

type Output = MagneticFlux

The resulting type after applying the * operator.
Source§

fn mul(self, rhs: &Ex) -> MagneticFlux

Performs the * operation. Read more
Source§

impl Mul<Expr<Numeric>> for &Ex

Source§

type Output = Expr<Numeric>

The resulting type after applying the * operator.
Source§

fn mul(self, rhs: Ex) -> Ex

Performs the * operation. Read more
Source§

impl Mul<Expr<Numeric>> for i64

Source§

type Output = Expr<Numeric>

The resulting type after applying the * operator.
Source§

fn mul(self, rhs: Ex) -> Ex

Performs the * operation. Read more
Source§

impl Mul<Expr<Numeric>> for f64

Source§

type Output = Expr<Numeric>

The resulting type after applying the * operator.
Source§

fn mul(self, rhs: Ex) -> Ex

Performs the * operation. Read more
Source§

impl Mul<Expr<Numeric>> for BigInt

Source§

type Output = Expr<Numeric>

The resulting type after applying the * operator.
Source§

fn mul(self, rhs: Ex) -> Ex

Performs the * operation. Read more
Source§

impl Mul<Expr<Numeric>> for Ratio<BigInt>

Source§

type Output = Expr<Numeric>

The resulting type after applying the * operator.
Source§

fn mul(self, rhs: Ex) -> Ex

Performs the * operation. Read more
Source§

impl Mul<Expr<Numeric>> for &Matrix

Source§

type Output = Matrix

The resulting type after applying the * operator.
Source§

fn mul(self, rhs: Ex) -> Matrix

Performs the * operation. Read more
Source§

impl Mul<Expr<Numeric>> for Matrix

Source§

type Output = Matrix

The resulting type after applying the * operator.
Source§

fn mul(self, rhs: Ex) -> Matrix

Performs the * operation. Read more
Source§

impl Mul<Expr<Numeric>> for &Quaternion

Source§

type Output = Quaternion

The resulting type after applying the * operator.
Source§

fn mul(self, rhs: Ex) -> Quaternion

Performs the * operation. Read more
Source§

impl Mul<Expr<Numeric>> for Quaternion

Source§

type Output = Quaternion

The resulting type after applying the * operator.
Source§

fn mul(self, rhs: Ex) -> Quaternion

Performs the * operation. Read more
Source§

impl MulAssign<&Expr<Numeric>> for Ex

Source§

fn mul_assign(&mut self, rhs: &Ex)

Performs the *= operation. Read more
Source§

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§

fn eq(&self, other: &Self) -> bool

Equality operator ==. Read more
1.0.0 (const: unstable) · Source§

fn ne(&self, other: &Rhs) -> bool

Inequality operator !=. Read more
Source§

impl<S: Sort> PartialEq<&Expr<S>> for ExprView<'_>

Source§

fn eq(&self, other: &&Expr<S>) -> bool

Equality operator ==. Read more
1.0.0 (const: unstable) · Source§

fn ne(&self, other: &Rhs) -> bool

Inequality operator !=. Read more
Source§

impl<S: Sort> PartialEq<Expr<S>> for ExprView<'_>

Source§

fn eq(&self, other: &Expr<S>) -> bool

Equality operator ==. Read more
1.0.0 (const: unstable) · Source§

fn ne(&self, other: &Rhs) -> bool

Inequality operator !=. Read more
Source§

impl<'a> Product<&'a Expr<Numeric>> for Ex

Source§

fn product<I: Iterator<Item = &'a Ex>>(iter: I) -> Self

Takes an iterator and generates Self from the elements by multiplying the items.
Source§

impl<'a> Product<&'a Expr<Numeric>> for Option<Ex>

Source§

fn product<I: Iterator<Item = &'a Ex>>(iter: I) -> Self

Takes an iterator and generates Self from the elements by multiplying the items.
Source§

impl Product<Expr<Numeric>> for Option<Ex>

None on an empty iterator, otherwise Some(product).

Source§

fn product<I: Iterator<Item = Ex>>(iter: I) -> Self

Takes an iterator and generates Self from the elements by multiplying the items.
Source§

impl Sub<&Expr<Numeric>> for Ex

Source§

type Output = Expr<Numeric>

The resulting type after applying the - operator.
Source§

fn sub(self, rhs: &Ex) -> Ex

Performs the - operation. Read more
Source§

impl Sub<&Expr<Numeric>> for &Ex

Source§

type Output = Expr<Numeric>

The resulting type after applying the - operator.
Source§

fn sub(self, rhs: &Ex) -> Ex

Performs the - operation. Read more
Source§

impl Sub<&Expr<Numeric>> for i64

Source§

type Output = Expr<Numeric>

The resulting type after applying the - operator.
Source§

fn sub(self, rhs: &Ex) -> Ex

Performs the - operation. Read more
Source§

impl Sub<&Expr<Numeric>> for f64

Source§

type Output = Expr<Numeric>

The resulting type after applying the - operator.
Source§

fn sub(self, rhs: &Ex) -> Ex

Performs the - operation. Read more
Source§

impl Sub<&Expr<Numeric>> for BigInt

Source§

type Output = Expr<Numeric>

The resulting type after applying the - operator.
Source§

fn sub(self, rhs: &Ex) -> Ex

Performs the - operation. Read more
Source§

impl Sub<&Expr<Numeric>> for Ratio<BigInt>

Source§

type Output = Expr<Numeric>

The resulting type after applying the - operator.
Source§

fn sub(self, rhs: &Ex) -> Ex

Performs the - operation. Read more
Source§

impl Sub<Expr<Numeric>> for &Ex

Source§

type Output = Expr<Numeric>

The resulting type after applying the - operator.
Source§

fn sub(self, rhs: Ex) -> Ex

Performs the - operation. Read more
Source§

impl Sub<Expr<Numeric>> for i64

Source§

type Output = Expr<Numeric>

The resulting type after applying the - operator.
Source§

fn sub(self, rhs: Ex) -> Ex

Performs the - operation. Read more
Source§

impl Sub<Expr<Numeric>> for f64

Source§

type Output = Expr<Numeric>

The resulting type after applying the - operator.
Source§

fn sub(self, rhs: Ex) -> Ex

Performs the - operation. Read more
Source§

impl Sub<Expr<Numeric>> for BigInt

Source§

type Output = Expr<Numeric>

The resulting type after applying the - operator.
Source§

fn sub(self, rhs: Ex) -> Ex

Performs the - operation. Read more
Source§

impl Sub<Expr<Numeric>> for Ratio<BigInt>

Source§

type Output = Expr<Numeric>

The resulting type after applying the - operator.
Source§

fn sub(self, rhs: Ex) -> Ex

Performs the - operation. Read more
Source§

impl SubAssign<&Expr<Numeric>> for Ex

Source§

fn sub_assign(&mut self, rhs: &Ex)

Performs the -= operation. Read more
Source§

impl<'a> Sum<&'a Expr<Numeric>> for Ex

Source§

fn sum<I: Iterator<Item = &'a Ex>>(iter: I) -> Self

Takes an iterator and generates Self from the elements by “summing up” the items.
Source§

impl<'a> Sum<&'a Expr<Numeric>> for Option<Ex>

Source§

fn sum<I: Iterator<Item = &'a Ex>>(iter: I) -> Self

Takes an iterator and generates Self from the elements by “summing up” the items.
Source§

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());
Source§

fn sum<I: Iterator<Item = Ex>>(iter: I) -> Self

Takes an iterator and generates Self from the elements by “summing up” the items.

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>

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts 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 more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where F: FnOnce(&Self) -> bool,

Converts 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
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T> ToString for T
where T: Display + ?Sized,

Source§

fn to_string(&self) -> String

Converts the given value to a String. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V

Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more