According to the documentation one can make substitutions as:
expr = sin(2*x) + cos(2*x)
expr.subs(sin(2*x), 2*sin(x)*cos(x))
But say x = y + 2
. How do I convert the expression from a function of x
to a function of y
without having to give the substitution explicitly?
2
SymPy offers a module to perform trigonometric simplification. It contains many transformation rules, named TR#some_number
, for example TR7
, etc.
The rule that applies to your case is TR11
. Basically, you execute TR11(your_expression)
to apply a trigonometric identity.
There is one small problem: whenever your perform a substitution, sympy evaluates the arguments of the expression. For example:
from sympy import *
var("x y")
expr = sin(2*x) + cos(2*x)
expr2 = expr.subs(x, y + 2)
expr2
# sin(2*y + 4) + cos(2*y + 4)
Note that 2 * (y + 2)
was evaluated to 2*y + 4
. The Fu module expects the arguments of the trigonometric functions to be in a specific form. TR11
expect the arguments of sin
and cos
to be in the form 2 * alpha
, or 2 * (y + 2)
in your case.
So, we need to find a way to perform that substitution without getting sympy to evaluate the expression. One solution is the somewhat controversial evaluate
context manager:
with evaluate(False):
expr2 = expr.subs(x, y+2)
print(expr2)
# sin(2*(y + 2)) + cos(2*(y + 2))
It is controversial because it is marked as an experimental feature, meaning it can be removed from sympy anytime. Moreover, the documentation clearly states that it might not work as expected on large expressions.
Another possible solution to this problem could be UnevaluatedExpr
, which keeps a specific expression from being evaluated. It works like this:
expr3 = expr.subs(x, UnevaluatedExpr(y+2))
expr3
# sin(2*(y + 2)) + cos(2*(y + 2))
However, expr
is a commutative expression, while expr3
is non commutative (I suppose it is a bug), which can produce unexpected results in later operations (like differentiation, etc). That’s why I’m not really a fan of this feature.
At this point, we can use the FU module:
from sympy.simplify.fu import TR11
TR11(expr2)
# -sin(y + 2)**2 + 2*sin(y + 2)*cos(y + 2) + cos(y + 2)**2
Note that TR11
applied the trigonometric identity both to the sin
and cos
. Should you wish to apply it only to the sin
you’d have to perform some more expression manipulation:
# extract sin terms
sin_terms = expr2.find(sin)
# apply TR11 only on sin
subs_dict = {k: TR11(k) for k in sin_terms}
# substitute back
expr2.subs(subs_dict)
# 2*sin(y + 2)*cos(y + 2) + cos(2*(y + 2))
To collect the 2 in the auto-expanded 2*(x + y)
you can use factor_terms
:
from sympy import *
from sympy.abc import x,y
from sympy.simplify.fu import TR11
>>> sin(2*(y + 2)) + cos(2*(y + 2))
sin(2*y + 4) + cos(2*y + 4)
>>> factor_terms(_)
sin(2*(y + 2)) + cos(2*(y + 2))
>>> TR11(_)
-sin(y + 2)**2 + 2*sin(y + 2)*cos(y + 2) + cos(y + 2)**2