Spaces:
Sleeping
Sleeping
| from collections import defaultdict | |
| from functools import reduce | |
| from sympy.core import (sympify, Basic, S, Expr, factor_terms, | |
| Mul, Add, bottom_up) | |
| from sympy.core.cache import cacheit | |
| from sympy.core.function import (count_ops, _mexpand, FunctionClass, expand, | |
| expand_mul, _coeff_isneg, Derivative) | |
| from sympy.core.numbers import I, Integer | |
| from sympy.core.intfunc import igcd | |
| from sympy.core.sorting import _nodes | |
| from sympy.core.symbol import Dummy, symbols, Wild | |
| from sympy.external.gmpy import SYMPY_INTS | |
| from sympy.functions import sin, cos, exp, cosh, tanh, sinh, tan, cot, coth | |
| from sympy.functions import atan2 | |
| from sympy.functions.elementary.hyperbolic import HyperbolicFunction | |
| from sympy.functions.elementary.trigonometric import TrigonometricFunction | |
| from sympy.polys import Poly, factor, cancel, parallel_poly_from_expr | |
| from sympy.polys.domains import ZZ | |
| from sympy.polys.polyerrors import PolificationFailed | |
| from sympy.polys.polytools import groebner | |
| from sympy.simplify.cse_main import cse | |
| from sympy.strategies.core import identity | |
| from sympy.strategies.tree import greedy | |
| from sympy.utilities.iterables import iterable | |
| from sympy.utilities.misc import debug | |
| def trigsimp_groebner(expr, hints=[], quick=False, order="grlex", | |
| polynomial=False): | |
| """ | |
| Simplify trigonometric expressions using a groebner basis algorithm. | |
| Explanation | |
| =========== | |
| This routine takes a fraction involving trigonometric or hyperbolic | |
| expressions, and tries to simplify it. The primary metric is the | |
| total degree. Some attempts are made to choose the simplest possible | |
| expression of the minimal degree, but this is non-rigorous, and also | |
| very slow (see the ``quick=True`` option). | |
| If ``polynomial`` is set to True, instead of simplifying numerator and | |
| denominator together, this function just brings numerator and denominator | |
| into a canonical form. This is much faster, but has potentially worse | |
| results. However, if the input is a polynomial, then the result is | |
| guaranteed to be an equivalent polynomial of minimal degree. | |
| The most important option is hints. Its entries can be any of the | |
| following: | |
| - a natural number | |
| - a function | |
| - an iterable of the form (func, var1, var2, ...) | |
| - anything else, interpreted as a generator | |
| A number is used to indicate that the search space should be increased. | |
| A function is used to indicate that said function is likely to occur in a | |
| simplified expression. | |
| An iterable is used indicate that func(var1 + var2 + ...) is likely to | |
| occur in a simplified . | |
| An additional generator also indicates that it is likely to occur. | |
| (See examples below). | |
| This routine carries out various computationally intensive algorithms. | |
| The option ``quick=True`` can be used to suppress one particularly slow | |
| step (at the expense of potentially more complicated results, but never at | |
| the expense of increased total degree). | |
| Examples | |
| ======== | |
| >>> from sympy.abc import x, y | |
| >>> from sympy import sin, tan, cos, sinh, cosh, tanh | |
| >>> from sympy.simplify.trigsimp import trigsimp_groebner | |
| Suppose you want to simplify ``sin(x)*cos(x)``. Naively, nothing happens: | |
| >>> ex = sin(x)*cos(x) | |
| >>> trigsimp_groebner(ex) | |
| sin(x)*cos(x) | |
| This is because ``trigsimp_groebner`` only looks for a simplification | |
| involving just ``sin(x)`` and ``cos(x)``. You can tell it to also try | |
| ``2*x`` by passing ``hints=[2]``: | |
| >>> trigsimp_groebner(ex, hints=[2]) | |
| sin(2*x)/2 | |
| >>> trigsimp_groebner(sin(x)**2 - cos(x)**2, hints=[2]) | |
| -cos(2*x) | |
| Increasing the search space this way can quickly become expensive. A much | |
| faster way is to give a specific expression that is likely to occur: | |
| >>> trigsimp_groebner(ex, hints=[sin(2*x)]) | |
| sin(2*x)/2 | |
| Hyperbolic expressions are similarly supported: | |
| >>> trigsimp_groebner(sinh(2*x)/sinh(x)) | |
| 2*cosh(x) | |
| Note how no hints had to be passed, since the expression already involved | |
| ``2*x``. | |
| The tangent function is also supported. You can either pass ``tan`` in the | |
| hints, to indicate that tan should be tried whenever cosine or sine are, | |
| or you can pass a specific generator: | |
| >>> trigsimp_groebner(sin(x)/cos(x), hints=[tan]) | |
| tan(x) | |
| >>> trigsimp_groebner(sinh(x)/cosh(x), hints=[tanh(x)]) | |
| tanh(x) | |
| Finally, you can use the iterable form to suggest that angle sum formulae | |
| should be tried: | |
| >>> ex = (tan(x) + tan(y))/(1 - tan(x)*tan(y)) | |
| >>> trigsimp_groebner(ex, hints=[(tan, x, y)]) | |
| tan(x + y) | |
| """ | |
| # TODO | |
| # - preprocess by replacing everything by funcs we can handle | |
| # - optionally use cot instead of tan | |
| # - more intelligent hinting. | |
| # For example, if the ideal is small, and we have sin(x), sin(y), | |
| # add sin(x + y) automatically... ? | |
| # - algebraic numbers ... | |
| # - expressions of lowest degree are not distinguished properly | |
| # e.g. 1 - sin(x)**2 | |
| # - we could try to order the generators intelligently, so as to influence | |
| # which monomials appear in the quotient basis | |
| # THEORY | |
| # ------ | |
| # Ratsimpmodprime above can be used to "simplify" a rational function | |
| # modulo a prime ideal. "Simplify" mainly means finding an equivalent | |
| # expression of lower total degree. | |
| # | |
| # We intend to use this to simplify trigonometric functions. To do that, | |
| # we need to decide (a) which ring to use, and (b) modulo which ideal to | |
| # simplify. In practice, (a) means settling on a list of "generators" | |
| # a, b, c, ..., such that the fraction we want to simplify is a rational | |
| # function in a, b, c, ..., with coefficients in ZZ (integers). | |
| # (2) means that we have to decide what relations to impose on the | |
| # generators. There are two practical problems: | |
| # (1) The ideal has to be *prime* (a technical term). | |
| # (2) The relations have to be polynomials in the generators. | |
| # | |
| # We typically have two kinds of generators: | |
| # - trigonometric expressions, like sin(x), cos(5*x), etc | |
| # - "everything else", like gamma(x), pi, etc. | |
| # | |
| # Since this function is trigsimp, we will concentrate on what to do with | |
| # trigonometric expressions. We can also simplify hyperbolic expressions, | |
| # but the extensions should be clear. | |
| # | |
| # One crucial point is that all *other* generators really should behave | |
| # like indeterminates. In particular if (say) "I" is one of them, then | |
| # in fact I**2 + 1 = 0 and we may and will compute non-sensical | |
| # expressions. However, we can work with a dummy and add the relation | |
| # I**2 + 1 = 0 to our ideal, then substitute back in the end. | |
| # | |
| # Now regarding trigonometric generators. We split them into groups, | |
| # according to the argument of the trigonometric functions. We want to | |
| # organise this in such a way that most trigonometric identities apply in | |
| # the same group. For example, given sin(x), cos(2*x) and cos(y), we would | |
| # group as [sin(x), cos(2*x)] and [cos(y)]. | |
| # | |
| # Our prime ideal will be built in three steps: | |
| # (1) For each group, compute a "geometrically prime" ideal of relations. | |
| # Geometrically prime means that it generates a prime ideal in | |
| # CC[gens], not just ZZ[gens]. | |
| # (2) Take the union of all the generators of the ideals for all groups. | |
| # By the geometric primality condition, this is still prime. | |
| # (3) Add further inter-group relations which preserve primality. | |
| # | |
| # Step (1) works as follows. We will isolate common factors in the | |
| # argument, so that all our generators are of the form sin(n*x), cos(n*x) | |
| # or tan(n*x), with n an integer. Suppose first there are no tan terms. | |
| # The ideal [sin(x)**2 + cos(x)**2 - 1] is geometrically prime, since | |
| # X**2 + Y**2 - 1 is irreducible over CC. | |
| # Now, if we have a generator sin(n*x), than we can, using trig identities, | |
| # express sin(n*x) as a polynomial in sin(x) and cos(x). We can add this | |
| # relation to the ideal, preserving geometric primality, since the quotient | |
| # ring is unchanged. | |
| # Thus we have treated all sin and cos terms. | |
| # For tan(n*x), we add a relation tan(n*x)*cos(n*x) - sin(n*x) = 0. | |
| # (This requires of course that we already have relations for cos(n*x) and | |
| # sin(n*x).) It is not obvious, but it seems that this preserves geometric | |
| # primality. | |
| # XXX A real proof would be nice. HELP! | |
| # Sketch that <S**2 + C**2 - 1, C*T - S> is a prime ideal of | |
| # CC[S, C, T]: | |
| # - it suffices to show that the projective closure in CP**3 is | |
| # irreducible | |
| # - using the half-angle substitutions, we can express sin(x), tan(x), | |
| # cos(x) as rational functions in tan(x/2) | |
| # - from this, we get a rational map from CP**1 to our curve | |
| # - this is a morphism, hence the curve is prime | |
| # | |
| # Step (2) is trivial. | |
| # | |
| # Step (3) works by adding selected relations of the form | |
| # sin(x + y) - sin(x)*cos(y) - sin(y)*cos(x), etc. Geometric primality is | |
| # preserved by the same argument as before. | |
| def parse_hints(hints): | |
| """Split hints into (n, funcs, iterables, gens).""" | |
| n = 1 | |
| funcs, iterables, gens = [], [], [] | |
| for e in hints: | |
| if isinstance(e, (SYMPY_INTS, Integer)): | |
| n = e | |
| elif isinstance(e, FunctionClass): | |
| funcs.append(e) | |
| elif iterable(e): | |
| iterables.append((e[0], e[1:])) | |
| # XXX sin(x+2y)? | |
| # Note: we go through polys so e.g. | |
| # sin(-x) -> -sin(x) -> sin(x) | |
| gens.extend(parallel_poly_from_expr( | |
| [e[0](x) for x in e[1:]] + [e[0](Add(*e[1:]))])[1].gens) | |
| else: | |
| gens.append(e) | |
| return n, funcs, iterables, gens | |
| def build_ideal(x, terms): | |
| """ | |
| Build generators for our ideal. ``Terms`` is an iterable with elements of | |
| the form (fn, coeff), indicating that we have a generator fn(coeff*x). | |
| If any of the terms is trigonometric, sin(x) and cos(x) are guaranteed | |
| to appear in terms. Similarly for hyperbolic functions. For tan(n*x), | |
| sin(n*x) and cos(n*x) are guaranteed. | |
| """ | |
| I = [] | |
| y = Dummy('y') | |
| for fn, coeff in terms: | |
| for c, s, t, rel in ( | |
| [cos, sin, tan, cos(x)**2 + sin(x)**2 - 1], | |
| [cosh, sinh, tanh, cosh(x)**2 - sinh(x)**2 - 1]): | |
| if coeff == 1 and fn in [c, s]: | |
| I.append(rel) | |
| elif fn == t: | |
| I.append(t(coeff*x)*c(coeff*x) - s(coeff*x)) | |
| elif fn in [c, s]: | |
| cn = fn(coeff*y).expand(trig=True).subs(y, x) | |
| I.append(fn(coeff*x) - cn) | |
| return list(set(I)) | |
| def analyse_gens(gens, hints): | |
| """ | |
| Analyse the generators ``gens``, using the hints ``hints``. | |
| The meaning of ``hints`` is described in the main docstring. | |
| Return a new list of generators, and also the ideal we should | |
| work with. | |
| """ | |
| # First parse the hints | |
| n, funcs, iterables, extragens = parse_hints(hints) | |
| debug('n=%s funcs: %s iterables: %s extragens: %s', | |
| (funcs, iterables, extragens)) | |
| # We just add the extragens to gens and analyse them as before | |
| gens = list(gens) | |
| gens.extend(extragens) | |
| # remove duplicates | |
| funcs = list(set(funcs)) | |
| iterables = list(set(iterables)) | |
| gens = list(set(gens)) | |
| # all the functions we can do anything with | |
| allfuncs = {sin, cos, tan, sinh, cosh, tanh} | |
| # sin(3*x) -> ((3, x), sin) | |
| trigterms = [(g.args[0].as_coeff_mul(), g.func) for g in gens | |
| if g.func in allfuncs] | |
| # Our list of new generators - start with anything that we cannot | |
| # work with (i.e. is not a trigonometric term) | |
| freegens = [g for g in gens if g.func not in allfuncs] | |
| newgens = [] | |
| trigdict = {} | |
| for (coeff, var), fn in trigterms: | |
| trigdict.setdefault(var, []).append((coeff, fn)) | |
| res = [] # the ideal | |
| for key, val in trigdict.items(): | |
| # We have now assembeled a dictionary. Its keys are common | |
| # arguments in trigonometric expressions, and values are lists of | |
| # pairs (fn, coeff). x0, (fn, coeff) in trigdict means that we | |
| # need to deal with fn(coeff*x0). We take the rational gcd of the | |
| # coeffs, call it ``gcd``. We then use x = x0/gcd as "base symbol", | |
| # all other arguments are integral multiples thereof. | |
| # We will build an ideal which works with sin(x), cos(x). | |
| # If hint tan is provided, also work with tan(x). Moreover, if | |
| # n > 1, also work with sin(k*x) for k <= n, and similarly for cos | |
| # (and tan if the hint is provided). Finally, any generators which | |
| # the ideal does not work with but we need to accommodate (either | |
| # because it was in expr or because it was provided as a hint) | |
| # we also build into the ideal. | |
| # This selection process is expressed in the list ``terms``. | |
| # build_ideal then generates the actual relations in our ideal, | |
| # from this list. | |
| fns = [x[1] for x in val] | |
| val = [x[0] for x in val] | |
| gcd = reduce(igcd, val) | |
| terms = [(fn, v/gcd) for (fn, v) in zip(fns, val)] | |
| fs = set(funcs + fns) | |
| for c, s, t in ([cos, sin, tan], [cosh, sinh, tanh]): | |
| if any(x in fs for x in (c, s, t)): | |
| fs.add(c) | |
| fs.add(s) | |
| for fn in fs: | |
| for k in range(1, n + 1): | |
| terms.append((fn, k)) | |
| extra = [] | |
| for fn, v in terms: | |
| if fn == tan: | |
| extra.append((sin, v)) | |
| extra.append((cos, v)) | |
| if fn in [sin, cos] and tan in fs: | |
| extra.append((tan, v)) | |
| if fn == tanh: | |
| extra.append((sinh, v)) | |
| extra.append((cosh, v)) | |
| if fn in [sinh, cosh] and tanh in fs: | |
| extra.append((tanh, v)) | |
| terms.extend(extra) | |
| x = gcd*Mul(*key) | |
| r = build_ideal(x, terms) | |
| res.extend(r) | |
| newgens.extend({fn(v*x) for fn, v in terms}) | |
| # Add generators for compound expressions from iterables | |
| for fn, args in iterables: | |
| if fn == tan: | |
| # Tan expressions are recovered from sin and cos. | |
| iterables.extend([(sin, args), (cos, args)]) | |
| elif fn == tanh: | |
| # Tanh expressions are recovered from sihn and cosh. | |
| iterables.extend([(sinh, args), (cosh, args)]) | |
| else: | |
| dummys = symbols('d:%i' % len(args), cls=Dummy) | |
| expr = fn( Add(*dummys)).expand(trig=True).subs(list(zip(dummys, args))) | |
| res.append(fn(Add(*args)) - expr) | |
| if myI in gens: | |
| res.append(myI**2 + 1) | |
| freegens.remove(myI) | |
| newgens.append(myI) | |
| return res, freegens, newgens | |
| myI = Dummy('I') | |
| expr = expr.subs(S.ImaginaryUnit, myI) | |
| subs = [(myI, S.ImaginaryUnit)] | |
| num, denom = cancel(expr).as_numer_denom() | |
| try: | |
| (pnum, pdenom), opt = parallel_poly_from_expr([num, denom]) | |
| except PolificationFailed: | |
| return expr | |
| debug('initial gens:', opt.gens) | |
| ideal, freegens, gens = analyse_gens(opt.gens, hints) | |
| debug('ideal:', ideal) | |
| debug('new gens:', gens, " -- len", len(gens)) | |
| debug('free gens:', freegens, " -- len", len(gens)) | |
| # NOTE we force the domain to be ZZ to stop polys from injecting generators | |
| # (which is usually a sign of a bug in the way we build the ideal) | |
| if not gens: | |
| return expr | |
| G = groebner(ideal, order=order, gens=gens, domain=ZZ) | |
| debug('groebner basis:', list(G), " -- len", len(G)) | |
| # If our fraction is a polynomial in the free generators, simplify all | |
| # coefficients separately: | |
| from sympy.simplify.ratsimp import ratsimpmodprime | |
| if freegens and pdenom.has_only_gens(*set(gens).intersection(pdenom.gens)): | |
| num = Poly(num, gens=gens+freegens).eject(*gens) | |
| res = [] | |
| for monom, coeff in num.terms(): | |
| ourgens = set(parallel_poly_from_expr([coeff, denom])[1].gens) | |
| # We compute the transitive closure of all generators that can | |
| # be reached from our generators through relations in the ideal. | |
| changed = True | |
| while changed: | |
| changed = False | |
| for p in ideal: | |
| p = Poly(p) | |
| if not ourgens.issuperset(p.gens) and \ | |
| not p.has_only_gens(*set(p.gens).difference(ourgens)): | |
| changed = True | |
| ourgens.update(p.exclude().gens) | |
| # NOTE preserve order! | |
| realgens = [x for x in gens if x in ourgens] | |
| # The generators of the ideal have now been (implicitly) split | |
| # into two groups: those involving ourgens and those that don't. | |
| # Since we took the transitive closure above, these two groups | |
| # live in subgrings generated by a *disjoint* set of variables. | |
| # Any sensible groebner basis algorithm will preserve this disjoint | |
| # structure (i.e. the elements of the groebner basis can be split | |
| # similarly), and and the two subsets of the groebner basis then | |
| # form groebner bases by themselves. (For the smaller generating | |
| # sets, of course.) | |
| ourG = [g.as_expr() for g in G.polys if | |
| g.has_only_gens(*ourgens.intersection(g.gens))] | |
| res.append(Mul(*[a**b for a, b in zip(freegens, monom)]) * \ | |
| ratsimpmodprime(coeff/denom, ourG, order=order, | |
| gens=realgens, quick=quick, domain=ZZ, | |
| polynomial=polynomial).subs(subs)) | |
| return Add(*res) | |
| # NOTE The following is simpler and has less assumptions on the | |
| # groebner basis algorithm. If the above turns out to be broken, | |
| # use this. | |
| return Add(*[Mul(*[a**b for a, b in zip(freegens, monom)]) * \ | |
| ratsimpmodprime(coeff/denom, list(G), order=order, | |
| gens=gens, quick=quick, domain=ZZ) | |
| for monom, coeff in num.terms()]) | |
| else: | |
| return ratsimpmodprime( | |
| expr, list(G), order=order, gens=freegens+gens, | |
| quick=quick, domain=ZZ, polynomial=polynomial).subs(subs) | |
| _trigs = (TrigonometricFunction, HyperbolicFunction) | |
| def _trigsimp_inverse(rv): | |
| def check_args(x, y): | |
| try: | |
| return x.args[0] == y.args[0] | |
| except IndexError: | |
| return False | |
| def f(rv): | |
| # for simple functions | |
| g = getattr(rv, 'inverse', None) | |
| if (g is not None and isinstance(rv.args[0], g()) and | |
| isinstance(g()(1), TrigonometricFunction)): | |
| return rv.args[0].args[0] | |
| # for atan2 simplifications, harder because atan2 has 2 args | |
| if isinstance(rv, atan2): | |
| y, x = rv.args | |
| if _coeff_isneg(y): | |
| return -f(atan2(-y, x)) | |
| elif _coeff_isneg(x): | |
| return S.Pi - f(atan2(y, -x)) | |
| if check_args(x, y): | |
| if isinstance(y, sin) and isinstance(x, cos): | |
| return x.args[0] | |
| if isinstance(y, cos) and isinstance(x, sin): | |
| return S.Pi / 2 - x.args[0] | |
| return rv | |
| return bottom_up(rv, f) | |
| def trigsimp(expr, inverse=False, **opts): | |
| """Returns a reduced expression by using known trig identities. | |
| Parameters | |
| ========== | |
| inverse : bool, optional | |
| If ``inverse=True``, it will be assumed that a composition of inverse | |
| functions, such as sin and asin, can be cancelled in any order. | |
| For example, ``asin(sin(x))`` will yield ``x`` without checking whether | |
| x belongs to the set where this relation is true. The default is False. | |
| Default : True | |
| method : string, optional | |
| Specifies the method to use. Valid choices are: | |
| - ``'matching'``, default | |
| - ``'groebner'`` | |
| - ``'combined'`` | |
| - ``'fu'`` | |
| - ``'old'`` | |
| If ``'matching'``, simplify the expression recursively by targeting | |
| common patterns. If ``'groebner'``, apply an experimental groebner | |
| basis algorithm. In this case further options are forwarded to | |
| ``trigsimp_groebner``, please refer to | |
| its docstring. If ``'combined'``, it first runs the groebner basis | |
| algorithm with small default parameters, then runs the ``'matching'`` | |
| algorithm. If ``'fu'``, run the collection of trigonometric | |
| transformations described by Fu, et al. (see the | |
| :py:func:`~sympy.simplify.fu.fu` docstring). If ``'old'``, the original | |
| SymPy trig simplification function is run. | |
| opts : | |
| Optional keyword arguments passed to the method. See each method's | |
| function docstring for details. | |
| Examples | |
| ======== | |
| >>> from sympy import trigsimp, sin, cos, log | |
| >>> from sympy.abc import x | |
| >>> e = 2*sin(x)**2 + 2*cos(x)**2 | |
| >>> trigsimp(e) | |
| 2 | |
| Simplification occurs wherever trigonometric functions are located. | |
| >>> trigsimp(log(e)) | |
| log(2) | |
| Using ``method='groebner'`` (or ``method='combined'``) might lead to | |
| greater simplification. | |
| The old trigsimp routine can be accessed as with method ``method='old'``. | |
| >>> from sympy import coth, tanh | |
| >>> t = 3*tanh(x)**7 - 2/coth(x)**7 | |
| >>> trigsimp(t, method='old') == t | |
| True | |
| >>> trigsimp(t) | |
| tanh(x)**7 | |
| """ | |
| from sympy.simplify.fu import fu | |
| expr = sympify(expr) | |
| _eval_trigsimp = getattr(expr, '_eval_trigsimp', None) | |
| if _eval_trigsimp is not None: | |
| return _eval_trigsimp(**opts) | |
| old = opts.pop('old', False) | |
| if not old: | |
| opts.pop('deep', None) | |
| opts.pop('recursive', None) | |
| method = opts.pop('method', 'matching') | |
| else: | |
| method = 'old' | |
| def groebnersimp(ex, **opts): | |
| def traverse(e): | |
| if e.is_Atom: | |
| return e | |
| args = [traverse(x) for x in e.args] | |
| if e.is_Function or e.is_Pow: | |
| args = [trigsimp_groebner(x, **opts) for x in args] | |
| return e.func(*args) | |
| new = traverse(ex) | |
| if not isinstance(new, Expr): | |
| return new | |
| return trigsimp_groebner(new, **opts) | |
| trigsimpfunc = { | |
| 'fu': (lambda x: fu(x, **opts)), | |
| 'matching': (lambda x: futrig(x)), | |
| 'groebner': (lambda x: groebnersimp(x, **opts)), | |
| 'combined': (lambda x: futrig(groebnersimp(x, | |
| polynomial=True, hints=[2, tan]))), | |
| 'old': lambda x: trigsimp_old(x, **opts), | |
| }[method] | |
| expr_simplified = trigsimpfunc(expr) | |
| if inverse: | |
| expr_simplified = _trigsimp_inverse(expr_simplified) | |
| return expr_simplified | |
| def exptrigsimp(expr): | |
| """ | |
| Simplifies exponential / trigonometric / hyperbolic functions. | |
| Examples | |
| ======== | |
| >>> from sympy import exptrigsimp, exp, cosh, sinh | |
| >>> from sympy.abc import z | |
| >>> exptrigsimp(exp(z) + exp(-z)) | |
| 2*cosh(z) | |
| >>> exptrigsimp(cosh(z) - sinh(z)) | |
| exp(-z) | |
| """ | |
| from sympy.simplify.fu import hyper_as_trig, TR2i | |
| def exp_trig(e): | |
| # select the better of e, and e rewritten in terms of exp or trig | |
| # functions | |
| choices = [e] | |
| if e.has(*_trigs): | |
| choices.append(e.rewrite(exp)) | |
| choices.append(e.rewrite(cos)) | |
| return min(*choices, key=count_ops) | |
| newexpr = bottom_up(expr, exp_trig) | |
| def f(rv): | |
| if not rv.is_Mul: | |
| return rv | |
| commutative_part, noncommutative_part = rv.args_cnc() | |
| # Since as_powers_dict loses order information, | |
| # if there is more than one noncommutative factor, | |
| # it should only be used to simplify the commutative part. | |
| if (len(noncommutative_part) > 1): | |
| return f(Mul(*commutative_part))*Mul(*noncommutative_part) | |
| rvd = rv.as_powers_dict() | |
| newd = rvd.copy() | |
| def signlog(expr, sign=S.One): | |
| if expr is S.Exp1: | |
| return sign, S.One | |
| elif isinstance(expr, exp) or (expr.is_Pow and expr.base == S.Exp1): | |
| return sign, expr.exp | |
| elif sign is S.One: | |
| return signlog(-expr, sign=-S.One) | |
| else: | |
| return None, None | |
| ee = rvd[S.Exp1] | |
| for k in rvd: | |
| if k.is_Add and len(k.args) == 2: | |
| # k == c*(1 + sign*E**x) | |
| c = k.args[0] | |
| sign, x = signlog(k.args[1]/c) | |
| if not x: | |
| continue | |
| m = rvd[k] | |
| newd[k] -= m | |
| if ee == -x*m/2: | |
| # sinh and cosh | |
| newd[S.Exp1] -= ee | |
| ee = 0 | |
| if sign == 1: | |
| newd[2*c*cosh(x/2)] += m | |
| else: | |
| newd[-2*c*sinh(x/2)] += m | |
| elif newd[1 - sign*S.Exp1**x] == -m: | |
| # tanh | |
| del newd[1 - sign*S.Exp1**x] | |
| if sign == 1: | |
| newd[-c/tanh(x/2)] += m | |
| else: | |
| newd[-c*tanh(x/2)] += m | |
| else: | |
| newd[1 + sign*S.Exp1**x] += m | |
| newd[c] += m | |
| return Mul(*[k**newd[k] for k in newd]) | |
| newexpr = bottom_up(newexpr, f) | |
| # sin/cos and sinh/cosh ratios to tan and tanh, respectively | |
| if newexpr.has(HyperbolicFunction): | |
| e, f = hyper_as_trig(newexpr) | |
| newexpr = f(TR2i(e)) | |
| if newexpr.has(TrigonometricFunction): | |
| newexpr = TR2i(newexpr) | |
| # can we ever generate an I where there was none previously? | |
| if not (newexpr.has(I) and not expr.has(I)): | |
| expr = newexpr | |
| return expr | |
| #-------------------- the old trigsimp routines --------------------- | |
| def trigsimp_old(expr, *, first=True, **opts): | |
| """ | |
| Reduces expression by using known trig identities. | |
| Notes | |
| ===== | |
| deep: | |
| - Apply trigsimp inside all objects with arguments | |
| recursive: | |
| - Use common subexpression elimination (cse()) and apply | |
| trigsimp recursively (this is quite expensive if the | |
| expression is large) | |
| method: | |
| - Determine the method to use. Valid choices are 'matching' (default), | |
| 'groebner', 'combined', 'fu' and 'futrig'. If 'matching', simplify the | |
| expression recursively by pattern matching. If 'groebner', apply an | |
| experimental groebner basis algorithm. In this case further options | |
| are forwarded to ``trigsimp_groebner``, please refer to its docstring. | |
| If 'combined', first run the groebner basis algorithm with small | |
| default parameters, then run the 'matching' algorithm. 'fu' runs the | |
| collection of trigonometric transformations described by Fu, et al. | |
| (see the `fu` docstring) while `futrig` runs a subset of Fu-transforms | |
| that mimic the behavior of `trigsimp`. | |
| compare: | |
| - show input and output from `trigsimp` and `futrig` when different, | |
| but returns the `trigsimp` value. | |
| Examples | |
| ======== | |
| >>> from sympy import trigsimp, sin, cos, log, cot | |
| >>> from sympy.abc import x | |
| >>> e = 2*sin(x)**2 + 2*cos(x)**2 | |
| >>> trigsimp(e, old=True) | |
| 2 | |
| >>> trigsimp(log(e), old=True) | |
| log(2*sin(x)**2 + 2*cos(x)**2) | |
| >>> trigsimp(log(e), deep=True, old=True) | |
| log(2) | |
| Using `method="groebner"` (or `"combined"`) can sometimes lead to a lot | |
| more simplification: | |
| >>> e = (-sin(x) + 1)/cos(x) + cos(x)/(-sin(x) + 1) | |
| >>> trigsimp(e, old=True) | |
| (1 - sin(x))/cos(x) + cos(x)/(1 - sin(x)) | |
| >>> trigsimp(e, method="groebner", old=True) | |
| 2/cos(x) | |
| >>> trigsimp(1/cot(x)**2, compare=True, old=True) | |
| futrig: tan(x)**2 | |
| cot(x)**(-2) | |
| """ | |
| old = expr | |
| if first: | |
| if not expr.has(*_trigs): | |
| return expr | |
| trigsyms = set().union(*[t.free_symbols for t in expr.atoms(*_trigs)]) | |
| if len(trigsyms) > 1: | |
| from sympy.simplify.simplify import separatevars | |
| d = separatevars(expr) | |
| if d.is_Mul: | |
| d = separatevars(d, dict=True) or d | |
| if isinstance(d, dict): | |
| expr = 1 | |
| for v in d.values(): | |
| # remove hollow factoring | |
| was = v | |
| v = expand_mul(v) | |
| opts['first'] = False | |
| vnew = trigsimp(v, **opts) | |
| if vnew == v: | |
| vnew = was | |
| expr *= vnew | |
| old = expr | |
| else: | |
| if d.is_Add: | |
| for s in trigsyms: | |
| r, e = expr.as_independent(s) | |
| if r: | |
| opts['first'] = False | |
| expr = r + trigsimp(e, **opts) | |
| if not expr.is_Add: | |
| break | |
| old = expr | |
| recursive = opts.pop('recursive', False) | |
| deep = opts.pop('deep', False) | |
| method = opts.pop('method', 'matching') | |
| def groebnersimp(ex, deep, **opts): | |
| def traverse(e): | |
| if e.is_Atom: | |
| return e | |
| args = [traverse(x) for x in e.args] | |
| if e.is_Function or e.is_Pow: | |
| args = [trigsimp_groebner(x, **opts) for x in args] | |
| return e.func(*args) | |
| if deep: | |
| ex = traverse(ex) | |
| return trigsimp_groebner(ex, **opts) | |
| trigsimpfunc = { | |
| 'matching': (lambda x, d: _trigsimp(x, d)), | |
| 'groebner': (lambda x, d: groebnersimp(x, d, **opts)), | |
| 'combined': (lambda x, d: _trigsimp(groebnersimp(x, | |
| d, polynomial=True, hints=[2, tan]), | |
| d)) | |
| }[method] | |
| if recursive: | |
| w, g = cse(expr) | |
| g = trigsimpfunc(g[0], deep) | |
| for sub in reversed(w): | |
| g = g.subs(sub[0], sub[1]) | |
| g = trigsimpfunc(g, deep) | |
| result = g | |
| else: | |
| result = trigsimpfunc(expr, deep) | |
| if opts.get('compare', False): | |
| f = futrig(old) | |
| if f != result: | |
| print('\tfutrig:', f) | |
| return result | |
| def _dotrig(a, b): | |
| """Helper to tell whether ``a`` and ``b`` have the same sorts | |
| of symbols in them -- no need to test hyperbolic patterns against | |
| expressions that have no hyperbolics in them.""" | |
| return a.func == b.func and ( | |
| a.has(TrigonometricFunction) and b.has(TrigonometricFunction) or | |
| a.has(HyperbolicFunction) and b.has(HyperbolicFunction)) | |
| _trigpat = None | |
| def _trigpats(): | |
| global _trigpat | |
| a, b, c = symbols('a b c', cls=Wild) | |
| d = Wild('d', commutative=False) | |
| # for the simplifications like sinh/cosh -> tanh: | |
| # DO NOT REORDER THE FIRST 14 since these are assumed to be in this | |
| # order in _match_div_rewrite. | |
| matchers_division = ( | |
| (a*sin(b)**c/cos(b)**c, a*tan(b)**c, sin(b), cos(b)), | |
| (a*tan(b)**c*cos(b)**c, a*sin(b)**c, sin(b), cos(b)), | |
| (a*cot(b)**c*sin(b)**c, a*cos(b)**c, sin(b), cos(b)), | |
| (a*tan(b)**c/sin(b)**c, a/cos(b)**c, sin(b), cos(b)), | |
| (a*cot(b)**c/cos(b)**c, a/sin(b)**c, sin(b), cos(b)), | |
| (a*cot(b)**c*tan(b)**c, a, sin(b), cos(b)), | |
| (a*(cos(b) + 1)**c*(cos(b) - 1)**c, | |
| a*(-sin(b)**2)**c, cos(b) + 1, cos(b) - 1), | |
| (a*(sin(b) + 1)**c*(sin(b) - 1)**c, | |
| a*(-cos(b)**2)**c, sin(b) + 1, sin(b) - 1), | |
| (a*sinh(b)**c/cosh(b)**c, a*tanh(b)**c, S.One, S.One), | |
| (a*tanh(b)**c*cosh(b)**c, a*sinh(b)**c, S.One, S.One), | |
| (a*coth(b)**c*sinh(b)**c, a*cosh(b)**c, S.One, S.One), | |
| (a*tanh(b)**c/sinh(b)**c, a/cosh(b)**c, S.One, S.One), | |
| (a*coth(b)**c/cosh(b)**c, a/sinh(b)**c, S.One, S.One), | |
| (a*coth(b)**c*tanh(b)**c, a, S.One, S.One), | |
| (c*(tanh(a) + tanh(b))/(1 + tanh(a)*tanh(b)), | |
| tanh(a + b)*c, S.One, S.One), | |
| ) | |
| matchers_add = ( | |
| (c*sin(a)*cos(b) + c*cos(a)*sin(b) + d, sin(a + b)*c + d), | |
| (c*cos(a)*cos(b) - c*sin(a)*sin(b) + d, cos(a + b)*c + d), | |
| (c*sin(a)*cos(b) - c*cos(a)*sin(b) + d, sin(a - b)*c + d), | |
| (c*cos(a)*cos(b) + c*sin(a)*sin(b) + d, cos(a - b)*c + d), | |
| (c*sinh(a)*cosh(b) + c*sinh(b)*cosh(a) + d, sinh(a + b)*c + d), | |
| (c*cosh(a)*cosh(b) + c*sinh(a)*sinh(b) + d, cosh(a + b)*c + d), | |
| ) | |
| # for cos(x)**2 + sin(x)**2 -> 1 | |
| matchers_identity = ( | |
| (a*sin(b)**2, a - a*cos(b)**2), | |
| (a*tan(b)**2, a*(1/cos(b))**2 - a), | |
| (a*cot(b)**2, a*(1/sin(b))**2 - a), | |
| (a*sin(b + c), a*(sin(b)*cos(c) + sin(c)*cos(b))), | |
| (a*cos(b + c), a*(cos(b)*cos(c) - sin(b)*sin(c))), | |
| (a*tan(b + c), a*((tan(b) + tan(c))/(1 - tan(b)*tan(c)))), | |
| (a*sinh(b)**2, a*cosh(b)**2 - a), | |
| (a*tanh(b)**2, a - a*(1/cosh(b))**2), | |
| (a*coth(b)**2, a + a*(1/sinh(b))**2), | |
| (a*sinh(b + c), a*(sinh(b)*cosh(c) + sinh(c)*cosh(b))), | |
| (a*cosh(b + c), a*(cosh(b)*cosh(c) + sinh(b)*sinh(c))), | |
| (a*tanh(b + c), a*((tanh(b) + tanh(c))/(1 + tanh(b)*tanh(c)))), | |
| ) | |
| # Reduce any lingering artifacts, such as sin(x)**2 changing | |
| # to 1-cos(x)**2 when sin(x)**2 was "simpler" | |
| artifacts = ( | |
| (a - a*cos(b)**2 + c, a*sin(b)**2 + c, cos), | |
| (a - a*(1/cos(b))**2 + c, -a*tan(b)**2 + c, cos), | |
| (a - a*(1/sin(b))**2 + c, -a*cot(b)**2 + c, sin), | |
| (a - a*cosh(b)**2 + c, -a*sinh(b)**2 + c, cosh), | |
| (a - a*(1/cosh(b))**2 + c, a*tanh(b)**2 + c, cosh), | |
| (a + a*(1/sinh(b))**2 + c, a*coth(b)**2 + c, sinh), | |
| # same as above but with noncommutative prefactor | |
| (a*d - a*d*cos(b)**2 + c, a*d*sin(b)**2 + c, cos), | |
| (a*d - a*d*(1/cos(b))**2 + c, -a*d*tan(b)**2 + c, cos), | |
| (a*d - a*d*(1/sin(b))**2 + c, -a*d*cot(b)**2 + c, sin), | |
| (a*d - a*d*cosh(b)**2 + c, -a*d*sinh(b)**2 + c, cosh), | |
| (a*d - a*d*(1/cosh(b))**2 + c, a*d*tanh(b)**2 + c, cosh), | |
| (a*d + a*d*(1/sinh(b))**2 + c, a*d*coth(b)**2 + c, sinh), | |
| ) | |
| _trigpat = (a, b, c, d, matchers_division, matchers_add, | |
| matchers_identity, artifacts) | |
| return _trigpat | |
| def _replace_mul_fpowxgpow(expr, f, g, rexp, h, rexph): | |
| """Helper for _match_div_rewrite. | |
| Replace f(b_)**c_*g(b_)**(rexp(c_)) with h(b)**rexph(c) if f(b_) | |
| and g(b_) are both positive or if c_ is an integer. | |
| """ | |
| # assert expr.is_Mul and expr.is_commutative and f != g | |
| fargs = defaultdict(int) | |
| gargs = defaultdict(int) | |
| args = [] | |
| for x in expr.args: | |
| if x.is_Pow or x.func in (f, g): | |
| b, e = x.as_base_exp() | |
| if b.is_positive or e.is_integer: | |
| if b.func == f: | |
| fargs[b.args[0]] += e | |
| continue | |
| elif b.func == g: | |
| gargs[b.args[0]] += e | |
| continue | |
| args.append(x) | |
| common = set(fargs) & set(gargs) | |
| hit = False | |
| while common: | |
| key = common.pop() | |
| fe = fargs.pop(key) | |
| ge = gargs.pop(key) | |
| if fe == rexp(ge): | |
| args.append(h(key)**rexph(fe)) | |
| hit = True | |
| else: | |
| fargs[key] = fe | |
| gargs[key] = ge | |
| if not hit: | |
| return expr | |
| while fargs: | |
| key, e = fargs.popitem() | |
| args.append(f(key)**e) | |
| while gargs: | |
| key, e = gargs.popitem() | |
| args.append(g(key)**e) | |
| return Mul(*args) | |
| _idn = lambda x: x | |
| _midn = lambda x: -x | |
| _one = lambda x: S.One | |
| def _match_div_rewrite(expr, i): | |
| """helper for __trigsimp""" | |
| if i == 0: | |
| expr = _replace_mul_fpowxgpow(expr, sin, cos, | |
| _midn, tan, _idn) | |
| elif i == 1: | |
| expr = _replace_mul_fpowxgpow(expr, tan, cos, | |
| _idn, sin, _idn) | |
| elif i == 2: | |
| expr = _replace_mul_fpowxgpow(expr, cot, sin, | |
| _idn, cos, _idn) | |
| elif i == 3: | |
| expr = _replace_mul_fpowxgpow(expr, tan, sin, | |
| _midn, cos, _midn) | |
| elif i == 4: | |
| expr = _replace_mul_fpowxgpow(expr, cot, cos, | |
| _midn, sin, _midn) | |
| elif i == 5: | |
| expr = _replace_mul_fpowxgpow(expr, cot, tan, | |
| _idn, _one, _idn) | |
| # i in (6, 7) is skipped | |
| elif i == 8: | |
| expr = _replace_mul_fpowxgpow(expr, sinh, cosh, | |
| _midn, tanh, _idn) | |
| elif i == 9: | |
| expr = _replace_mul_fpowxgpow(expr, tanh, cosh, | |
| _idn, sinh, _idn) | |
| elif i == 10: | |
| expr = _replace_mul_fpowxgpow(expr, coth, sinh, | |
| _idn, cosh, _idn) | |
| elif i == 11: | |
| expr = _replace_mul_fpowxgpow(expr, tanh, sinh, | |
| _midn, cosh, _midn) | |
| elif i == 12: | |
| expr = _replace_mul_fpowxgpow(expr, coth, cosh, | |
| _midn, sinh, _midn) | |
| elif i == 13: | |
| expr = _replace_mul_fpowxgpow(expr, coth, tanh, | |
| _idn, _one, _idn) | |
| else: | |
| return None | |
| return expr | |
| def _trigsimp(expr, deep=False): | |
| # protect the cache from non-trig patterns; we only allow | |
| # trig patterns to enter the cache | |
| if expr.has(*_trigs): | |
| return __trigsimp(expr, deep) | |
| return expr | |
| def __trigsimp(expr, deep=False): | |
| """recursive helper for trigsimp""" | |
| from sympy.simplify.fu import TR10i | |
| if _trigpat is None: | |
| _trigpats() | |
| a, b, c, d, matchers_division, matchers_add, \ | |
| matchers_identity, artifacts = _trigpat | |
| if expr.is_Mul: | |
| # do some simplifications like sin/cos -> tan: | |
| if not expr.is_commutative: | |
| com, nc = expr.args_cnc() | |
| expr = _trigsimp(Mul._from_args(com), deep)*Mul._from_args(nc) | |
| else: | |
| for i, (pattern, simp, ok1, ok2) in enumerate(matchers_division): | |
| if not _dotrig(expr, pattern): | |
| continue | |
| newexpr = _match_div_rewrite(expr, i) | |
| if newexpr is not None: | |
| if newexpr != expr: | |
| expr = newexpr | |
| break | |
| else: | |
| continue | |
| # use SymPy matching instead | |
| res = expr.match(pattern) | |
| if res and res.get(c, 0): | |
| if not res[c].is_integer: | |
| ok = ok1.subs(res) | |
| if not ok.is_positive: | |
| continue | |
| ok = ok2.subs(res) | |
| if not ok.is_positive: | |
| continue | |
| # if "a" contains any of trig or hyperbolic funcs with | |
| # argument "b" then skip the simplification | |
| if any(w.args[0] == res[b] for w in res[a].atoms( | |
| TrigonometricFunction, HyperbolicFunction)): | |
| continue | |
| # simplify and finish: | |
| expr = simp.subs(res) | |
| break # process below | |
| if expr.is_Add: | |
| args = [] | |
| for term in expr.args: | |
| if not term.is_commutative: | |
| com, nc = term.args_cnc() | |
| nc = Mul._from_args(nc) | |
| term = Mul._from_args(com) | |
| else: | |
| nc = S.One | |
| term = _trigsimp(term, deep) | |
| for pattern, result in matchers_identity: | |
| res = term.match(pattern) | |
| if res is not None: | |
| term = result.subs(res) | |
| break | |
| args.append(term*nc) | |
| if args != expr.args: | |
| expr = Add(*args) | |
| expr = min(expr, expand(expr), key=count_ops) | |
| if expr.is_Add: | |
| for pattern, result in matchers_add: | |
| if not _dotrig(expr, pattern): | |
| continue | |
| expr = TR10i(expr) | |
| if expr.has(HyperbolicFunction): | |
| res = expr.match(pattern) | |
| # if "d" contains any trig or hyperbolic funcs with | |
| # argument "a" or "b" then skip the simplification; | |
| # this isn't perfect -- see tests | |
| if res is None or not (a in res and b in res) or any( | |
| w.args[0] in (res[a], res[b]) for w in res[d].atoms( | |
| TrigonometricFunction, HyperbolicFunction)): | |
| continue | |
| expr = result.subs(res) | |
| break | |
| # Reduce any lingering artifacts, such as sin(x)**2 changing | |
| # to 1 - cos(x)**2 when sin(x)**2 was "simpler" | |
| for pattern, result, ex in artifacts: | |
| if not _dotrig(expr, pattern): | |
| continue | |
| # Substitute a new wild that excludes some function(s) | |
| # to help influence a better match. This is because | |
| # sometimes, for example, 'a' would match sec(x)**2 | |
| a_t = Wild('a', exclude=[ex]) | |
| pattern = pattern.subs(a, a_t) | |
| result = result.subs(a, a_t) | |
| m = expr.match(pattern) | |
| was = None | |
| while m and was != expr: | |
| was = expr | |
| if m[a_t] == 0 or \ | |
| -m[a_t] in m[c].args or m[a_t] + m[c] == 0: | |
| break | |
| if d in m and m[a_t]*m[d] + m[c] == 0: | |
| break | |
| expr = result.subs(m) | |
| m = expr.match(pattern) | |
| m.setdefault(c, S.Zero) | |
| elif expr.is_Mul or expr.is_Pow or deep and expr.args: | |
| expr = expr.func(*[_trigsimp(a, deep) for a in expr.args]) | |
| try: | |
| if not expr.has(*_trigs): | |
| raise TypeError | |
| e = expr.atoms(exp) | |
| new = expr.rewrite(exp, deep=deep) | |
| if new == e: | |
| raise TypeError | |
| fnew = factor(new) | |
| if fnew != new: | |
| new = min([new, factor(new)], key=count_ops) | |
| # if all exp that were introduced disappeared then accept it | |
| if not (new.atoms(exp) - e): | |
| expr = new | |
| except TypeError: | |
| pass | |
| return expr | |
| #------------------- end of old trigsimp routines -------------------- | |
| def futrig(e, *, hyper=True, **kwargs): | |
| """Return simplified ``e`` using Fu-like transformations. | |
| This is not the "Fu" algorithm. This is called by default | |
| from ``trigsimp``. By default, hyperbolics subexpressions | |
| will be simplified, but this can be disabled by setting | |
| ``hyper=False``. | |
| Examples | |
| ======== | |
| >>> from sympy import trigsimp, tan, sinh, tanh | |
| >>> from sympy.simplify.trigsimp import futrig | |
| >>> from sympy.abc import x | |
| >>> trigsimp(1/tan(x)**2) | |
| tan(x)**(-2) | |
| >>> futrig(sinh(x)/tanh(x)) | |
| cosh(x) | |
| """ | |
| from sympy.simplify.fu import hyper_as_trig | |
| e = sympify(e) | |
| if not isinstance(e, Basic): | |
| return e | |
| if not e.args: | |
| return e | |
| old = e | |
| e = bottom_up(e, _futrig) | |
| if hyper and e.has(HyperbolicFunction): | |
| e, f = hyper_as_trig(e) | |
| e = f(bottom_up(e, _futrig)) | |
| if e != old and e.is_Mul and e.args[0].is_Rational: | |
| # redistribute leading coeff on 2-arg Add | |
| e = Mul(*e.as_coeff_Mul()) | |
| return e | |
| def _futrig(e): | |
| """Helper for futrig.""" | |
| from sympy.simplify.fu import ( | |
| TR1, TR2, TR3, TR2i, TR10, L, TR10i, | |
| TR8, TR6, TR15, TR16, TR111, TR5, TRmorrie, TR11, _TR11, TR14, TR22, | |
| TR12) | |
| if not e.has(TrigonometricFunction): | |
| return e | |
| if e.is_Mul: | |
| coeff, e = e.as_independent(TrigonometricFunction) | |
| else: | |
| coeff = None | |
| Lops = lambda x: (L(x), x.count_ops(), _nodes(x), len(x.args), x.is_Add) | |
| trigs = lambda x: x.has(TrigonometricFunction) | |
| tree = [identity, | |
| ( | |
| TR3, # canonical angles | |
| TR1, # sec-csc -> cos-sin | |
| TR12, # expand tan of sum | |
| lambda x: _eapply(factor, x, trigs), | |
| TR2, # tan-cot -> sin-cos | |
| [identity, lambda x: _eapply(_mexpand, x, trigs)], | |
| TR2i, # sin-cos ratio -> tan | |
| lambda x: _eapply(lambda i: factor(i.normal()), x, trigs), | |
| TR14, # factored identities | |
| TR5, # sin-pow -> cos_pow | |
| TR10, # sin-cos of sums -> sin-cos prod | |
| TR11, _TR11, TR6, # reduce double angles and rewrite cos pows | |
| lambda x: _eapply(factor, x, trigs), | |
| TR14, # factored powers of identities | |
| [identity, lambda x: _eapply(_mexpand, x, trigs)], | |
| TR10i, # sin-cos products > sin-cos of sums | |
| TRmorrie, | |
| [identity, TR8], # sin-cos products -> sin-cos of sums | |
| [identity, lambda x: TR2i(TR2(x))], # tan -> sin-cos -> tan | |
| [ | |
| lambda x: _eapply(expand_mul, TR5(x), trigs), | |
| lambda x: _eapply( | |
| expand_mul, TR15(x), trigs)], # pos/neg powers of sin | |
| [ | |
| lambda x: _eapply(expand_mul, TR6(x), trigs), | |
| lambda x: _eapply( | |
| expand_mul, TR16(x), trigs)], # pos/neg powers of cos | |
| TR111, # tan, sin, cos to neg power -> cot, csc, sec | |
| [identity, TR2i], # sin-cos ratio to tan | |
| [identity, lambda x: _eapply( | |
| expand_mul, TR22(x), trigs)], # tan-cot to sec-csc | |
| TR1, TR2, TR2i, | |
| [identity, lambda x: _eapply( | |
| factor_terms, TR12(x), trigs)], # expand tan of sum | |
| )] | |
| e = greedy(tree, objective=Lops)(e) | |
| if coeff is not None: | |
| e = coeff * e | |
| return e | |
| def _is_Expr(e): | |
| """_eapply helper to tell whether ``e`` and all its args | |
| are Exprs.""" | |
| if isinstance(e, Derivative): | |
| return _is_Expr(e.expr) | |
| if not isinstance(e, Expr): | |
| return False | |
| return all(_is_Expr(i) for i in e.args) | |
| def _eapply(func, e, cond=None): | |
| """Apply ``func`` to ``e`` if all args are Exprs else only | |
| apply it to those args that *are* Exprs.""" | |
| if not isinstance(e, Expr): | |
| return e | |
| if _is_Expr(e) or not e.args: | |
| return func(e) | |
| return e.func(*[ | |
| _eapply(func, ei) if (cond is None or cond(ei)) else ei | |
| for ei in e.args]) | |