pub type Ex = Expr<Numeric>;Expand description
A numeric expression — the primary type for symbolic math.
Aliased Type§
pub struct Ex { /* private fields */ }Implementations§
Source§impl Ex
impl Ex
Sourcepub fn singularities(
&self,
var: &Ex,
domain: Option<&SetEx>,
) -> Result<SetEx, SymplexError>
pub fn singularities( &self, var: &Ex, domain: Option<&SetEx>, ) -> Result<SetEx, SymplexError>
The points of domain (default ℝ) where self is undefined —
SymPy’s singularities.
The rule set is SymPy’s: zeros of the base of every negative power
(this covers denominators, sec, csc and cot), zeros of the
argument of ln, poles of tan, and atanh(g) at g = ±1. Only
real points are reported.
Zeros are found exactly with solve; equations with
sin/cos/tan of var use solve_general
and the periodic families are enumerated inside a bounded domain
(tan(x) on [0, 10] gives {π/2, 3π/2, 5π/2}). On an unbounded
domain such a family is returned as the condition set
{x | cos(x) = 0} (intersected with the domain), since the
infinite family has no interval / finite-set representation.
§Errors
InvalidArgument—varis not a symbol.ComputationFailed— the zeros of some source cannot be found exactly (the solver fails ong = 0), or, on the periodic-family path only, a family is not linear in its integer parameter, its members cannot be located numerically, more than 10 000 of them may lie in the domain, or the membership of a member indomaincannot be decided. Plain (non-periodic) zeros whose membership indomainis undecided do not error: the result is thenOkwith the intersection{p, …} ∩ domainleft unevaluated.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
// SymPy: singularities(1/(x**2 - 1), x) == {-1, 1}
let s = (1 / (&x.powi(2) - 1)).singularities(&x, None).unwrap();
assert_eq!(s.to_string(), "{-1, 1}");
// SymPy: singularities(log(x), x) == {0}
assert_eq!(x.ln().singularities(&x, None).unwrap().to_string(), "{0}");
// Polynomials have none.
assert_eq!(x.powi(2).singularities(&x, None).unwrap().is_empty(), Some(true));
// Restricted to a domain.
let dom = ctx.interval(&ctx.int(0), &ctx.int(5), false, false);
assert_eq!((1 / (&x.powi(2) - 1)).singularities(&x, Some(&dom)).unwrap().to_string(), "{1}");Sourcepub fn stationary_points(
&self,
var: &Ex,
domain: Option<&SetEx>,
) -> Result<SetEx, SymplexError>
pub fn stationary_points( &self, var: &Ex, domain: Option<&SetEx>, ) -> Result<SetEx, SymplexError>
The real solutions of d self / d var = 0 in domain (default ℝ)
— SymPy’s stationary_points.
Periodic families of critical points (sin, cos, tan) are
enumerated on a bounded domain and returned as a condition set
{x | f'(x) = 0} on an unbounded one; see
singularities. An expression that does not
depend on var has derivative 0, so every point of the domain is
stationary and the domain itself is returned (as SymPy does).
The sign(h) factors that differentiating |h| introduces are
resolved by cases: on each region where every h has a fixed sign
the derivative is a plain expression whose zeros are found and kept
where the assumed signs hold (|x − 1| + x² → {1/2}). A region
on which the derivative vanishes identically is returned whole
(|x| + x on [−1, 2] → [−1, 0)), and a kink at which the
derivative evaluates to zero counts as stationary (|x| → {0},
since sign(0) = 0) — all as SymPy does. At most four distinct
sign factors are resolved.
§Errors
InvalidArgument—varis not a symbol.ComputationFailed— the derivative is a formalDerivative, the zeros of the derivative cannot be found exactly, or more than foursignfactors would have to be resolved.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
let f = &x.powi(3) - &x * 3;
// SymPy: stationary_points(x**3 - 3*x, x) == {-1, 1}
assert_eq!(f.stationary_points(&x, None).unwrap().to_string(), "{-1, 1}");
// SymPy: stationary_points(x**3 - 3*x, x, Interval(0, 5)) == {1}
let dom = ctx.interval(&ctx.int(0), &ctx.int(5), false, false);
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), false, false);
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), false, false);
assert_eq!(g.stationary_points(&x, Some(&dom)).unwrap().to_string(), "{1/2}");Sourcepub fn maximum(&self, var: &Ex, domain: &SetEx) -> Result<Ex, SymplexError>
pub fn maximum(&self, var: &Ex, domain: &SetEx) -> Result<Ex, SymplexError>
Supremum of self (continuous in var) over domain, a union of
intervals — SymPy’s maximum.
The candidates are the values at the stationary points and abs
kinks inside the domain (see stationary_points
for how |h| is handled: maximum(|x|, [−1, 2]) = 2,
minimum(|x − 1| + x², [−1, 2]) = 3/4), at closed endpoints, and
the one-sided limits at open or infinite endpoints; +∞ / −∞ are
legitimate results. Candidates are compared exactly (see the module
notes for the numeric fallbacks). The supremum need not be
attained: maximum(x, (0, 1)) = 1.
§Errors
InvalidArgument—varis not a symbol, ordomainis empty or not a union of intervals.ComputationFailed—selfhas singularities inside the domain, contains a discontinuous or opaque node (floor,sign,Piecewise, an unknown function, …), its stationary points cannot be enumerated, an endpoint limit cannot be computed, or two candidates cannot be compared.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
let f = &x.powi(3) - &x * 3;
let dom = ctx.interval(&ctx.int(-2), &ctx.int(2), false, false);
// 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(), false, true);
assert_eq!((1 / &x).maximum(&x, &tail).unwrap().to_string(), "1");Sourcepub fn minimum(&self, var: &Ex, domain: &SetEx) -> Result<Ex, SymplexError>
pub fn minimum(&self, var: &Ex, domain: &SetEx) -> Result<Ex, SymplexError>
Infimum of self (continuous in var) over domain — SymPy’s
minimum. Same method, candidates and errors as
maximum.
§Errors
See maximum.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
let f = &x.powi(3) - &x * 3;
let dom = ctx.interval(&ctx.int(-2), &ctx.int(2), false, false);
// 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(), false, true);
assert_eq!((1 / &x).minimum(&x, &tail).unwrap().to_string(), "0");
// SymPy: minimum(x**2, x, S.Reals) == 0
assert_eq!(x.powi(2).minimum(&x, &ctx.reals()).unwrap().to_string(), "0");Sourcepub fn is_increasing(&self, var: &Ex, domain: &SetEx) -> Option<bool>
pub fn is_increasing(&self, var: &Ex, domain: &SetEx) -> Option<bool>
Is self non-decreasing in var on domain (f' ≥ 0 there)?
SymPy’s is_increasing.
Three-valued. Polynomial and rational derivatives with rational
coefficients are decided exactly (Sturm sequences on each interval of
the domain, the denominator having constant sign there); otherwise
the assumption system and the inequality solver
(solve_ge) are consulted. A derivative that is
undefined at a closed endpoint (√x at 0) is tested on the
interior instead, provided the function is continuous there.
None means undecided — never a guess; expressions with
discontinuous or opaque nodes (floor, sign, unknown functions)
are always None. The domain must be a union of intervals (None
otherwise); the empty domain is vacuously Some(true).
A pole strictly inside the domain refutes monotonicity regardless of
the sign of f' on either side: 1/x is not decreasing on
[−1, 1] (f(−1) = −1 < 1 = f(1)) and tan x is not increasing
on [0, π], although f' < 0 resp. f' > 0 wherever it is
defined. (SymPy tests the derivative alone and answers True for
is_increasing(tan(x), Interval(0, pi)).) When the singularities
inside the domain cannot be enumerated (tan x on ℝ) the answer is
None.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
let reals = ctx.reals();
// SymPy: is_increasing(x**3, S.Reals, x) is True
assert_eq!(x.powi(3).is_increasing(&x, &reals), Some(true));
// SymPy: is_increasing(x**2, S.Reals, x) is False
assert_eq!(x.powi(2).is_increasing(&x, &reals), Some(false));
// SymPy: is_increasing(x**2, Interval(0, oo), x) is True
let half = ctx.interval(&ctx.int(0), &ctx.infinity(), false, true);
assert_eq!(x.powi(2).is_increasing(&x, &half), Some(true));
// SymPy: is_increasing(exp(x), S.Reals, x) is True
assert_eq!(x.exp().is_increasing(&x, &reals), Some(true));Sourcepub fn is_decreasing(&self, var: &Ex, domain: &SetEx) -> Option<bool>
pub fn is_decreasing(&self, var: &Ex, domain: &SetEx) -> Option<bool>
Is self non-increasing in var on domain (f' ≤ 0 there)?
SymPy’s is_decreasing. Same method as
is_increasing.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
// SymPy: is_decreasing(x**2, Interval(-oo, 0), x) is True
let left = ctx.interval(&ctx.neg_infinity(), &ctx.int(0), true, false);
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(), true, true);
assert_eq!((1 / &x).is_decreasing(&x, &pos), Some(true));
assert_eq!(x.powi(3).is_decreasing(&x, &ctx.reals()), Some(false));Sourcepub fn is_strictly_increasing(&self, var: &Ex, domain: &SetEx) -> Option<bool>
pub fn is_strictly_increasing(&self, var: &Ex, domain: &SetEx) -> Option<bool>
Is self strictly increasing in var on domain? SymPy’s
is_strictly_increasing.
Decided as f' ≥ 0 with only isolated zeros: for polynomial and
rational derivatives this is exact (x³ is strictly increasing on
ℝ although f'(0) = 0, and so is x² on [0, ∞)); otherwise
Some(true) needs f' > 0 on the domain or a finite zero set from
the inequality solver, Some(false) needs f' < 0 somewhere, and
anything else is None. (SymPy tests domain ⊆ {f' > 0} and
answers None / False for x³ on ℝ; the mathematically correct
answer is returned here.)
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
assert_eq!(x.powi(3).is_strictly_increasing(&x, &ctx.reals()), Some(true));
assert_eq!(x.powi(2).is_strictly_increasing(&x, &ctx.reals()), Some(false));
// A constant is increasing but not strictly.
assert_eq!(ctx.int(3).is_increasing(&x, &ctx.reals()), Some(true));
assert_eq!(ctx.int(3).is_strictly_increasing(&x, &ctx.reals()), Some(false));Sourcepub fn is_strictly_decreasing(&self, var: &Ex, domain: &SetEx) -> Option<bool>
pub fn is_strictly_decreasing(&self, var: &Ex, domain: &SetEx) -> Option<bool>
Is self strictly decreasing in var on domain? SymPy’s
is_strictly_decreasing; see
is_strictly_increasing.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
assert_eq!((-&x.powi(3)).is_strictly_decreasing(&x, &ctx.reals()), Some(true));
let pos = ctx.interval(&ctx.int(0), &ctx.infinity(), true, true);
assert_eq!((1 / &x).is_strictly_decreasing(&x, &pos), Some(true));Sourcepub fn is_monotonic(&self, var: &Ex, domain: &SetEx) -> Option<bool>
pub fn is_monotonic(&self, var: &Ex, domain: &SetEx) -> Option<bool>
Is self monotonic (non-decreasing or non-increasing) in var on
domain? SymPy’s is_monotonic.
The three-valued disjunction of is_increasing
and is_decreasing: Some(true) when either
is proven, Some(false) when both are refuted, None otherwise.
(SymPy’s is_monotonic instead asks whether f' has no zeros in
the domain and therefore answers False for x³ on ℝ.)
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
assert_eq!(x.powi(3).is_monotonic(&x, &ctx.reals()), Some(true));
assert_eq!((-&x).is_monotonic(&x, &ctx.reals()), Some(true));
assert_eq!(x.powi(2).is_monotonic(&x, &ctx.reals()), Some(false));Sourcepub fn is_convex(&self, var: &Ex, domain: &SetEx) -> Option<bool>
pub fn is_convex(&self, var: &Ex, domain: &SetEx) -> Option<bool>
Is self convex in var on domain (f'' ≥ 0 there)? SymPy’s
is_convex for one variable.
Same machinery as is_increasing, applied to
the first derivative. Three-valued.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
// SymPy: is_convex(x**2, x) is True, is_convex(x**3, x) is False
assert_eq!(x.powi(2).is_convex(&x, &ctx.reals()), Some(true));
assert_eq!(x.powi(3).is_convex(&x, &ctx.reals()), Some(false));
// SymPy: is_convex(x**3, x, domain=Interval(0, oo)) is True
let half = ctx.interval(&ctx.int(0), &ctx.infinity(), false, true);
assert_eq!(x.powi(3).is_convex(&x, &half), Some(true));
assert_eq!(x.exp().is_convex(&x, &ctx.reals()), Some(true));Sourcepub fn periodicity(&self, var: &Ex) -> Option<Ex>
pub fn periodicity(&self, var: &Ex) -> Option<Ex>
A period of self in var — SymPy’s periodicity. Like
SymPy’s, the value is a period, not necessarily the fundamental
one: composite expressions get the lcm of the periods of their
pieces, and identities that shorten the period are not detected
(sin²x·cos²x = sin²(2x)/4 gives π, whose fundamental period is
π/2 — SymPy answers π/2 here through its own simplification).
Some(0)whenselfdoes not depend onvar.sin(a·x + b),cos(a·x + b)→2π/|a|;tan(a·x + b)→π/|a|;sec,csc,cot(which are built fromsin/cos) follow, with productssin(g)ᵖ·cos(g)ᵠof even exponent sum (sin·cos,cos/sin,sin²) and|sin g|,|cos g|getting the half periodπ/|a|.- Sums, products, powers and compositions (
exp(sin x),sin(2x) + cos(3x)) take the lcm of the periods of theirvar-dependent parts; the lcm needs pairwise rational ratios. Nonewhen avar-dependent part is not recognised as periodic (x²,sin(x²),sin(x) + x,sin(√2·x) + sin(x)).
The expression is simplified first (sin²x + cos²x → 1 →
Some(0)); the original form is tried if the simplified one is not
recognised. Note that SymPy reports 2π for sin(x)²; the
half-period rule gives π here.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
let p = |e: &Ex| e.periodicity(&x).map(|p| p.to_string());
// SymPy: periodicity(sin(2*x) + cos(3*x), x) == 2*pi
assert_eq!(p(&(&(&x * 2).sin() + &(&x * 3).cos())), Some("2*pi".into()));
// SymPy: periodicity(tan(x), x) == pi
assert_eq!(p(&x.tan()), Some("pi".into()));
// SymPy: periodicity(sin(3*x + 1), x) == 2*pi/3
assert_eq!(p(&(&x * 3 + 1).sin()), Some("2/3*pi".into()));
// SymPy: periodicity(S(3), x) == 0; periodicity(x**2, x) is None
assert_eq!(p(&ctx.int(3)), Some("0".into()));
assert_eq!(p(&x.powi(2)), None);Sourcepub fn function_range(
&self,
var: &Ex,
domain: &SetEx,
) -> Result<SetEx, SymplexError>
pub fn function_range( &self, var: &Ex, domain: &SetEx, ) -> Result<SetEx, SymplexError>
The image of self (continuous in var) over domain, a union of
intervals — SymPy’s function_range.
On each interval of the domain the infimum and supremum are found
as in minimum / maximum; the image
of that interval is [inf, sup] with an endpoint open exactly when
the value is only approached (a one-sided limit at an open or
infinite endpoint that is not also attained elsewhere) or infinite.
The pieces are united and simplified.
§Errors
InvalidArgument—varis not a symbol, ordomainis not a union of intervals (the empty domain gives the empty set).ComputationFailed— as formaximum.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
let r = |f: &Ex, d: &SetEx| f.function_range(&x, d).unwrap().to_string();
// SymPy: function_range(sin(x), x, Interval(0, pi)) == Interval(0, 1)
assert_eq!(r(&x.sin(), &ctx.interval(&ctx.int(0), &ctx.pi(), false, false)), "[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(), false, true);
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 Ex
impl Ex
Sourcepub fn as_rational(&self) -> Option<Ratio<BigInt>>
pub fn as_rational(&self) -> Option<Ratio<BigInt>>
The exact value if this expression is a numeric literal.
Returns None for anything that is not a plain number node (symbols,
pi, sqrt(2), unevaluated sums, …) — call
eval first if you want constant folding.
use symplex::prelude::*;
use num_bigint::BigInt;
use num_rational::Ratio;
let ctx = Context::new();
let r = ctx.rational(6, 4).as_rational().unwrap();
assert_eq!(r, Ratio::new(BigInt::from(3), BigInt::from(2)));
assert!(ctx.pi().as_rational().is_none());
assert!((&ctx.int(2).sqrt() * &ctx.int(2).sqrt()).eval().as_rational().is_some());Sourcepub fn as_ratio_parts(&self) -> Option<(BigInt, BigInt)>
pub fn as_ratio_parts(&self) -> Option<(BigInt, BigInt)>
Numerator and denominator (lowest terms, denominator positive) if this
expression is a rational literal — SymPy’s Rational.p / .q.
Unlike as_numer_denom, which decomposes
any expression symbolically, this returns plain integers and only
for numbers. Call eval first to fold constant
arithmetic such as 1/3 + 1/6.
use symplex::prelude::*;
use symplex::num_bigint::BigInt;
let ctx = Context::new();
let (p, q) = ctx.rational(6, -4).as_ratio_parts().unwrap();
assert_eq!((p, q), (BigInt::from(-3), BigInt::from(2)));
assert_eq!(ctx.int(7).as_ratio_parts(), Some((BigInt::from(7), BigInt::from(1))));
assert!(ctx.symbol("x").as_ratio_parts().is_none());Sourcepub fn as_ratio_i128(&self) -> Option<(i128, i128)>
pub fn as_ratio_i128(&self) -> Option<(i128, i128)>
Numerator and denominator as machine integers, if this expression is a
rational literal whose parts fit in i128.
The convenient form for comparing with literals or feeding other
exact-arithmetic code without touching BigInt:
use symplex::prelude::*;
let ctx = Context::new();
assert_eq!(ctx.rational(3, 31).as_ratio_i128(), Some((3, 31)));
assert_eq!((ctx.rational(1, 3) + ctx.rational(1, 6)).as_ratio_i128(), Some((1, 2)));
assert_eq!(ctx.int(-4).as_ratio_i128(), Some((-4, 1)));
// Too large for i128 → None (use `as_ratio_parts`).
assert!(ctx.from_bigint(symplex::num_bigint::BigInt::from(2).pow(200)).as_ratio_i128().is_none());Sourcepub fn as_bigint(&self) -> Option<BigInt>
pub fn as_bigint(&self) -> Option<BigInt>
The exact value if this expression is an integer literal.
use symplex::prelude::*;
use num_bigint::BigInt;
let ctx = Context::new();
assert_eq!(ctx.int(-7).as_bigint(), Some(BigInt::from(-7)));
assert_eq!(ctx.rational(1, 2).as_bigint(), None);Sourcepub fn as_i64(&self) -> Option<i64>
pub fn as_i64(&self) -> Option<i64>
The value if this expression is an integer literal that fits in i64.
use symplex::prelude::*;
let ctx = Context::new();
assert_eq!(ctx.int(42).as_i64(), Some(42));
assert_eq!(ctx.from_u64(u64::MAX).as_i64(), None);
assert_eq!(ctx.symbol("n").as_i64(), None);Sourcepub fn compare_numeric(&self, other: &Ex) -> Option<Ordering>
pub fn compare_numeric(&self, other: &Ex) -> Option<Ordering>
Three-valued numeric comparison of self and other.
Decision procedure, in order:
- Both are numeric literals → exact rational comparison.
d = (self − other).eval()is a literal → exact sign ofd; ifdisoo/-oo→Greater/Less.- The assumption system knows the sign of
d(e.g.a − bwithapositive andbnegative;x² + 1for realx). - Both are constants (no free symbols) → 16-digit numeric
evaluation; decided only if the values differ by more than
1e-9relative and both are real. - Otherwise
None.
§Examples
use std::cmp::Ordering;
use symplex::prelude::*;
let ctx = Context::new();
assert_eq!(ctx.rational(1, 3).compare_numeric(&ctx.rational(1, 2)), Some(Ordering::Less));
assert_eq!(ctx.pi().compare_numeric(&ctx.int(3)), Some(Ordering::Greater));
assert_eq!(ctx.int(2).sqrt().compare_numeric(&ctx.rational(3, 2)), Some(Ordering::Less));
let x = ctx.symbol("x");
assert_eq!((&x + 1).compare_numeric(&x), Some(Ordering::Greater));
assert_eq!(x.compare_numeric(&ctx.int(0)), None);
let p = ctx.symbol_with("p", &[Assumption::Positive]);
assert_eq!(p.compare_numeric(&ctx.int(0)), Some(Ordering::Greater));Sourcepub fn is_less_than(&self, other: &Ex) -> Option<bool>
pub fn is_less_than(&self, other: &Ex) -> Option<bool>
Is self < other? Three-valued; see
compare_numeric for the decision procedure.
use symplex::prelude::*;
let ctx = Context::new();
assert_eq!(ctx.int(1).is_less_than(&ctx.int(2)), Some(true));
assert_eq!(ctx.pi().is_less_than(&ctx.int(3)), Some(false));
let x = ctx.symbol("x");
assert_eq!(x.is_less_than(&ctx.int(3)), None);
// Assumptions help: x² ≥ 0 for real x, so x² < -1 is false.
let r = ctx.symbol_with("r", &[Assumption::Real]);
assert_eq!(r.powi(2).is_less_than(&ctx.int(-1)), Some(false));Sourcepub fn is_greater_than(&self, other: &Ex) -> Option<bool>
pub fn is_greater_than(&self, other: &Ex) -> Option<bool>
Is self > other? Three-valued; see
compare_numeric.
use symplex::prelude::*;
let ctx = Context::new();
assert_eq!(ctx.e().is_greater_than(&ctx.int(2)), Some(true));
assert_eq!(ctx.int(2).is_greater_than(&ctx.int(2)), Some(false));
assert_eq!(ctx.symbol("x").is_greater_than(&ctx.int(0)), None);Sourcepub fn probably_equal(&self, other: &Ex, samples: usize) -> Option<bool>
pub fn probably_equal(&self, other: &Ex, samples: usize) -> Option<bool>
Randomized equality test: evaluate both sides at samples random
rational points and compare.
Some(false)— a concrete point was found where the two sides differ. When both sides fold to exact rationals at that point this is a proof; when transcendental functions force floating-point evaluation the sides differ by more than1e-9relative.Some(true)—equalsproved it symbolically, or every sample agreed. The latter is probabilistic: for polynomial and rational identities the chance of a false positive is negligible after a few samples, but no proof is produced.None— no sample point could be evaluated (domain errors at every point) and the symbolic test was inconclusive.
Sample points are drawn from a fixed-seed generator keyed on the two
expressions, so results are reproducible. samples == 0 is treated
as 1.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
let lhs = (&x + 1).powi(3);
let rhs = &x.powi(3) + &x.powi(2) * 3 + &x * 3 + 1;
assert_eq!(lhs.probably_equal(&rhs, 5), Some(true));
assert_eq!(x.probably_equal(&ctx.symbol("y"), 5), Some(false));
assert_eq!(x.sin().probably_equal(&x.cos(), 5), Some(false));Sourcepub fn eval_at(&self, pairs: &[(&Ex, &Ex)]) -> Ex
pub fn eval_at(&self, pairs: &[(&Ex, &Ex)]) -> Ex
Substitute (symbol, value) pairs simultaneously and evaluate.
Shorthand for self.subs_map(pairs).eval(). The result is exact
and may still be symbolic if not every symbol was bound.
use symplex::prelude::*;
let ctx = Context::new();
let (x, y) = (ctx.symbol("x"), ctx.symbol("y"));
let f = &x.powi(2) + &y;
assert_eq!(format!("{}", f.eval_at(&[(&x, &ctx.int(3)), (&y, &ctx.rational(1, 2))])), "19/2");
assert_eq!(format!("{}", f.eval_at(&[(&x, &ctx.pi())])), "y + pi^2");Source§impl Ex
impl Ex
Sourcepub fn solve_general(&self, var: &Ex) -> Result<GeneralSolution, SymplexError>
pub fn solve_general(&self, var: &Ex) -> Result<GeneralSolution, SymplexError>
Solve self = 0 for var, returning general solution families.
Unlike solve, which returns only principal branches,
this expresses periodic solutions with a fresh integer parameter
(n, or n1, n2, … if n is already in use), exposed through
GeneralSolution::parameters:
sin(x) = c→asin(c) + 2πn,π − asin(c) + 2πncos(x) = c→±acos(c) + 2πntan(x) = c→atan(c) + πn
Linear arguments (sin(a·x + b) = c) and change-of-variable forms
(sin²x − sin x = 0) are supported. Non-periodic equations return
the same solutions as solve with an empty parameter list.
§Errors
Same as solve: InfiniteSolutions for identities,
NoSolution for contradictions, ComputationFailed when nothing
applies.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
let eq = &x.sin() - &ctx.rational(1, 2);
let fam = eq.solve_general(&x).unwrap();
assert_eq!(fam.solutions.len(), 2);
assert_eq!(fam.parameters.len(), 1);
// Every member of every family satisfies the equation.
for k in -2..=2 {
for s in fam.instance(k) {
let residual = eq.subs(&x, &s).eval_f64().unwrap();
assert!(residual.abs() < 1e-12);
}
}Source§impl Ex
impl Ex
Sourcepub fn solve_ode_ivp(
&self,
func: &Ex,
var: &Ex,
ics: &[(usize, Ex, Ex)],
) -> Result<Ex, SymplexError>
pub fn solve_ode_ivp( &self, func: &Ex, var: &Ex, ics: &[(usize, Ex, Ex)], ) -> Result<Ex, SymplexError>
Solve the ODE self = 0 for func(var) subject to initial
conditions.
Each initial condition is (k, x0, value) meaning
d^k func / d var^k (x0) = value (k = 0 is func(x0) = value).
The general solution is found with solve_ode,
then the integration constants C1, C2, … are determined by
substituting the conditions and solving the resulting (usually
linear) system with linsolve; nonlinear constant equations are
handled one at a time with solve. Constants not
pinned down by the conditions remain in the result.
§Errors
SymplexError::ComputationFailedif the ODE cannot be solved or the constants cannot be determined.SymplexError::NoSolutionif the initial conditions are contradictory.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let (x, y) = (ctx.symbol("x"), ctx.symbol("y"));
// y'' + y = 0, y(0) = 0, y'(0) = 1 → y = sin(x)
let ode = &y.formal_diff(&x).formal_diff(&x) + &y;
let sol = ode
.solve_ode_ivp(&y, &x, &[(0, ctx.int(0), ctx.int(0)), (1, ctx.int(0), ctx.int(1))])
.unwrap();
assert_eq!(format!("{}", sol.simplify()), "sin(x)");Source§impl Ex
impl Ex
Sourcepub fn solve_riccati(
&self,
func: &Ex,
var: &Ex,
particular: &Ex,
) -> Result<Ex, SymplexError>
pub fn solve_riccati( &self, func: &Ex, var: &Ex, particular: &Ex, ) -> Result<Ex, SymplexError>
Solve the Riccati equation self = 0, i.e.
y' = q₀(x) + q₁(x)·y + q₂(x)·y², given a known particular
solution particular.
The substitution y = y_p + 1/v reduces the equation to the linear
ODE v' + (q₁ + 2·q₂·y_p)·v = −q₂; the result is y_p + 1/v with the
integration constant C1.
§Errors
SymplexError::InvalidArgumentifselfis not a Riccati equation infuncorparticulardoes not satisfy it.SymplexError::ComputationFailedif the linear equation forvcannot be solved in closed form.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let (x, y) = (ctx.symbol("x"), ctx.symbol("y"));
// y' = y² - 2/x² has the particular solution y = 1/x
let ode = &y.formal_diff(&x) - &y.powi(2) + &(&ctx.int(2) / &x.powi(2));
let sol = ode.solve_riccati(&y, &x, &(&ctx.int(1) / &x)).unwrap();
assert!(sol.contains(&ctx.symbol("C1")));
assert!(ode.check_ode_solution(&sol, &y, &x));Source§impl Ex
impl Ex
Sourcepub fn as_poly(&self, gens: &[&Ex]) -> Option<Poly>
pub fn as_poly(&self, gens: &[&Ex]) -> Option<Poly>
View this expression as a Poly in gens; see Poly::new.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let (x, a) = (ctx.symbol("x"), ctx.symbol("a"));
let p = (&a * &x.powi(2) + 1).as_poly(&[&x]).unwrap();
assert_eq!(p.degree_in(&x), Some(2));
assert_eq!(p.leading_coeff(), a);
assert!(x.sin().as_poly(&[&x]).is_none());Source§impl Ex
impl Ex
Sourcepub fn z_transform(&self, n: &Ex, z: &Ex) -> Result<Ex, SymplexError>
pub fn z_transform(&self, n: &Ex, z: &Ex) -> Result<Ex, SymplexError>
Unilateral Z-transform X(z) = Σ_{n≥0} x[n] z^{−n} of this sequence
(a function of the integer index n).
Table: constants, aⁿ, nᵏ aⁿ (via Z{n x[n]} = −z X′(z)),
sin(ωn), cos(ωn), aⁿ sin(ωn), aⁿ cos(ωn), H(n − k),
δ[n − k], C(n, k), 1/n!; rules: linearity, scaling
aⁿ x[n] → X(z/a), delay x[n − k] H(n − k) → z^{−k} X(z).
§Errors
ComputationFailed if n/z are not symbols or no rule applies.
There is no unevaluated Z-transform node, so this API is
Result-only.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let n = ctx.symbol("n");
let z = ctx.symbol("z");
let half = ctx.rational(1, 2);
// Z{(1/2)^n} = z/(z − 1/2)
let result = half.pow(&n).z_transform(&n, &z).unwrap();
assert_eq!(result, &z / (&z - half));
// n² → z(z + 1)/(z − 1)³
let x = n.powi(2).z_transform(&n, &z).unwrap();
let expected = &z * (&z + 1) / (&z - 1).powi(3);
assert!((&x - &expected).simplify().is_zero_structural(), "{x}");
// δ[n − 3] → z⁻³
assert_eq!((&n - 3).dirac_delta().z_transform(&n, &z).unwrap(), z.powi(-3));Sourcepub fn inverse_z_transform(&self, z: &Ex, n: &Ex) -> Result<Ex, SymplexError>
pub fn inverse_z_transform(&self, z: &Ex, n: &Ex) -> Result<Ex, SymplexError>
Inverse (unilateral) Z-transform of this expression (a function of
z) as a sequence in n.
Rational X(z) is handled through partial fractions in z
(z/(z − a)ᵐ → C(n, m−1) a^{n−m+1}, 1/(z − a)ᵐ through the delay
rule), together with constants (δ[n]), z^{−k} (δ[n − k]),
z^{−k} X(z) (x[n−k] H(n−k)), e^{1/z} (1/n!) and the
trigonometric forms.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let n = ctx.symbol("n");
let z = ctx.symbol("z");
// Z⁻¹{z/(z−2)} = 2ⁿ
let xz = &z / &(&z - 2);
assert_eq!(format!("{}", xz.inverse_z_transform(&z, &n).unwrap()), "2^n");
// Z⁻¹{z⁻²} = δ[n − 2]
let d = (1 / z.powi(2)).inverse_z_transform(&z, &n).unwrap();
assert_eq!(format!("{d}"), "KroneckerDelta(2, n)");Source§impl Ex
impl Ex
Sourcepub fn prove_nonnegative_on_box(
&self,
bounds: &[(Ex, Ex, Ex)],
degree: u32,
) -> Result<BoxOutcome, SymplexError>
pub fn prove_nonnegative_on_box( &self, bounds: &[(Ex, Ex, Ex)], degree: u32, ) -> Result<BoxOutcome, SymplexError>
prove_nonnegative_on_box as a method.
Source§impl Ex
impl Ex
Sourcepub fn find_root_bracket(
&self,
var: &Ex,
a: f64,
b: f64,
) -> Result<f64, SymplexError>
pub fn find_root_bracket( &self, var: &Ex, a: f64, b: f64, ) -> Result<f64, SymplexError>
Numerically find a root of this expression in var inside the
bracket [a, b] by brent_root with default RootOpts.
The expression is compiled with compile first, so
evaluation is fast and the usual compile-time checks apply.
§Errors
SymplexError::InvalidArgumentifvaris not a symbol, or the bracket is invalid (non-finite, or no sign change).SymplexError::FreeSymbolif the expression contains a symbol other thanvar.SymplexError::NotImplementedif the expression cannot be compiled tof64arithmetic.SymplexError::ComputationFailedif the iteration does not converge or meets a non-finite value.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
let r = (&x.powi(2) - 2).find_root_bracket(&x, 0.0, 2.0).unwrap();
assert!((r - 2f64.sqrt()).abs() < 1e-12);
// A transcendental equation: cos x = x.
let r = (x.cos() - &x).find_root_bracket(&x, 0.0, 1.0).unwrap();
assert!((r - 0.739_085_133_215_160_6).abs() < 1e-12);
// Another free symbol → FreeSymbol, not a silent NaN.
let a = ctx.symbol("a");
assert!(matches!(
(&x.powi(2) - &a).find_root_bracket(&x, 0.0, 2.0),
Err(SymplexError::FreeSymbol { .. })
));Sourcepub fn find_root_bracket_with(
&self,
var: &Ex,
a: f64,
b: f64,
opts: &RootOpts,
) -> Result<f64, SymplexError>
pub fn find_root_bracket_with( &self, var: &Ex, a: f64, b: f64, opts: &RootOpts, ) -> Result<f64, SymplexError>
find_root_bracket with explicit
RootOpts.
§Examples
use symplex::optimize::RootOpts;
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
let opts = RootOpts { xtol: 1e-6, ..RootOpts::default() };
let r = (x.exp() - 3).find_root_bracket_with(&x, 0.0, 2.0, &opts).unwrap();
assert!((r - 3f64.ln()).abs() < 1e-6);Sourcepub fn minimize_numeric(
&self,
vars: &[&Ex],
x0: &[f64],
) -> Result<MinimizeResult, SymplexError>
pub fn minimize_numeric( &self, vars: &[&Ex], x0: &[f64], ) -> Result<MinimizeResult, SymplexError>
Minimise this expression numerically over vars from the starting
point x0 by nelder_mead with default MinimizeOpts.
x0[i] is the initial value of vars[i].
§Errors
SymplexError::InvalidArgumentifvarsis empty, a variable is not a symbol, orx0.len() != vars.len().SymplexError::FreeSymbolif the expression contains a symbol not listed invars.SymplexError::NotImplementedif the expression cannot be compiled tof64arithmetic.
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let (x, y) = (ctx.symbol("x"), ctx.symbol("y"));
let bowl = (&x - 1).powi(2) + (&y + 2).powi(2);
let r = bowl.minimize_numeric(&[&x, &y], &[0.0, 0.0]).unwrap();
assert!(r.converged);
assert!((r.x[0] - 1.0).abs() < 1e-6 && (r.x[1] + 2.0).abs() < 1e-6);
assert!(r.fun < 1e-12);Sourcepub fn minimize_numeric_with(
&self,
vars: &[&Ex],
x0: &[f64],
opts: &MinimizeOpts,
) -> Result<MinimizeResult, SymplexError>
pub fn minimize_numeric_with( &self, vars: &[&Ex], x0: &[f64], opts: &MinimizeOpts, ) -> Result<MinimizeResult, SymplexError>
minimize_numeric with explicit
MinimizeOpts.
§Examples
use symplex::optimize::MinimizeOpts;
use symplex::prelude::*;
let ctx = Context::new();
let (x, y) = (ctx.symbol("x"), ctx.symbol("y"));
let rosen = (1 - &x).powi(2) + 100 * (&y - &x.powi(2)).powi(2);
let opts = MinimizeOpts { max_iter: 2000, ..MinimizeOpts::default() };
let r = rosen.minimize_numeric_with(&[&x, &y], &[-1.2, 1.0], &opts).unwrap();
assert!((r.x[0] - 1.0).abs() < 1e-4 && (r.x[1] - 1.0).abs() < 1e-4);Sourcepub fn minimize_scalar_numeric(
&self,
var: &Ex,
a: f64,
b: f64,
) -> Result<(f64, f64), SymplexError>
pub fn minimize_scalar_numeric( &self, var: &Ex, a: f64, b: f64, ) -> Result<(f64, f64), SymplexError>
Minimise this expression in the single variable var over [a, b]
by Brent’s method (minimize_scalar) with default
MinimizeOpts. Returns (x_min, f_min).
§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 (xm, fm) = (&x * x.ln()).minimize_scalar_numeric(&x, 0.1, 2.0).unwrap();
assert!((xm - (-1.0f64).exp()).abs() < 1e-6);
assert!((fm + (-1.0f64).exp()).abs() < 1e-12);Sourcepub fn minimize_global_numeric(
&self,
vars: &[&Ex],
bounds: &[(f64, f64)],
opts: &DeOpts,
) -> Result<MinimizeResult, SymplexError>
pub fn minimize_global_numeric( &self, vars: &[&Ex], bounds: &[(f64, f64)], opts: &DeOpts, ) -> Result<MinimizeResult, SymplexError>
Globally minimise this expression over the box bounds (one
(lo, hi) pair 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 r = h.minimize_global_numeric(&[&x, &y], &[(-5.0, 5.0), (-5.0, 5.0)], &DeOpts::default()).unwrap();
assert!(r.fun < 1e-8, "f = {}", r.fun);Sourcepub fn poly_fit_points(
ctx: &Context,
points: &[(Ex, Ex)],
var: &Ex,
degree: usize,
) -> Result<Ex, SymplexError>
pub fn poly_fit_points( ctx: &Context, points: &[(Ex, Ex)], var: &Ex, degree: usize, ) -> Result<Ex, SymplexError>
Exact least-squares polynomial of degree degree in var through
the rational points (x, y).
Each coordinate is constant-folded with eval and
must then be a rational literal (ctx.int, ctx.rational,
sqrt(4), …). The fit is computed by poly_fit_exact, so the
result is the exact least-squares polynomial — the interpolating
polynomial when degree + 1 == points.len() or the data are
consistent.
§Errors
SymplexError::InvalidArgumentif a coordinate is not a rational literal after evaluation, ordegree >= points.len().SymplexError::ComputationFailedif the normal equations are singular (fewer thandegree + 1distinct abscissae).
§Panics
Panics if var or a point belongs to a different context than
ctx (the standard cross-context guard).
§Examples
use symplex::prelude::*;
let ctx = Context::new();
let x = ctx.symbol("x");
// Five samples of x²/3 − x/2 + 1/7.
let pts = [
(ctx.int(0), ctx.rational(1, 7)),
(ctx.int(1), ctx.rational(-1, 42)),
(ctx.int(2), ctx.rational(10, 21)),
(ctx.int(3), ctx.rational(23, 14)),
(ctx.int(4), ctx.rational(73, 21)),
];
let p = Ex::poly_fit_points(&ctx, &pts, &x, 2).unwrap();
let expected = &x.powi(2) * ctx.rational(1, 3) - &x * ctx.rational(1, 2) + ctx.rational(1, 7);
assert!((&p - &expected).expand().is_zero_structural(), "{p}");
// A symbolic coordinate is rejected.
let a = ctx.symbol("a");
assert!(Ex::poly_fit_points(&ctx, &[(ctx.int(0), a), (ctx.int(1), ctx.int(1))], &x, 1).is_err());Trait Implementations§
Source§impl AddAssign for Ex
impl AddAssign for Ex
Source§fn add_assign(&mut self, rhs: Ex)
fn add_assign(&mut self, rhs: Ex)
+= operation. Read moreSource§impl AddAssign<&Expr<Numeric>> for Ex
impl AddAssign<&Expr<Numeric>> for Ex
Source§fn add_assign(&mut self, rhs: &Ex)
fn add_assign(&mut self, rhs: &Ex)
+= operation. Read moreSource§impl<T: Scalar> AddAssign<T> for Ex
impl<T: Scalar> AddAssign<T> for Ex
Source§fn add_assign(&mut self, rhs: T)
fn add_assign(&mut self, rhs: T)
+= operation. Read moreSource§impl DivAssign for Ex
impl DivAssign for Ex
Source§fn div_assign(&mut self, rhs: Ex)
fn div_assign(&mut self, rhs: Ex)
/= operation. Read moreSource§impl DivAssign<&Expr<Numeric>> for Ex
impl DivAssign<&Expr<Numeric>> for Ex
Source§fn div_assign(&mut self, rhs: &Ex)
fn div_assign(&mut self, rhs: &Ex)
/= operation. Read moreSource§impl<T: Scalar> DivAssign<T> for Ex
impl<T: Scalar> DivAssign<T> for Ex
Source§fn div_assign(&mut self, rhs: T)
fn div_assign(&mut self, rhs: T)
/= operation. Read moreSource§impl Mul<&Quaternion> for &Ex
impl Mul<&Quaternion> for &Ex
Source§type Output = Quaternion
type Output = Quaternion
* operator.Source§fn mul(self, rhs: &Quaternion) -> Quaternion
fn mul(self, rhs: &Quaternion) -> Quaternion
* operation. Read moreSource§impl Mul<&Quaternion> for Ex
impl Mul<&Quaternion> for Ex
Source§type Output = Quaternion
type Output = Quaternion
* operator.Source§fn mul(self, rhs: &Quaternion) -> Quaternion
fn mul(self, rhs: &Quaternion) -> Quaternion
* operation. Read moreSource§impl Mul<Acceleration> for &Ex
impl Mul<Acceleration> for &Ex
Source§type Output = Acceleration
type Output = Acceleration
* operator.Source§fn mul(self, rhs: Acceleration) -> Acceleration
fn mul(self, rhs: Acceleration) -> Acceleration
* operation. Read moreSource§impl Mul<AngularAcceleration> for &Ex
impl Mul<AngularAcceleration> for &Ex
Source§type Output = AngularAcceleration
type Output = AngularAcceleration
* operator.Source§fn mul(self, rhs: AngularAcceleration) -> AngularAcceleration
fn mul(self, rhs: AngularAcceleration) -> AngularAcceleration
* operation. Read moreSource§impl Mul<AngularMomentum> for &Ex
impl Mul<AngularMomentum> for &Ex
Source§type Output = AngularMomentum
type Output = AngularMomentum
* operator.Source§fn mul(self, rhs: AngularMomentum) -> AngularMomentum
fn mul(self, rhs: AngularMomentum) -> AngularMomentum
* operation. Read moreSource§impl Mul<AngularVelocity> for &Ex
impl Mul<AngularVelocity> for &Ex
Source§type Output = AngularVelocity
type Output = AngularVelocity
* operator.Source§fn mul(self, rhs: AngularVelocity) -> AngularVelocity
fn mul(self, rhs: AngularVelocity) -> AngularVelocity
* operation. Read moreSource§impl Mul<Capacitance> for &Ex
impl Mul<Capacitance> for &Ex
Source§type Output = Capacitance
type Output = Capacitance
* operator.Source§fn mul(self, rhs: Capacitance) -> Capacitance
fn mul(self, rhs: Capacitance) -> Capacitance
* operation. Read moreSource§impl Mul<Dimensionless> for &Ex
impl Mul<Dimensionless> for &Ex
Source§type Output = Dimensionless
type Output = Dimensionless
* operator.Source§fn mul(self, rhs: Dimensionless) -> Dimensionless
fn mul(self, rhs: Dimensionless) -> Dimensionless
* operation. Read moreSource§impl Mul<Inductance> for &Ex
impl Mul<Inductance> for &Ex
Source§type Output = Inductance
type Output = Inductance
* operator.Source§fn mul(self, rhs: Inductance) -> Inductance
fn mul(self, rhs: Inductance) -> Inductance
* operation. Read moreSource§impl Mul<MagneticFlux> for &Ex
impl Mul<MagneticFlux> for &Ex
Source§type Output = MagneticFlux
type Output = MagneticFlux
* operator.Source§fn mul(self, rhs: MagneticFlux) -> MagneticFlux
fn mul(self, rhs: MagneticFlux) -> MagneticFlux
* operation. Read moreSource§impl Mul<MomentOfInertia> for &Ex
impl Mul<MomentOfInertia> for &Ex
Source§type Output = MomentOfInertia
type Output = MomentOfInertia
* operator.Source§fn mul(self, rhs: MomentOfInertia) -> MomentOfInertia
fn mul(self, rhs: MomentOfInertia) -> MomentOfInertia
* operation. Read moreSource§impl Mul<Quaternion> for &Ex
impl Mul<Quaternion> for &Ex
Source§type Output = Quaternion
type Output = Quaternion
* operator.Source§fn mul(self, rhs: Quaternion) -> Quaternion
fn mul(self, rhs: Quaternion) -> Quaternion
* operation. Read moreSource§impl Mul<Quaternion> for Ex
impl Mul<Quaternion> for Ex
Source§type Output = Quaternion
type Output = Quaternion
* operator.Source§fn mul(self, rhs: Quaternion) -> Quaternion
fn mul(self, rhs: Quaternion) -> Quaternion
* operation. Read moreSource§impl Mul<Resistance> for &Ex
impl Mul<Resistance> for &Ex
Source§type Output = Resistance
type Output = Resistance
* operator.Source§fn mul(self, rhs: Resistance) -> Resistance
fn mul(self, rhs: Resistance) -> Resistance
* operation. Read moreSource§impl Mul<Temperature> for &Ex
impl Mul<Temperature> for &Ex
Source§type Output = Temperature
type Output = Temperature
* operator.Source§fn mul(self, rhs: Temperature) -> Temperature
fn mul(self, rhs: Temperature) -> Temperature
* operation. Read moreSource§impl MulAssign for Ex
impl MulAssign for Ex
Source§fn mul_assign(&mut self, rhs: Ex)
fn mul_assign(&mut self, rhs: Ex)
*= operation. Read moreSource§impl MulAssign<&Expr<Numeric>> for Ex
impl MulAssign<&Expr<Numeric>> for Ex
Source§fn mul_assign(&mut self, rhs: &Ex)
fn mul_assign(&mut self, rhs: &Ex)
*= operation. Read moreSource§impl<T: Scalar> MulAssign<T> for Ex
impl<T: Scalar> MulAssign<T> for Ex
Source§fn mul_assign(&mut self, rhs: T)
fn mul_assign(&mut self, rhs: T)
*= operation. Read moreSource§impl Product for Ex
Multiply an iterator of expressions.
impl Product for Ex
Multiply an iterator of expressions.
§Panics
Panics on an empty iterator (no context to build 1 in) — see
Context::product and the Option<Ex> implementation — and on
mixed-context input.
Source§impl SubAssign for Ex
impl SubAssign for Ex
Source§fn sub_assign(&mut self, rhs: Ex)
fn sub_assign(&mut self, rhs: Ex)
-= operation. Read moreSource§impl SubAssign<&Expr<Numeric>> for Ex
impl SubAssign<&Expr<Numeric>> for Ex
Source§fn sub_assign(&mut self, rhs: &Ex)
fn sub_assign(&mut self, rhs: &Ex)
-= operation. Read moreSource§impl<T: Scalar> SubAssign<T> for Ex
impl<T: Scalar> SubAssign<T> for Ex
Source§fn sub_assign(&mut self, rhs: T)
fn sub_assign(&mut self, rhs: T)
-= operation. Read moreSource§impl Sum for Ex
Sum an iterator of expressions.
impl Sum for Ex
Sum an iterator of expressions.
§Panics
Panics on an empty iterator: there is no context in which to build
0. Use Context::sum (yields 0 on empty) or collect into
Option<Ex> (yields None on empty) when the iterator may be empty.
Also panics if the expressions come from different contexts.
use symplex::prelude::*;
let ctx = Context::new();
let total: Ex = (1..=4).map(|n| ctx.int(n)).sum();
assert_eq!(format!("{total}"), "10");
let none: Option<Ex> = std::iter::empty::<Ex>().sum();
assert!(none.is_none());