Simplification
The generic way to do nontrivial simplifications of expressions is
calling simplify()
function.
>>> simplify(sin(x)**2 + cos(x)**2)
1
>>> simplify((x**3 + x**2 - x - 1)/(x**2 + 2*x + 1))
x - 1
>>> simplify(gamma(x)/gamma(x - 2))
(x - 2)⋅(x - 1)
There are also more directed simplification functions. These apply
very specific rules to the input expression and are typically able to
make guarantees about the output. For instance, the
factor()
function, given a polynomial
with rational coefficients in several variables, is guaranteed to
produce a factorization into irreducible factors.
The simplify()
function applies
almost all available in Diofant such specific simplification rules in
some heuristics sequence to produce the simplest result.
Tip
The optional measure
keyword argument for
simplify()
lets the user specify
the Python function used to determine how “simple” an expression
is. The default is count_ops()
, which
returns the total number of operations in the expression.
That is why it is usually slow. But more important pitfail is that
sometimes simplify()
doesn’t
“simplify” how you might expect, if, for example, it miss some
transformation or apply it too early or too late. Lets look on an
example
>>> simplify(x**2 + 2*x + 1)
2
x + 2⋅x + 1
>>> factor(_)
2
(x + 1)
Obviously, the factored form is more “simple”, as it has less arithmetic operations.
The function simplify()
is best when
used interactively, when you just want to whittle down an expression
to a simpler form. You may then choose to apply specific functions
once you see what simplify()
returns,
to get a more precise result. It is also useful when you have no idea
what form an expression will take, and you need a catchall function to
simplify it.
Rational Functions
expand()
is one of the most common
simplification functions in Diofant. Although it has a lot of scopes,
for now, we will consider its function in expanding polynomial
expressions.
>>> expand((x + 1)**2)
2
x + 2⋅x + 1
>>> expand((x + 2)*(x - 3))
2
x - x - 6
Given a polynomial, expand()
will put it
into a canonical form of a sum of monomials with help of more directed
expansion methods, namely
expand_multinomial()
and
expand_mul()
.
expand()
may not sound like a
simplification function. After all, by its very name, it makes
expressions bigger, not smaller. Usually this is the case, but often
an expression will become smaller upon calling
expand()
on it due to cancellation.
>>> expand((x + 1)*(x - 2) - (x - 1)*x)
-2
Function factor()
takes a multivariate
polynomial with rational coefficients and factors it into irreducible
factors.
>>> factor(x**3 - x**2 + x - 1)
⎛ 2 ⎞
(x - 1)⋅⎝x + 1⎠
>>> factor(x**2*z + 4*x*y*z + 4*y**2*z)
2
z⋅(x + 2⋅y)
For polynomials, factor()
is the
opposite of expand()
.
Note
The input to factor()
and
expand()
need not be polynomials in
the strict sense. They will intelligently factor or expand any
kind of expression (though, for example, the factors may not be
irreducible if the input is no longer a polynomial over the
rationals).
>>> expand((cos(x) + sin(x))**2)
2 2
sin (x) + 2⋅sin(x)⋅cos(x) + cos (x)
>>> factor(_)
2
(sin(x) + cos(x))
collect()
collects common powers of a
term in an expression.
>>> x*y + x - 3 + 2*x**2 - z*x**2 + x**3
3 2 2
x - x ⋅z + 2⋅x + x⋅y + x - 3
>>> collect(_, x)
3 2
x + x ⋅(-z + 2) + x⋅(y + 1) - 3
collect()
is particularly useful in
conjunction with the coeff()
method.
>>> _.coeff(x, 2)
-z + 2
cancel()
will take any rational
function and put it into the standard canonical form, \(p/q\), where \(p\)
and \(q\) are expanded polynomials with no common factors.
>>> 1/x + (3*x/2 - 2)/(x - 4)
3⋅x
─── - 2
2 1
─────── + ─
x - 4 x
>>> cancel(_)
2
3⋅x - 2⋅x - 8
──────────────
2
2⋅x - 8⋅x
>>> expr = (x*y**2 - 2*x*y*z + x*z**2 + y**2 - 2*y*z + z**2)/(x**2 - 1)
>>> expr
2 2 2 2
x⋅y - 2⋅x⋅y⋅z + x⋅z + y - 2⋅y⋅z + z
───────────────────────────────────────
2
x - 1
>>> cancel(_)
2 2
y - 2⋅y⋅z + z
───────────────
x - 1
Note
Since factor()
will completely
factorize both the numerator and the denominator of an expression,
it can also be used to do the same thing:
>>> factor(expr)
2
(y - z)
────────
x - 1
However, it’s less efficient if you are only interested in making sure that the expression is in canceled form.
apart()
performs a partial fraction
decomposition on a
rational function.
>>> (4*x**3 + 21*x**2 + 10*x + 12)/(x**4 + 5*x**3 + 5*x**2 + 4*x)
3 2
4⋅x + 21⋅x + 10⋅x + 12
────────────────────────
4 3 2
x + 5⋅x + 5⋅x + 4⋅x
>>> apart(_)
2⋅x - 1 1 3
────────── - ───── + ─
2 x + 4 x
x + x + 1
Trigonometric Functions
To simplify expressions using trigonometric identities, use
trigsimp()
function.
>>> trigsimp(sin(x)**2 + cos(x)**2)
1
>>> trigsimp(sin(x)**4 - 2*cos(x)**2*sin(x)**2 + cos(x)**4)
cos(4⋅x) 1
──────── + ─
2 2
>>> trigsimp(sin(x)*tan(x)/sec(x))
2
sin (x)
It also works with hyperbolic functions.
>>> trigsimp(cosh(x)**2 + sinh(x)**2)
cosh(2⋅x)
>>> trigsimp(sinh(x)/tanh(x))
cosh(x)
Much like simplify()
function,
trigsimp()
applies various
trigonometric identities to the input expression, and then uses a
heuristic to return the “best” one.
To expand trigonometric functions, that is, apply the sum or double
angle identities, use expand_trig()
function.
>>> expand_trig(sin(x + y))
sin(x)⋅cos(y) + sin(y)⋅cos(x)
>>> expand_trig(tan(2*x))
2⋅tan(x)
─────────────
2
- tan (x) + 1
Powers and Logarithms
powdenest()
function applies identity
\((x^a)^b = x^{a b}\), from left to right, if assumptions allow.
>>> a, b = symbols('a b', real=True)
>>> p = symbols('p', positive=True)
>>> powdenest((p**a)**b)
a⋅b
p
powsimp()
function reduces expression
by combining powers with similar bases and exponent.
>>> powsimp(z**x*z**y)
x + y
z
Again, as for powdenest()
above, for
the identity \(x^a y^a = (x y)^a\), that combine bases, we should be
careful about assumptions.
>>> q = symbols('q', positive=True)
>>> powsimp(p**a*q**a)
a
(p⋅q)
In general, this identity doesn’t hold. For example, if \(x = y = -1\) and \(a = 1/2\).
expand_power_exp()
and
expand_power_base()
functions do reverse
of powsimp()
.
>>> expand_power_exp(x**(y + z))
y z
x ⋅x
>>> expand_power_base((p*q)**a)
a a
p ⋅q
Logarithms have similar issues as powers. There are two main identities
\(\log{(xy)} = \log{(x)} + \log{(y)}\)
\(\log{(x^n)} = n\log{(x)}\)
Neither identity is true for arbitrary complex \(x\) and \(y\), due to the branch cut in the complex plane for the complex logarithm.
To apply above identities from left to right, use
expand_log()
. As for powers, the
identities will not be applied unless they are valid with given set of
assumptions for symbols.
>>> expand_log(log(p*q))
log(p) + log(q)
>>> expand_log(log(p/q))
log(p) - log(q)
>>> expand_log(log(p**2))
2⋅log(p)
>>> expand_log(log(p**a))
a⋅log(p)
>>> expand_log(log(x*y))
log(x⋅y)
To apply identities from right to left, i.e. do reverse of
expand_log()
, use
logcombine()
function.
>>> logcombine(log(p) + log(q))
log(p⋅q)
>>> logcombine(a*log(p))
⎛ a⎞
log⎝p ⎠
>>> logcombine(a*log(z))
a⋅log(z)
Special Functions
Diofant implements dozens of special functions, ranging from functions in combinatorics to mathematical physics.
To expand special functions in terms of some identities, use
expand_func()
. For example the gamma
function
gamma
can be
expanded as
>>> expand_func(gamma(x + 3))
x⋅(x + 1)⋅(x + 2)⋅Γ(x)
This method also can help if you would like to rewrite the generalized
hypergeometric function
hyper
or the Meijer
G-function meijerg
in terms
of more standard functions.
>>> expand_func(hyper([1, 1], [2], z))
-log(-z + 1)
─────────────
z
>>> meijerg([[1], [1]], [[1], []], -z)
╭─╮1, 1 ⎛1 1 │ ⎞
│╶┐ ⎜ │ -z⎟
╰─╯2, 1 ⎝1 │ ⎠
>>> expand_func(_)
z ___
╲╱ ℯ
Another type of expand rule is expanding complex valued expressions and putting
them into a normal form. For this expand_complex()
is used. Note that it will always perform arithmetic expand to obtain the
desired normal form.
>>> expand_complex(x + I*y)
ⅈ⋅(re(y) + im(x)) + re(x) - im(y)
The same behavior can be obtained by using
as_real_imag()
method.
>>> (x + I*y).as_real_imag()
(re(x) - im(y), re(y) + im(x))
To simplify combinatorial expressions, involving
factorial
,
binomial
or
gamma
— use
combsimp()
function.
>>> combsimp(factorial(x)/factorial(x - 3))
x⋅(x - 2)⋅(x - 1)
>>> combsimp(binomial(x + 1, y + 1)/binomial(x, y))
x + 1
─────
y + 1
>>> combsimp(gamma(x)*gamma(1 - x))
π
────────
sin(π⋅x)
CSE
Before evaluating a large expression, it is often useful to identify common
subexpressions, collect them and evaluate them at once. This is called common
subexpression elimination (CSE) and implemented in the
cse()
function.
>>> cse(sqrt(sin(x)))
⎛ ⎡ ________⎤⎞
⎝[], ⎣╲╱ sin(x) ⎦⎠
>>> cse(sqrt(sin(x) + 5)*sqrt(sin(x) + 4))
⎛ ⎡ ________ ________⎤⎞
⎝[(x₀, sin(x))], ⎣╲╱ x₀ + 4 ⋅╲╱ x₀ + 5 ⎦⎠
>>> cse(sqrt(sin(x + 1) + 5 + cos(y))*sqrt(sin(x + 1) + 4 + cos(y)))
⎛ ⎡ ________ ________⎤⎞
⎝[(x₀, sin(x + 1) + cos(y))], ⎣╲╱ x₀ + 4 ⋅╲╱ x₀ + 5 ⎦⎠
>>> cse((x - y)*(z - y) + sqrt((x - y)*(z - y)))
⎛ ⎡ ____ ⎤⎞
⎝[(x₀, -y), (x₁, (x + x₀)⋅(x₀ + z))], ⎣╲╱ x₁ + x₁⎦⎠
Optimizations to be performed before and after common subexpressions elimination can be passed in the``optimizations`` optional argument.
>>> cse((x - y)*(z - y) + sqrt((x - y)*(z - y)), optimizations='basic')
⎛ ⎡ ____ ⎤⎞
⎝[(x₀, -(x - y)⋅(y - z))], ⎣╲╱ x₀ + x₀⎦⎠
However, these optimizations can be very slow for large expressions. Moreover,
if speed is a concern, one can pass the option order='none'
. Order of
terms will then be dependent on hashing algorithm implementation, but speed
will be greatly improved.