Coverage for /usr/lib/python3/dist-packages/sympy/simplify/trigsimp.py: 8%

530 statements  

« prev     ^ index     » next       coverage.py v7.9.1, created at 2025-06-14 15:55 +0200

1from collections import defaultdict 

2from functools import reduce 

3 

4from sympy.core import (sympify, Basic, S, Expr, factor_terms, 

5 Mul, Add, bottom_up) 

6from sympy.core.cache import cacheit 

7from sympy.core.function import (count_ops, _mexpand, FunctionClass, expand, 

8 expand_mul, _coeff_isneg, Derivative) 

9from sympy.core.numbers import I, Integer, igcd 

10from sympy.core.sorting import _nodes 

11from sympy.core.symbol import Dummy, symbols, Wild 

12from sympy.external.gmpy import SYMPY_INTS 

13from sympy.functions import sin, cos, exp, cosh, tanh, sinh, tan, cot, coth 

14from sympy.functions import atan2 

15from sympy.functions.elementary.hyperbolic import HyperbolicFunction 

16from sympy.functions.elementary.trigonometric import TrigonometricFunction 

17from sympy.polys import Poly, factor, cancel, parallel_poly_from_expr 

18from sympy.polys.domains import ZZ 

19from sympy.polys.polyerrors import PolificationFailed 

20from sympy.polys.polytools import groebner 

21from sympy.simplify.cse_main import cse 

22from sympy.strategies.core import identity 

23from sympy.strategies.tree import greedy 

24from sympy.utilities.iterables import iterable 

25from sympy.utilities.misc import debug 

26 

27def trigsimp_groebner(expr, hints=[], quick=False, order="grlex", 

28 polynomial=False): 

29 """ 

30 Simplify trigonometric expressions using a groebner basis algorithm. 

31 

32 Explanation 

33 =========== 

34 

35 This routine takes a fraction involving trigonometric or hyperbolic 

36 expressions, and tries to simplify it. The primary metric is the 

37 total degree. Some attempts are made to choose the simplest possible 

38 expression of the minimal degree, but this is non-rigorous, and also 

39 very slow (see the ``quick=True`` option). 

40 

41 If ``polynomial`` is set to True, instead of simplifying numerator and 

42 denominator together, this function just brings numerator and denominator 

43 into a canonical form. This is much faster, but has potentially worse 

44 results. However, if the input is a polynomial, then the result is 

45 guaranteed to be an equivalent polynomial of minimal degree. 

46 

47 The most important option is hints. Its entries can be any of the 

48 following: 

49 

50 - a natural number 

51 - a function 

52 - an iterable of the form (func, var1, var2, ...) 

53 - anything else, interpreted as a generator 

54 

55 A number is used to indicate that the search space should be increased. 

56 A function is used to indicate that said function is likely to occur in a 

57 simplified expression. 

58 An iterable is used indicate that func(var1 + var2 + ...) is likely to 

59 occur in a simplified . 

60 An additional generator also indicates that it is likely to occur. 

61 (See examples below). 

62 

63 This routine carries out various computationally intensive algorithms. 

64 The option ``quick=True`` can be used to suppress one particularly slow 

65 step (at the expense of potentially more complicated results, but never at 

66 the expense of increased total degree). 

67 

68 Examples 

69 ======== 

70 

71 >>> from sympy.abc import x, y 

72 >>> from sympy import sin, tan, cos, sinh, cosh, tanh 

73 >>> from sympy.simplify.trigsimp import trigsimp_groebner 

74 

75 Suppose you want to simplify ``sin(x)*cos(x)``. Naively, nothing happens: 

76 

77 >>> ex = sin(x)*cos(x) 

78 >>> trigsimp_groebner(ex) 

79 sin(x)*cos(x) 

80 

81 This is because ``trigsimp_groebner`` only looks for a simplification 

82 involving just ``sin(x)`` and ``cos(x)``. You can tell it to also try 

83 ``2*x`` by passing ``hints=[2]``: 

84 

85 >>> trigsimp_groebner(ex, hints=[2]) 

86 sin(2*x)/2 

87 >>> trigsimp_groebner(sin(x)**2 - cos(x)**2, hints=[2]) 

88 -cos(2*x) 

89 

90 Increasing the search space this way can quickly become expensive. A much 

91 faster way is to give a specific expression that is likely to occur: 

92 

93 >>> trigsimp_groebner(ex, hints=[sin(2*x)]) 

94 sin(2*x)/2 

95 

96 Hyperbolic expressions are similarly supported: 

97 

98 >>> trigsimp_groebner(sinh(2*x)/sinh(x)) 

99 2*cosh(x) 

100 

101 Note how no hints had to be passed, since the expression already involved 

102 ``2*x``. 

103 

104 The tangent function is also supported. You can either pass ``tan`` in the 

105 hints, to indicate that tan should be tried whenever cosine or sine are, 

106 or you can pass a specific generator: 

107 

108 >>> trigsimp_groebner(sin(x)/cos(x), hints=[tan]) 

109 tan(x) 

110 >>> trigsimp_groebner(sinh(x)/cosh(x), hints=[tanh(x)]) 

111 tanh(x) 

112 

113 Finally, you can use the iterable form to suggest that angle sum formulae 

114 should be tried: 

115 

116 >>> ex = (tan(x) + tan(y))/(1 - tan(x)*tan(y)) 

117 >>> trigsimp_groebner(ex, hints=[(tan, x, y)]) 

118 tan(x + y) 

119 """ 

120 # TODO 

121 # - preprocess by replacing everything by funcs we can handle 

122 # - optionally use cot instead of tan 

123 # - more intelligent hinting. 

124 # For example, if the ideal is small, and we have sin(x), sin(y), 

125 # add sin(x + y) automatically... ? 

126 # - algebraic numbers ... 

127 # - expressions of lowest degree are not distinguished properly 

128 # e.g. 1 - sin(x)**2 

129 # - we could try to order the generators intelligently, so as to influence 

130 # which monomials appear in the quotient basis 

131 

132 # THEORY 

133 # ------ 

134 # Ratsimpmodprime above can be used to "simplify" a rational function 

135 # modulo a prime ideal. "Simplify" mainly means finding an equivalent 

136 # expression of lower total degree. 

137 # 

138 # We intend to use this to simplify trigonometric functions. To do that, 

139 # we need to decide (a) which ring to use, and (b) modulo which ideal to 

140 # simplify. In practice, (a) means settling on a list of "generators" 

141 # a, b, c, ..., such that the fraction we want to simplify is a rational 

142 # function in a, b, c, ..., with coefficients in ZZ (integers). 

143 # (2) means that we have to decide what relations to impose on the 

144 # generators. There are two practical problems: 

145 # (1) The ideal has to be *prime* (a technical term). 

146 # (2) The relations have to be polynomials in the generators. 

147 # 

148 # We typically have two kinds of generators: 

149 # - trigonometric expressions, like sin(x), cos(5*x), etc 

150 # - "everything else", like gamma(x), pi, etc. 

151 # 

152 # Since this function is trigsimp, we will concentrate on what to do with 

153 # trigonometric expressions. We can also simplify hyperbolic expressions, 

154 # but the extensions should be clear. 

155 # 

156 # One crucial point is that all *other* generators really should behave 

157 # like indeterminates. In particular if (say) "I" is one of them, then 

158 # in fact I**2 + 1 = 0 and we may and will compute non-sensical 

159 # expressions. However, we can work with a dummy and add the relation 

160 # I**2 + 1 = 0 to our ideal, then substitute back in the end. 

161 # 

162 # Now regarding trigonometric generators. We split them into groups, 

163 # according to the argument of the trigonometric functions. We want to 

164 # organise this in such a way that most trigonometric identities apply in 

165 # the same group. For example, given sin(x), cos(2*x) and cos(y), we would 

166 # group as [sin(x), cos(2*x)] and [cos(y)]. 

167 # 

168 # Our prime ideal will be built in three steps: 

169 # (1) For each group, compute a "geometrically prime" ideal of relations. 

170 # Geometrically prime means that it generates a prime ideal in 

171 # CC[gens], not just ZZ[gens]. 

172 # (2) Take the union of all the generators of the ideals for all groups. 

173 # By the geometric primality condition, this is still prime. 

174 # (3) Add further inter-group relations which preserve primality. 

175 # 

176 # Step (1) works as follows. We will isolate common factors in the 

177 # argument, so that all our generators are of the form sin(n*x), cos(n*x) 

178 # or tan(n*x), with n an integer. Suppose first there are no tan terms. 

179 # The ideal [sin(x)**2 + cos(x)**2 - 1] is geometrically prime, since 

180 # X**2 + Y**2 - 1 is irreducible over CC. 

181 # Now, if we have a generator sin(n*x), than we can, using trig identities, 

182 # express sin(n*x) as a polynomial in sin(x) and cos(x). We can add this 

183 # relation to the ideal, preserving geometric primality, since the quotient 

184 # ring is unchanged. 

185 # Thus we have treated all sin and cos terms. 

186 # For tan(n*x), we add a relation tan(n*x)*cos(n*x) - sin(n*x) = 0. 

187 # (This requires of course that we already have relations for cos(n*x) and 

188 # sin(n*x).) It is not obvious, but it seems that this preserves geometric 

189 # primality. 

190 # XXX A real proof would be nice. HELP! 

191 # Sketch that <S**2 + C**2 - 1, C*T - S> is a prime ideal of 

192 # CC[S, C, T]: 

193 # - it suffices to show that the projective closure in CP**3 is 

194 # irreducible 

195 # - using the half-angle substitutions, we can express sin(x), tan(x), 

196 # cos(x) as rational functions in tan(x/2) 

197 # - from this, we get a rational map from CP**1 to our curve 

198 # - this is a morphism, hence the curve is prime 

199 # 

200 # Step (2) is trivial. 

201 # 

202 # Step (3) works by adding selected relations of the form 

203 # sin(x + y) - sin(x)*cos(y) - sin(y)*cos(x), etc. Geometric primality is 

204 # preserved by the same argument as before. 

205 

206 def parse_hints(hints): 

207 """Split hints into (n, funcs, iterables, gens).""" 

208 n = 1 

209 funcs, iterables, gens = [], [], [] 

210 for e in hints: 

211 if isinstance(e, (SYMPY_INTS, Integer)): 

212 n = e 

213 elif isinstance(e, FunctionClass): 

214 funcs.append(e) 

215 elif iterable(e): 

216 iterables.append((e[0], e[1:])) 

217 # XXX sin(x+2y)? 

218 # Note: we go through polys so e.g. 

219 # sin(-x) -> -sin(x) -> sin(x) 

220 gens.extend(parallel_poly_from_expr( 

221 [e[0](x) for x in e[1:]] + [e[0](Add(*e[1:]))])[1].gens) 

222 else: 

223 gens.append(e) 

224 return n, funcs, iterables, gens 

225 

226 def build_ideal(x, terms): 

227 """ 

228 Build generators for our ideal. ``Terms`` is an iterable with elements of 

229 the form (fn, coeff), indicating that we have a generator fn(coeff*x). 

230 

231 If any of the terms is trigonometric, sin(x) and cos(x) are guaranteed 

232 to appear in terms. Similarly for hyperbolic functions. For tan(n*x), 

233 sin(n*x) and cos(n*x) are guaranteed. 

234 """ 

235 I = [] 

236 y = Dummy('y') 

237 for fn, coeff in terms: 

238 for c, s, t, rel in ( 

239 [cos, sin, tan, cos(x)**2 + sin(x)**2 - 1], 

240 [cosh, sinh, tanh, cosh(x)**2 - sinh(x)**2 - 1]): 

241 if coeff == 1 and fn in [c, s]: 

242 I.append(rel) 

243 elif fn == t: 

244 I.append(t(coeff*x)*c(coeff*x) - s(coeff*x)) 

245 elif fn in [c, s]: 

246 cn = fn(coeff*y).expand(trig=True).subs(y, x) 

247 I.append(fn(coeff*x) - cn) 

248 return list(set(I)) 

249 

250 def analyse_gens(gens, hints): 

251 """ 

252 Analyse the generators ``gens``, using the hints ``hints``. 

253 

254 The meaning of ``hints`` is described in the main docstring. 

255 Return a new list of generators, and also the ideal we should 

256 work with. 

257 """ 

258 # First parse the hints 

259 n, funcs, iterables, extragens = parse_hints(hints) 

260 debug('n=%s funcs: %s iterables: %s extragens: %s', 

261 (funcs, iterables, extragens)) 

262 

263 # We just add the extragens to gens and analyse them as before 

264 gens = list(gens) 

265 gens.extend(extragens) 

266 

267 # remove duplicates 

268 funcs = list(set(funcs)) 

269 iterables = list(set(iterables)) 

270 gens = list(set(gens)) 

271 

272 # all the functions we can do anything with 

273 allfuncs = {sin, cos, tan, sinh, cosh, tanh} 

274 # sin(3*x) -> ((3, x), sin) 

275 trigterms = [(g.args[0].as_coeff_mul(), g.func) for g in gens 

276 if g.func in allfuncs] 

277 # Our list of new generators - start with anything that we cannot 

278 # work with (i.e. is not a trigonometric term) 

279 freegens = [g for g in gens if g.func not in allfuncs] 

280 newgens = [] 

281 trigdict = {} 

282 for (coeff, var), fn in trigterms: 

283 trigdict.setdefault(var, []).append((coeff, fn)) 

284 res = [] # the ideal 

285 

286 for key, val in trigdict.items(): 

287 # We have now assembeled a dictionary. Its keys are common 

288 # arguments in trigonometric expressions, and values are lists of 

289 # pairs (fn, coeff). x0, (fn, coeff) in trigdict means that we 

290 # need to deal with fn(coeff*x0). We take the rational gcd of the 

291 # coeffs, call it ``gcd``. We then use x = x0/gcd as "base symbol", 

292 # all other arguments are integral multiples thereof. 

293 # We will build an ideal which works with sin(x), cos(x). 

294 # If hint tan is provided, also work with tan(x). Moreover, if 

295 # n > 1, also work with sin(k*x) for k <= n, and similarly for cos 

296 # (and tan if the hint is provided). Finally, any generators which 

297 # the ideal does not work with but we need to accommodate (either 

298 # because it was in expr or because it was provided as a hint) 

299 # we also build into the ideal. 

300 # This selection process is expressed in the list ``terms``. 

301 # build_ideal then generates the actual relations in our ideal, 

302 # from this list. 

303 fns = [x[1] for x in val] 

304 val = [x[0] for x in val] 

305 gcd = reduce(igcd, val) 

306 terms = [(fn, v/gcd) for (fn, v) in zip(fns, val)] 

307 fs = set(funcs + fns) 

308 for c, s, t in ([cos, sin, tan], [cosh, sinh, tanh]): 

309 if any(x in fs for x in (c, s, t)): 

310 fs.add(c) 

311 fs.add(s) 

312 for fn in fs: 

313 for k in range(1, n + 1): 

314 terms.append((fn, k)) 

315 extra = [] 

316 for fn, v in terms: 

317 if fn == tan: 

318 extra.append((sin, v)) 

319 extra.append((cos, v)) 

320 if fn in [sin, cos] and tan in fs: 

321 extra.append((tan, v)) 

322 if fn == tanh: 

323 extra.append((sinh, v)) 

324 extra.append((cosh, v)) 

325 if fn in [sinh, cosh] and tanh in fs: 

326 extra.append((tanh, v)) 

327 terms.extend(extra) 

328 x = gcd*Mul(*key) 

329 r = build_ideal(x, terms) 

330 res.extend(r) 

331 newgens.extend({fn(v*x) for fn, v in terms}) 

332 

333 # Add generators for compound expressions from iterables 

334 for fn, args in iterables: 

335 if fn == tan: 

336 # Tan expressions are recovered from sin and cos. 

337 iterables.extend([(sin, args), (cos, args)]) 

338 elif fn == tanh: 

339 # Tanh expressions are recovered from sihn and cosh. 

340 iterables.extend([(sinh, args), (cosh, args)]) 

341 else: 

342 dummys = symbols('d:%i' % len(args), cls=Dummy) 

343 expr = fn( Add(*dummys)).expand(trig=True).subs(list(zip(dummys, args))) 

344 res.append(fn(Add(*args)) - expr) 

345 

346 if myI in gens: 

347 res.append(myI**2 + 1) 

348 freegens.remove(myI) 

349 newgens.append(myI) 

350 

351 return res, freegens, newgens 

352 

353 myI = Dummy('I') 

354 expr = expr.subs(S.ImaginaryUnit, myI) 

355 subs = [(myI, S.ImaginaryUnit)] 

356 

357 num, denom = cancel(expr).as_numer_denom() 

358 try: 

359 (pnum, pdenom), opt = parallel_poly_from_expr([num, denom]) 

360 except PolificationFailed: 

361 return expr 

362 debug('initial gens:', opt.gens) 

363 ideal, freegens, gens = analyse_gens(opt.gens, hints) 

364 debug('ideal:', ideal) 

365 debug('new gens:', gens, " -- len", len(gens)) 

366 debug('free gens:', freegens, " -- len", len(gens)) 

367 # NOTE we force the domain to be ZZ to stop polys from injecting generators 

368 # (which is usually a sign of a bug in the way we build the ideal) 

369 if not gens: 

370 return expr 

371 G = groebner(ideal, order=order, gens=gens, domain=ZZ) 

372 debug('groebner basis:', list(G), " -- len", len(G)) 

373 

374 # If our fraction is a polynomial in the free generators, simplify all 

375 # coefficients separately: 

376 

377 from sympy.simplify.ratsimp import ratsimpmodprime 

378 

379 if freegens and pdenom.has_only_gens(*set(gens).intersection(pdenom.gens)): 

380 num = Poly(num, gens=gens+freegens).eject(*gens) 

381 res = [] 

382 for monom, coeff in num.terms(): 

383 ourgens = set(parallel_poly_from_expr([coeff, denom])[1].gens) 

384 # We compute the transitive closure of all generators that can 

385 # be reached from our generators through relations in the ideal. 

386 changed = True 

387 while changed: 

388 changed = False 

389 for p in ideal: 

390 p = Poly(p) 

391 if not ourgens.issuperset(p.gens) and \ 

392 not p.has_only_gens(*set(p.gens).difference(ourgens)): 

393 changed = True 

394 ourgens.update(p.exclude().gens) 

395 # NOTE preserve order! 

396 realgens = [x for x in gens if x in ourgens] 

397 # The generators of the ideal have now been (implicitly) split 

398 # into two groups: those involving ourgens and those that don't. 

399 # Since we took the transitive closure above, these two groups 

400 # live in subgrings generated by a *disjoint* set of variables. 

401 # Any sensible groebner basis algorithm will preserve this disjoint 

402 # structure (i.e. the elements of the groebner basis can be split 

403 # similarly), and and the two subsets of the groebner basis then 

404 # form groebner bases by themselves. (For the smaller generating 

405 # sets, of course.) 

406 ourG = [g.as_expr() for g in G.polys if 

407 g.has_only_gens(*ourgens.intersection(g.gens))] 

408 res.append(Mul(*[a**b for a, b in zip(freegens, monom)]) * \ 

409 ratsimpmodprime(coeff/denom, ourG, order=order, 

410 gens=realgens, quick=quick, domain=ZZ, 

411 polynomial=polynomial).subs(subs)) 

412 return Add(*res) 

413 # NOTE The following is simpler and has less assumptions on the 

414 # groebner basis algorithm. If the above turns out to be broken, 

415 # use this. 

416 return Add(*[Mul(*[a**b for a, b in zip(freegens, monom)]) * \ 

417 ratsimpmodprime(coeff/denom, list(G), order=order, 

418 gens=gens, quick=quick, domain=ZZ) 

419 for monom, coeff in num.terms()]) 

420 else: 

421 return ratsimpmodprime( 

422 expr, list(G), order=order, gens=freegens+gens, 

423 quick=quick, domain=ZZ, polynomial=polynomial).subs(subs) 

424 

425 

426_trigs = (TrigonometricFunction, HyperbolicFunction) 

427 

428 

429def _trigsimp_inverse(rv): 

430 

431 def check_args(x, y): 

432 try: 

433 return x.args[0] == y.args[0] 

434 except IndexError: 

435 return False 

436 

437 def f(rv): 

438 # for simple functions 

439 g = getattr(rv, 'inverse', None) 

440 if (g is not None and isinstance(rv.args[0], g()) and 

441 isinstance(g()(1), TrigonometricFunction)): 

442 return rv.args[0].args[0] 

443 

444 # for atan2 simplifications, harder because atan2 has 2 args 

445 if isinstance(rv, atan2): 

446 y, x = rv.args 

447 if _coeff_isneg(y): 

448 return -f(atan2(-y, x)) 

449 elif _coeff_isneg(x): 

450 return S.Pi - f(atan2(y, -x)) 

451 

452 if check_args(x, y): 

453 if isinstance(y, sin) and isinstance(x, cos): 

454 return x.args[0] 

455 if isinstance(y, cos) and isinstance(x, sin): 

456 return S.Pi / 2 - x.args[0] 

457 

458 return rv 

459 

460 return bottom_up(rv, f) 

461 

462 

463def trigsimp(expr, inverse=False, **opts): 

464 """Returns a reduced expression by using known trig identities. 

465 

466 Parameters 

467 ========== 

468 

469 inverse : bool, optional 

470 If ``inverse=True``, it will be assumed that a composition of inverse 

471 functions, such as sin and asin, can be cancelled in any order. 

472 For example, ``asin(sin(x))`` will yield ``x`` without checking whether 

473 x belongs to the set where this relation is true. The default is False. 

474 Default : True 

475 

476 method : string, optional 

477 Specifies the method to use. Valid choices are: 

478 

479 - ``'matching'``, default 

480 - ``'groebner'`` 

481 - ``'combined'`` 

482 - ``'fu'`` 

483 - ``'old'`` 

484 

485 If ``'matching'``, simplify the expression recursively by targeting 

486 common patterns. If ``'groebner'``, apply an experimental groebner 

487 basis algorithm. In this case further options are forwarded to 

488 ``trigsimp_groebner``, please refer to 

489 its docstring. If ``'combined'``, it first runs the groebner basis 

490 algorithm with small default parameters, then runs the ``'matching'`` 

491 algorithm. If ``'fu'``, run the collection of trigonometric 

492 transformations described by Fu, et al. (see the 

493 :py:func:`~sympy.simplify.fu.fu` docstring). If ``'old'``, the original 

494 SymPy trig simplification function is run. 

495 opts : 

496 Optional keyword arguments passed to the method. See each method's 

497 function docstring for details. 

498 

499 Examples 

500 ======== 

501 

502 >>> from sympy import trigsimp, sin, cos, log 

503 >>> from sympy.abc import x 

504 >>> e = 2*sin(x)**2 + 2*cos(x)**2 

505 >>> trigsimp(e) 

506 2 

507 

508 Simplification occurs wherever trigonometric functions are located. 

509 

510 >>> trigsimp(log(e)) 

511 log(2) 

512 

513 Using ``method='groebner'`` (or ``method='combined'``) might lead to 

514 greater simplification. 

515 

516 The old trigsimp routine can be accessed as with method ``method='old'``. 

517 

518 >>> from sympy import coth, tanh 

519 >>> t = 3*tanh(x)**7 - 2/coth(x)**7 

520 >>> trigsimp(t, method='old') == t 

521 True 

522 >>> trigsimp(t) 

523 tanh(x)**7 

524 

525 """ 

526 from sympy.simplify.fu import fu 

527 

528 expr = sympify(expr) 

529 

530 _eval_trigsimp = getattr(expr, '_eval_trigsimp', None) 

531 if _eval_trigsimp is not None: 

532 return _eval_trigsimp(**opts) 

533 

534 old = opts.pop('old', False) 

535 if not old: 

536 opts.pop('deep', None) 

537 opts.pop('recursive', None) 

538 method = opts.pop('method', 'matching') 

539 else: 

540 method = 'old' 

541 

542 def groebnersimp(ex, **opts): 

543 def traverse(e): 

544 if e.is_Atom: 

545 return e 

546 args = [traverse(x) for x in e.args] 

547 if e.is_Function or e.is_Pow: 

548 args = [trigsimp_groebner(x, **opts) for x in args] 

549 return e.func(*args) 

550 new = traverse(ex) 

551 if not isinstance(new, Expr): 

552 return new 

553 return trigsimp_groebner(new, **opts) 

554 

555 trigsimpfunc = { 

556 'fu': (lambda x: fu(x, **opts)), 

557 'matching': (lambda x: futrig(x)), 

558 'groebner': (lambda x: groebnersimp(x, **opts)), 

559 'combined': (lambda x: futrig(groebnersimp(x, 

560 polynomial=True, hints=[2, tan]))), 

561 'old': lambda x: trigsimp_old(x, **opts), 

562 }[method] 

563 

564 expr_simplified = trigsimpfunc(expr) 

565 if inverse: 

566 expr_simplified = _trigsimp_inverse(expr_simplified) 

567 

568 return expr_simplified 

569 

570 

571def exptrigsimp(expr): 

572 """ 

573 Simplifies exponential / trigonometric / hyperbolic functions. 

574 

575 Examples 

576 ======== 

577 

578 >>> from sympy import exptrigsimp, exp, cosh, sinh 

579 >>> from sympy.abc import z 

580 

581 >>> exptrigsimp(exp(z) + exp(-z)) 

582 2*cosh(z) 

583 >>> exptrigsimp(cosh(z) - sinh(z)) 

584 exp(-z) 

585 """ 

586 from sympy.simplify.fu import hyper_as_trig, TR2i 

587 

588 def exp_trig(e): 

589 # select the better of e, and e rewritten in terms of exp or trig 

590 # functions 

591 choices = [e] 

592 if e.has(*_trigs): 

593 choices.append(e.rewrite(exp)) 

594 choices.append(e.rewrite(cos)) 

595 return min(*choices, key=count_ops) 

596 newexpr = bottom_up(expr, exp_trig) 

597 

598 def f(rv): 

599 if not rv.is_Mul: 

600 return rv 

601 commutative_part, noncommutative_part = rv.args_cnc() 

602 # Since as_powers_dict loses order information, 

603 # if there is more than one noncommutative factor, 

604 # it should only be used to simplify the commutative part. 

605 if (len(noncommutative_part) > 1): 

606 return f(Mul(*commutative_part))*Mul(*noncommutative_part) 

607 rvd = rv.as_powers_dict() 

608 newd = rvd.copy() 

609 

610 def signlog(expr, sign=S.One): 

611 if expr is S.Exp1: 

612 return sign, S.One 

613 elif isinstance(expr, exp) or (expr.is_Pow and expr.base == S.Exp1): 

614 return sign, expr.exp 

615 elif sign is S.One: 

616 return signlog(-expr, sign=-S.One) 

617 else: 

618 return None, None 

619 

620 ee = rvd[S.Exp1] 

621 for k in rvd: 

622 if k.is_Add and len(k.args) == 2: 

623 # k == c*(1 + sign*E**x) 

624 c = k.args[0] 

625 sign, x = signlog(k.args[1]/c) 

626 if not x: 

627 continue 

628 m = rvd[k] 

629 newd[k] -= m 

630 if ee == -x*m/2: 

631 # sinh and cosh 

632 newd[S.Exp1] -= ee 

633 ee = 0 

634 if sign == 1: 

635 newd[2*c*cosh(x/2)] += m 

636 else: 

637 newd[-2*c*sinh(x/2)] += m 

638 elif newd[1 - sign*S.Exp1**x] == -m: 

639 # tanh 

640 del newd[1 - sign*S.Exp1**x] 

641 if sign == 1: 

642 newd[-c/tanh(x/2)] += m 

643 else: 

644 newd[-c*tanh(x/2)] += m 

645 else: 

646 newd[1 + sign*S.Exp1**x] += m 

647 newd[c] += m 

648 

649 return Mul(*[k**newd[k] for k in newd]) 

650 newexpr = bottom_up(newexpr, f) 

651 

652 # sin/cos and sinh/cosh ratios to tan and tanh, respectively 

653 if newexpr.has(HyperbolicFunction): 

654 e, f = hyper_as_trig(newexpr) 

655 newexpr = f(TR2i(e)) 

656 if newexpr.has(TrigonometricFunction): 

657 newexpr = TR2i(newexpr) 

658 

659 # can we ever generate an I where there was none previously? 

660 if not (newexpr.has(I) and not expr.has(I)): 

661 expr = newexpr 

662 return expr 

663 

664#-------------------- the old trigsimp routines --------------------- 

665 

666def trigsimp_old(expr, *, first=True, **opts): 

667 """ 

668 Reduces expression by using known trig identities. 

669 

670 Notes 

671 ===== 

672 

673 deep: 

674 - Apply trigsimp inside all objects with arguments 

675 

676 recursive: 

677 - Use common subexpression elimination (cse()) and apply 

678 trigsimp recursively (this is quite expensive if the 

679 expression is large) 

680 

681 method: 

682 - Determine the method to use. Valid choices are 'matching' (default), 

683 'groebner', 'combined', 'fu' and 'futrig'. If 'matching', simplify the 

684 expression recursively by pattern matching. If 'groebner', apply an 

685 experimental groebner basis algorithm. In this case further options 

686 are forwarded to ``trigsimp_groebner``, please refer to its docstring. 

687 If 'combined', first run the groebner basis algorithm with small 

688 default parameters, then run the 'matching' algorithm. 'fu' runs the 

689 collection of trigonometric transformations described by Fu, et al. 

690 (see the `fu` docstring) while `futrig` runs a subset of Fu-transforms 

691 that mimic the behavior of `trigsimp`. 

692 

693 compare: 

694 - show input and output from `trigsimp` and `futrig` when different, 

695 but returns the `trigsimp` value. 

696 

697 Examples 

698 ======== 

699 

700 >>> from sympy import trigsimp, sin, cos, log, cot 

701 >>> from sympy.abc import x 

702 >>> e = 2*sin(x)**2 + 2*cos(x)**2 

703 >>> trigsimp(e, old=True) 

704 2 

705 >>> trigsimp(log(e), old=True) 

706 log(2*sin(x)**2 + 2*cos(x)**2) 

707 >>> trigsimp(log(e), deep=True, old=True) 

708 log(2) 

709 

710 Using `method="groebner"` (or `"combined"`) can sometimes lead to a lot 

711 more simplification: 

712 

713 >>> e = (-sin(x) + 1)/cos(x) + cos(x)/(-sin(x) + 1) 

714 >>> trigsimp(e, old=True) 

715 (1 - sin(x))/cos(x) + cos(x)/(1 - sin(x)) 

716 >>> trigsimp(e, method="groebner", old=True) 

717 2/cos(x) 

718 

719 >>> trigsimp(1/cot(x)**2, compare=True, old=True) 

720 futrig: tan(x)**2 

721 cot(x)**(-2) 

722 

723 """ 

724 old = expr 

725 if first: 

726 if not expr.has(*_trigs): 

727 return expr 

728 

729 trigsyms = set().union(*[t.free_symbols for t in expr.atoms(*_trigs)]) 

730 if len(trigsyms) > 1: 

731 from sympy.simplify.simplify import separatevars 

732 

733 d = separatevars(expr) 

734 if d.is_Mul: 

735 d = separatevars(d, dict=True) or d 

736 if isinstance(d, dict): 

737 expr = 1 

738 for k, v in d.items(): 

739 # remove hollow factoring 

740 was = v 

741 v = expand_mul(v) 

742 opts['first'] = False 

743 vnew = trigsimp(v, **opts) 

744 if vnew == v: 

745 vnew = was 

746 expr *= vnew 

747 old = expr 

748 else: 

749 if d.is_Add: 

750 for s in trigsyms: 

751 r, e = expr.as_independent(s) 

752 if r: 

753 opts['first'] = False 

754 expr = r + trigsimp(e, **opts) 

755 if not expr.is_Add: 

756 break 

757 old = expr 

758 

759 recursive = opts.pop('recursive', False) 

760 deep = opts.pop('deep', False) 

761 method = opts.pop('method', 'matching') 

762 

763 def groebnersimp(ex, deep, **opts): 

764 def traverse(e): 

765 if e.is_Atom: 

766 return e 

767 args = [traverse(x) for x in e.args] 

768 if e.is_Function or e.is_Pow: 

769 args = [trigsimp_groebner(x, **opts) for x in args] 

770 return e.func(*args) 

771 if deep: 

772 ex = traverse(ex) 

773 return trigsimp_groebner(ex, **opts) 

774 

775 trigsimpfunc = { 

776 'matching': (lambda x, d: _trigsimp(x, d)), 

777 'groebner': (lambda x, d: groebnersimp(x, d, **opts)), 

778 'combined': (lambda x, d: _trigsimp(groebnersimp(x, 

779 d, polynomial=True, hints=[2, tan]), 

780 d)) 

781 }[method] 

782 

783 if recursive: 

784 w, g = cse(expr) 

785 g = trigsimpfunc(g[0], deep) 

786 

787 for sub in reversed(w): 

788 g = g.subs(sub[0], sub[1]) 

789 g = trigsimpfunc(g, deep) 

790 result = g 

791 else: 

792 result = trigsimpfunc(expr, deep) 

793 

794 if opts.get('compare', False): 

795 f = futrig(old) 

796 if f != result: 

797 print('\tfutrig:', f) 

798 

799 return result 

800 

801 

802def _dotrig(a, b): 

803 """Helper to tell whether ``a`` and ``b`` have the same sorts 

804 of symbols in them -- no need to test hyperbolic patterns against 

805 expressions that have no hyperbolics in them.""" 

806 return a.func == b.func and ( 

807 a.has(TrigonometricFunction) and b.has(TrigonometricFunction) or 

808 a.has(HyperbolicFunction) and b.has(HyperbolicFunction)) 

809 

810 

811_trigpat = None 

812def _trigpats(): 

813 global _trigpat 

814 a, b, c = symbols('a b c', cls=Wild) 

815 d = Wild('d', commutative=False) 

816 

817 # for the simplifications like sinh/cosh -> tanh: 

818 # DO NOT REORDER THE FIRST 14 since these are assumed to be in this 

819 # order in _match_div_rewrite. 

820 matchers_division = ( 

821 (a*sin(b)**c/cos(b)**c, a*tan(b)**c, sin(b), cos(b)), 

822 (a*tan(b)**c*cos(b)**c, a*sin(b)**c, sin(b), cos(b)), 

823 (a*cot(b)**c*sin(b)**c, a*cos(b)**c, sin(b), cos(b)), 

824 (a*tan(b)**c/sin(b)**c, a/cos(b)**c, sin(b), cos(b)), 

825 (a*cot(b)**c/cos(b)**c, a/sin(b)**c, sin(b), cos(b)), 

826 (a*cot(b)**c*tan(b)**c, a, sin(b), cos(b)), 

827 (a*(cos(b) + 1)**c*(cos(b) - 1)**c, 

828 a*(-sin(b)**2)**c, cos(b) + 1, cos(b) - 1), 

829 (a*(sin(b) + 1)**c*(sin(b) - 1)**c, 

830 a*(-cos(b)**2)**c, sin(b) + 1, sin(b) - 1), 

831 

832 (a*sinh(b)**c/cosh(b)**c, a*tanh(b)**c, S.One, S.One), 

833 (a*tanh(b)**c*cosh(b)**c, a*sinh(b)**c, S.One, S.One), 

834 (a*coth(b)**c*sinh(b)**c, a*cosh(b)**c, S.One, S.One), 

835 (a*tanh(b)**c/sinh(b)**c, a/cosh(b)**c, S.One, S.One), 

836 (a*coth(b)**c/cosh(b)**c, a/sinh(b)**c, S.One, S.One), 

837 (a*coth(b)**c*tanh(b)**c, a, S.One, S.One), 

838 

839 (c*(tanh(a) + tanh(b))/(1 + tanh(a)*tanh(b)), 

840 tanh(a + b)*c, S.One, S.One), 

841 ) 

842 

843 matchers_add = ( 

844 (c*sin(a)*cos(b) + c*cos(a)*sin(b) + d, sin(a + b)*c + d), 

845 (c*cos(a)*cos(b) - c*sin(a)*sin(b) + d, cos(a + b)*c + d), 

846 (c*sin(a)*cos(b) - c*cos(a)*sin(b) + d, sin(a - b)*c + d), 

847 (c*cos(a)*cos(b) + c*sin(a)*sin(b) + d, cos(a - b)*c + d), 

848 (c*sinh(a)*cosh(b) + c*sinh(b)*cosh(a) + d, sinh(a + b)*c + d), 

849 (c*cosh(a)*cosh(b) + c*sinh(a)*sinh(b) + d, cosh(a + b)*c + d), 

850 ) 

851 

852 # for cos(x)**2 + sin(x)**2 -> 1 

853 matchers_identity = ( 

854 (a*sin(b)**2, a - a*cos(b)**2), 

855 (a*tan(b)**2, a*(1/cos(b))**2 - a), 

856 (a*cot(b)**2, a*(1/sin(b))**2 - a), 

857 (a*sin(b + c), a*(sin(b)*cos(c) + sin(c)*cos(b))), 

858 (a*cos(b + c), a*(cos(b)*cos(c) - sin(b)*sin(c))), 

859 (a*tan(b + c), a*((tan(b) + tan(c))/(1 - tan(b)*tan(c)))), 

860 

861 (a*sinh(b)**2, a*cosh(b)**2 - a), 

862 (a*tanh(b)**2, a - a*(1/cosh(b))**2), 

863 (a*coth(b)**2, a + a*(1/sinh(b))**2), 

864 (a*sinh(b + c), a*(sinh(b)*cosh(c) + sinh(c)*cosh(b))), 

865 (a*cosh(b + c), a*(cosh(b)*cosh(c) + sinh(b)*sinh(c))), 

866 (a*tanh(b + c), a*((tanh(b) + tanh(c))/(1 + tanh(b)*tanh(c)))), 

867 

868 ) 

869 

870 # Reduce any lingering artifacts, such as sin(x)**2 changing 

871 # to 1-cos(x)**2 when sin(x)**2 was "simpler" 

872 artifacts = ( 

873 (a - a*cos(b)**2 + c, a*sin(b)**2 + c, cos), 

874 (a - a*(1/cos(b))**2 + c, -a*tan(b)**2 + c, cos), 

875 (a - a*(1/sin(b))**2 + c, -a*cot(b)**2 + c, sin), 

876 

877 (a - a*cosh(b)**2 + c, -a*sinh(b)**2 + c, cosh), 

878 (a - a*(1/cosh(b))**2 + c, a*tanh(b)**2 + c, cosh), 

879 (a + a*(1/sinh(b))**2 + c, a*coth(b)**2 + c, sinh), 

880 

881 # same as above but with noncommutative prefactor 

882 (a*d - a*d*cos(b)**2 + c, a*d*sin(b)**2 + c, cos), 

883 (a*d - a*d*(1/cos(b))**2 + c, -a*d*tan(b)**2 + c, cos), 

884 (a*d - a*d*(1/sin(b))**2 + c, -a*d*cot(b)**2 + c, sin), 

885 

886 (a*d - a*d*cosh(b)**2 + c, -a*d*sinh(b)**2 + c, cosh), 

887 (a*d - a*d*(1/cosh(b))**2 + c, a*d*tanh(b)**2 + c, cosh), 

888 (a*d + a*d*(1/sinh(b))**2 + c, a*d*coth(b)**2 + c, sinh), 

889 ) 

890 

891 _trigpat = (a, b, c, d, matchers_division, matchers_add, 

892 matchers_identity, artifacts) 

893 return _trigpat 

894 

895 

896def _replace_mul_fpowxgpow(expr, f, g, rexp, h, rexph): 

897 """Helper for _match_div_rewrite. 

898 

899 Replace f(b_)**c_*g(b_)**(rexp(c_)) with h(b)**rexph(c) if f(b_) 

900 and g(b_) are both positive or if c_ is an integer. 

901 """ 

902 # assert expr.is_Mul and expr.is_commutative and f != g 

903 fargs = defaultdict(int) 

904 gargs = defaultdict(int) 

905 args = [] 

906 for x in expr.args: 

907 if x.is_Pow or x.func in (f, g): 

908 b, e = x.as_base_exp() 

909 if b.is_positive or e.is_integer: 

910 if b.func == f: 

911 fargs[b.args[0]] += e 

912 continue 

913 elif b.func == g: 

914 gargs[b.args[0]] += e 

915 continue 

916 args.append(x) 

917 common = set(fargs) & set(gargs) 

918 hit = False 

919 while common: 

920 key = common.pop() 

921 fe = fargs.pop(key) 

922 ge = gargs.pop(key) 

923 if fe == rexp(ge): 

924 args.append(h(key)**rexph(fe)) 

925 hit = True 

926 else: 

927 fargs[key] = fe 

928 gargs[key] = ge 

929 if not hit: 

930 return expr 

931 while fargs: 

932 key, e = fargs.popitem() 

933 args.append(f(key)**e) 

934 while gargs: 

935 key, e = gargs.popitem() 

936 args.append(g(key)**e) 

937 return Mul(*args) 

938 

939 

940_idn = lambda x: x 

941_midn = lambda x: -x 

942_one = lambda x: S.One 

943 

944def _match_div_rewrite(expr, i): 

945 """helper for __trigsimp""" 

946 if i == 0: 

947 expr = _replace_mul_fpowxgpow(expr, sin, cos, 

948 _midn, tan, _idn) 

949 elif i == 1: 

950 expr = _replace_mul_fpowxgpow(expr, tan, cos, 

951 _idn, sin, _idn) 

952 elif i == 2: 

953 expr = _replace_mul_fpowxgpow(expr, cot, sin, 

954 _idn, cos, _idn) 

955 elif i == 3: 

956 expr = _replace_mul_fpowxgpow(expr, tan, sin, 

957 _midn, cos, _midn) 

958 elif i == 4: 

959 expr = _replace_mul_fpowxgpow(expr, cot, cos, 

960 _midn, sin, _midn) 

961 elif i == 5: 

962 expr = _replace_mul_fpowxgpow(expr, cot, tan, 

963 _idn, _one, _idn) 

964 # i in (6, 7) is skipped 

965 elif i == 8: 

966 expr = _replace_mul_fpowxgpow(expr, sinh, cosh, 

967 _midn, tanh, _idn) 

968 elif i == 9: 

969 expr = _replace_mul_fpowxgpow(expr, tanh, cosh, 

970 _idn, sinh, _idn) 

971 elif i == 10: 

972 expr = _replace_mul_fpowxgpow(expr, coth, sinh, 

973 _idn, cosh, _idn) 

974 elif i == 11: 

975 expr = _replace_mul_fpowxgpow(expr, tanh, sinh, 

976 _midn, cosh, _midn) 

977 elif i == 12: 

978 expr = _replace_mul_fpowxgpow(expr, coth, cosh, 

979 _midn, sinh, _midn) 

980 elif i == 13: 

981 expr = _replace_mul_fpowxgpow(expr, coth, tanh, 

982 _idn, _one, _idn) 

983 else: 

984 return None 

985 return expr 

986 

987 

988def _trigsimp(expr, deep=False): 

989 # protect the cache from non-trig patterns; we only allow 

990 # trig patterns to enter the cache 

991 if expr.has(*_trigs): 

992 return __trigsimp(expr, deep) 

993 return expr 

994 

995 

996@cacheit 

997def __trigsimp(expr, deep=False): 

998 """recursive helper for trigsimp""" 

999 from sympy.simplify.fu import TR10i 

1000 

1001 if _trigpat is None: 

1002 _trigpats() 

1003 a, b, c, d, matchers_division, matchers_add, \ 

1004 matchers_identity, artifacts = _trigpat 

1005 

1006 if expr.is_Mul: 

1007 # do some simplifications like sin/cos -> tan: 

1008 if not expr.is_commutative: 

1009 com, nc = expr.args_cnc() 

1010 expr = _trigsimp(Mul._from_args(com), deep)*Mul._from_args(nc) 

1011 else: 

1012 for i, (pattern, simp, ok1, ok2) in enumerate(matchers_division): 

1013 if not _dotrig(expr, pattern): 

1014 continue 

1015 

1016 newexpr = _match_div_rewrite(expr, i) 

1017 if newexpr is not None: 

1018 if newexpr != expr: 

1019 expr = newexpr 

1020 break 

1021 else: 

1022 continue 

1023 

1024 # use SymPy matching instead 

1025 res = expr.match(pattern) 

1026 if res and res.get(c, 0): 

1027 if not res[c].is_integer: 

1028 ok = ok1.subs(res) 

1029 if not ok.is_positive: 

1030 continue 

1031 ok = ok2.subs(res) 

1032 if not ok.is_positive: 

1033 continue 

1034 # if "a" contains any of trig or hyperbolic funcs with 

1035 # argument "b" then skip the simplification 

1036 if any(w.args[0] == res[b] for w in res[a].atoms( 

1037 TrigonometricFunction, HyperbolicFunction)): 

1038 continue 

1039 # simplify and finish: 

1040 expr = simp.subs(res) 

1041 break # process below 

1042 

1043 if expr.is_Add: 

1044 args = [] 

1045 for term in expr.args: 

1046 if not term.is_commutative: 

1047 com, nc = term.args_cnc() 

1048 nc = Mul._from_args(nc) 

1049 term = Mul._from_args(com) 

1050 else: 

1051 nc = S.One 

1052 term = _trigsimp(term, deep) 

1053 for pattern, result in matchers_identity: 

1054 res = term.match(pattern) 

1055 if res is not None: 

1056 term = result.subs(res) 

1057 break 

1058 args.append(term*nc) 

1059 if args != expr.args: 

1060 expr = Add(*args) 

1061 expr = min(expr, expand(expr), key=count_ops) 

1062 if expr.is_Add: 

1063 for pattern, result in matchers_add: 

1064 if not _dotrig(expr, pattern): 

1065 continue 

1066 expr = TR10i(expr) 

1067 if expr.has(HyperbolicFunction): 

1068 res = expr.match(pattern) 

1069 # if "d" contains any trig or hyperbolic funcs with 

1070 # argument "a" or "b" then skip the simplification; 

1071 # this isn't perfect -- see tests 

1072 if res is None or not (a in res and b in res) or any( 

1073 w.args[0] in (res[a], res[b]) for w in res[d].atoms( 

1074 TrigonometricFunction, HyperbolicFunction)): 

1075 continue 

1076 expr = result.subs(res) 

1077 break 

1078 

1079 # Reduce any lingering artifacts, such as sin(x)**2 changing 

1080 # to 1 - cos(x)**2 when sin(x)**2 was "simpler" 

1081 for pattern, result, ex in artifacts: 

1082 if not _dotrig(expr, pattern): 

1083 continue 

1084 # Substitute a new wild that excludes some function(s) 

1085 # to help influence a better match. This is because 

1086 # sometimes, for example, 'a' would match sec(x)**2 

1087 a_t = Wild('a', exclude=[ex]) 

1088 pattern = pattern.subs(a, a_t) 

1089 result = result.subs(a, a_t) 

1090 

1091 m = expr.match(pattern) 

1092 was = None 

1093 while m and was != expr: 

1094 was = expr 

1095 if m[a_t] == 0 or \ 

1096 -m[a_t] in m[c].args or m[a_t] + m[c] == 0: 

1097 break 

1098 if d in m and m[a_t]*m[d] + m[c] == 0: 

1099 break 

1100 expr = result.subs(m) 

1101 m = expr.match(pattern) 

1102 m.setdefault(c, S.Zero) 

1103 

1104 elif expr.is_Mul or expr.is_Pow or deep and expr.args: 

1105 expr = expr.func(*[_trigsimp(a, deep) for a in expr.args]) 

1106 

1107 try: 

1108 if not expr.has(*_trigs): 

1109 raise TypeError 

1110 e = expr.atoms(exp) 

1111 new = expr.rewrite(exp, deep=deep) 

1112 if new == e: 

1113 raise TypeError 

1114 fnew = factor(new) 

1115 if fnew != new: 

1116 new = sorted([new, factor(new)], key=count_ops)[0] 

1117 # if all exp that were introduced disappeared then accept it 

1118 if not (new.atoms(exp) - e): 

1119 expr = new 

1120 except TypeError: 

1121 pass 

1122 

1123 return expr 

1124#------------------- end of old trigsimp routines -------------------- 

1125 

1126 

1127def futrig(e, *, hyper=True, **kwargs): 

1128 """Return simplified ``e`` using Fu-like transformations. 

1129 This is not the "Fu" algorithm. This is called by default 

1130 from ``trigsimp``. By default, hyperbolics subexpressions 

1131 will be simplified, but this can be disabled by setting 

1132 ``hyper=False``. 

1133 

1134 Examples 

1135 ======== 

1136 

1137 >>> from sympy import trigsimp, tan, sinh, tanh 

1138 >>> from sympy.simplify.trigsimp import futrig 

1139 >>> from sympy.abc import x 

1140 >>> trigsimp(1/tan(x)**2) 

1141 tan(x)**(-2) 

1142 

1143 >>> futrig(sinh(x)/tanh(x)) 

1144 cosh(x) 

1145 

1146 """ 

1147 from sympy.simplify.fu import hyper_as_trig 

1148 

1149 e = sympify(e) 

1150 

1151 if not isinstance(e, Basic): 

1152 return e 

1153 

1154 if not e.args: 

1155 return e 

1156 

1157 old = e 

1158 e = bottom_up(e, _futrig) 

1159 

1160 if hyper and e.has(HyperbolicFunction): 

1161 e, f = hyper_as_trig(e) 

1162 e = f(bottom_up(e, _futrig)) 

1163 

1164 if e != old and e.is_Mul and e.args[0].is_Rational: 

1165 # redistribute leading coeff on 2-arg Add 

1166 e = Mul(*e.as_coeff_Mul()) 

1167 return e 

1168 

1169 

1170def _futrig(e): 

1171 """Helper for futrig.""" 

1172 from sympy.simplify.fu import ( 

1173 TR1, TR2, TR3, TR2i, TR10, L, TR10i, 

1174 TR8, TR6, TR15, TR16, TR111, TR5, TRmorrie, TR11, _TR11, TR14, TR22, 

1175 TR12) 

1176 

1177 if not e.has(TrigonometricFunction): 

1178 return e 

1179 

1180 if e.is_Mul: 

1181 coeff, e = e.as_independent(TrigonometricFunction) 

1182 else: 

1183 coeff = None 

1184 

1185 Lops = lambda x: (L(x), x.count_ops(), _nodes(x), len(x.args), x.is_Add) 

1186 trigs = lambda x: x.has(TrigonometricFunction) 

1187 

1188 tree = [identity, 

1189 ( 

1190 TR3, # canonical angles 

1191 TR1, # sec-csc -> cos-sin 

1192 TR12, # expand tan of sum 

1193 lambda x: _eapply(factor, x, trigs), 

1194 TR2, # tan-cot -> sin-cos 

1195 [identity, lambda x: _eapply(_mexpand, x, trigs)], 

1196 TR2i, # sin-cos ratio -> tan 

1197 lambda x: _eapply(lambda i: factor(i.normal()), x, trigs), 

1198 TR14, # factored identities 

1199 TR5, # sin-pow -> cos_pow 

1200 TR10, # sin-cos of sums -> sin-cos prod 

1201 TR11, _TR11, TR6, # reduce double angles and rewrite cos pows 

1202 lambda x: _eapply(factor, x, trigs), 

1203 TR14, # factored powers of identities 

1204 [identity, lambda x: _eapply(_mexpand, x, trigs)], 

1205 TR10i, # sin-cos products > sin-cos of sums 

1206 TRmorrie, 

1207 [identity, TR8], # sin-cos products -> sin-cos of sums 

1208 [identity, lambda x: TR2i(TR2(x))], # tan -> sin-cos -> tan 

1209 [ 

1210 lambda x: _eapply(expand_mul, TR5(x), trigs), 

1211 lambda x: _eapply( 

1212 expand_mul, TR15(x), trigs)], # pos/neg powers of sin 

1213 [ 

1214 lambda x: _eapply(expand_mul, TR6(x), trigs), 

1215 lambda x: _eapply( 

1216 expand_mul, TR16(x), trigs)], # pos/neg powers of cos 

1217 TR111, # tan, sin, cos to neg power -> cot, csc, sec 

1218 [identity, TR2i], # sin-cos ratio to tan 

1219 [identity, lambda x: _eapply( 

1220 expand_mul, TR22(x), trigs)], # tan-cot to sec-csc 

1221 TR1, TR2, TR2i, 

1222 [identity, lambda x: _eapply( 

1223 factor_terms, TR12(x), trigs)], # expand tan of sum 

1224 )] 

1225 e = greedy(tree, objective=Lops)(e) 

1226 

1227 if coeff is not None: 

1228 e = coeff * e 

1229 

1230 return e 

1231 

1232 

1233def _is_Expr(e): 

1234 """_eapply helper to tell whether ``e`` and all its args 

1235 are Exprs.""" 

1236 if isinstance(e, Derivative): 

1237 return _is_Expr(e.expr) 

1238 if not isinstance(e, Expr): 

1239 return False 

1240 return all(_is_Expr(i) for i in e.args) 

1241 

1242 

1243def _eapply(func, e, cond=None): 

1244 """Apply ``func`` to ``e`` if all args are Exprs else only 

1245 apply it to those args that *are* Exprs.""" 

1246 if not isinstance(e, Expr): 

1247 return e 

1248 if _is_Expr(e) or not e.args: 

1249 return func(e) 

1250 return e.func(*[ 

1251 _eapply(func, ei) if (cond is None or cond(ei)) else ei 

1252 for ei in e.args])