partfrac.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496
  1. """Algorithms for partial fraction decomposition of rational functions. """
  2. from sympy.core import S, Add, sympify, Function, Lambda, Dummy
  3. from sympy.core.traversal import preorder_traversal
  4. from sympy.polys import Poly, RootSum, cancel, factor
  5. from sympy.polys.polyerrors import PolynomialError
  6. from sympy.polys.polyoptions import allowed_flags, set_defaults
  7. from sympy.polys.polytools import parallel_poly_from_expr
  8. from sympy.utilities import numbered_symbols, take, xthreaded, public
  9. @xthreaded
  10. @public
  11. def apart(f, x=None, full=False, **options):
  12. """
  13. Compute partial fraction decomposition of a rational function.
  14. Given a rational function ``f``, computes the partial fraction
  15. decomposition of ``f``. Two algorithms are available: One is based on the
  16. undertermined coefficients method, the other is Bronstein's full partial
  17. fraction decomposition algorithm.
  18. The undetermined coefficients method (selected by ``full=False``) uses
  19. polynomial factorization (and therefore accepts the same options as
  20. factor) for the denominator. Per default it works over the rational
  21. numbers, therefore decomposition of denominators with non-rational roots
  22. (e.g. irrational, complex roots) is not supported by default (see options
  23. of factor).
  24. Bronstein's algorithm can be selected by using ``full=True`` and allows a
  25. decomposition of denominators with non-rational roots. A human-readable
  26. result can be obtained via ``doit()`` (see examples below).
  27. Examples
  28. ========
  29. >>> from sympy.polys.partfrac import apart
  30. >>> from sympy.abc import x, y
  31. By default, using the undetermined coefficients method:
  32. >>> apart(y/(x + 2)/(x + 1), x)
  33. -y/(x + 2) + y/(x + 1)
  34. The undetermined coefficients method does not provide a result when the
  35. denominators roots are not rational:
  36. >>> apart(y/(x**2 + x + 1), x)
  37. y/(x**2 + x + 1)
  38. You can choose Bronstein's algorithm by setting ``full=True``:
  39. >>> apart(y/(x**2 + x + 1), x, full=True)
  40. RootSum(_w**2 + _w + 1, Lambda(_a, (-2*_a*y/3 - y/3)/(-_a + x)))
  41. Calling ``doit()`` yields a human-readable result:
  42. >>> apart(y/(x**2 + x + 1), x, full=True).doit()
  43. (-y/3 - 2*y*(-1/2 - sqrt(3)*I/2)/3)/(x + 1/2 + sqrt(3)*I/2) + (-y/3 -
  44. 2*y*(-1/2 + sqrt(3)*I/2)/3)/(x + 1/2 - sqrt(3)*I/2)
  45. See Also
  46. ========
  47. apart_list, assemble_partfrac_list
  48. """
  49. allowed_flags(options, [])
  50. f = sympify(f)
  51. if f.is_Atom:
  52. return f
  53. else:
  54. P, Q = f.as_numer_denom()
  55. _options = options.copy()
  56. options = set_defaults(options, extension=True)
  57. try:
  58. (P, Q), opt = parallel_poly_from_expr((P, Q), x, **options)
  59. except PolynomialError as msg:
  60. if f.is_commutative:
  61. raise PolynomialError(msg)
  62. # non-commutative
  63. if f.is_Mul:
  64. c, nc = f.args_cnc(split_1=False)
  65. nc = f.func(*nc)
  66. if c:
  67. c = apart(f.func._from_args(c), x=x, full=full, **_options)
  68. return c*nc
  69. else:
  70. return nc
  71. elif f.is_Add:
  72. c = []
  73. nc = []
  74. for i in f.args:
  75. if i.is_commutative:
  76. c.append(i)
  77. else:
  78. try:
  79. nc.append(apart(i, x=x, full=full, **_options))
  80. except NotImplementedError:
  81. nc.append(i)
  82. return apart(f.func(*c), x=x, full=full, **_options) + f.func(*nc)
  83. else:
  84. reps = []
  85. pot = preorder_traversal(f)
  86. next(pot)
  87. for e in pot:
  88. try:
  89. reps.append((e, apart(e, x=x, full=full, **_options)))
  90. pot.skip() # this was handled successfully
  91. except NotImplementedError:
  92. pass
  93. return f.xreplace(dict(reps))
  94. if P.is_multivariate:
  95. fc = f.cancel()
  96. if fc != f:
  97. return apart(fc, x=x, full=full, **_options)
  98. raise NotImplementedError(
  99. "multivariate partial fraction decomposition")
  100. common, P, Q = P.cancel(Q)
  101. poly, P = P.div(Q, auto=True)
  102. P, Q = P.rat_clear_denoms(Q)
  103. if Q.degree() <= 1:
  104. partial = P/Q
  105. else:
  106. if not full:
  107. partial = apart_undetermined_coeffs(P, Q)
  108. else:
  109. partial = apart_full_decomposition(P, Q)
  110. terms = S.Zero
  111. for term in Add.make_args(partial):
  112. if term.has(RootSum):
  113. terms += term
  114. else:
  115. terms += factor(term)
  116. return common*(poly.as_expr() + terms)
  117. def apart_undetermined_coeffs(P, Q):
  118. """Partial fractions via method of undetermined coefficients. """
  119. X = numbered_symbols(cls=Dummy)
  120. partial, symbols = [], []
  121. _, factors = Q.factor_list()
  122. for f, k in factors:
  123. n, q = f.degree(), Q
  124. for i in range(1, k + 1):
  125. coeffs, q = take(X, n), q.quo(f)
  126. partial.append((coeffs, q, f, i))
  127. symbols.extend(coeffs)
  128. dom = Q.get_domain().inject(*symbols)
  129. F = Poly(0, Q.gen, domain=dom)
  130. for i, (coeffs, q, f, k) in enumerate(partial):
  131. h = Poly(coeffs, Q.gen, domain=dom)
  132. partial[i] = (h, f, k)
  133. q = q.set_domain(dom)
  134. F += h*q
  135. system, result = [], S.Zero
  136. for (k,), coeff in F.terms():
  137. system.append(coeff - P.nth(k))
  138. from sympy.solvers import solve
  139. solution = solve(system, symbols)
  140. for h, f, k in partial:
  141. h = h.as_expr().subs(solution)
  142. result += h/f.as_expr()**k
  143. return result
  144. def apart_full_decomposition(P, Q):
  145. """
  146. Bronstein's full partial fraction decomposition algorithm.
  147. Given a univariate rational function ``f``, performing only GCD
  148. operations over the algebraic closure of the initial ground domain
  149. of definition, compute full partial fraction decomposition with
  150. fractions having linear denominators.
  151. Note that no factorization of the initial denominator of ``f`` is
  152. performed. The final decomposition is formed in terms of a sum of
  153. :class:`RootSum` instances.
  154. References
  155. ==========
  156. .. [1] [Bronstein93]_
  157. """
  158. return assemble_partfrac_list(apart_list(P/Q, P.gens[0]))
  159. @public
  160. def apart_list(f, x=None, dummies=None, **options):
  161. """
  162. Compute partial fraction decomposition of a rational function
  163. and return the result in structured form.
  164. Given a rational function ``f`` compute the partial fraction decomposition
  165. of ``f``. Only Bronstein's full partial fraction decomposition algorithm
  166. is supported by this method. The return value is highly structured and
  167. perfectly suited for further algorithmic treatment rather than being
  168. human-readable. The function returns a tuple holding three elements:
  169. * The first item is the common coefficient, free of the variable `x` used
  170. for decomposition. (It is an element of the base field `K`.)
  171. * The second item is the polynomial part of the decomposition. This can be
  172. the zero polynomial. (It is an element of `K[x]`.)
  173. * The third part itself is a list of quadruples. Each quadruple
  174. has the following elements in this order:
  175. - The (not necessarily irreducible) polynomial `D` whose roots `w_i` appear
  176. in the linear denominator of a bunch of related fraction terms. (This item
  177. can also be a list of explicit roots. However, at the moment ``apart_list``
  178. never returns a result this way, but the related ``assemble_partfrac_list``
  179. function accepts this format as input.)
  180. - The numerator of the fraction, written as a function of the root `w`
  181. - The linear denominator of the fraction *excluding its power exponent*,
  182. written as a function of the root `w`.
  183. - The power to which the denominator has to be raised.
  184. On can always rebuild a plain expression by using the function ``assemble_partfrac_list``.
  185. Examples
  186. ========
  187. A first example:
  188. >>> from sympy.polys.partfrac import apart_list, assemble_partfrac_list
  189. >>> from sympy.abc import x, t
  190. >>> f = (2*x**3 - 2*x) / (x**2 - 2*x + 1)
  191. >>> pfd = apart_list(f)
  192. >>> pfd
  193. (1,
  194. Poly(2*x + 4, x, domain='ZZ'),
  195. [(Poly(_w - 1, _w, domain='ZZ'), Lambda(_a, 4), Lambda(_a, -_a + x), 1)])
  196. >>> assemble_partfrac_list(pfd)
  197. 2*x + 4 + 4/(x - 1)
  198. Second example:
  199. >>> f = (-2*x - 2*x**2) / (3*x**2 - 6*x)
  200. >>> pfd = apart_list(f)
  201. >>> pfd
  202. (-1,
  203. Poly(2/3, x, domain='QQ'),
  204. [(Poly(_w - 2, _w, domain='ZZ'), Lambda(_a, 2), Lambda(_a, -_a + x), 1)])
  205. >>> assemble_partfrac_list(pfd)
  206. -2/3 - 2/(x - 2)
  207. Another example, showing symbolic parameters:
  208. >>> pfd = apart_list(t/(x**2 + x + t), x)
  209. >>> pfd
  210. (1,
  211. Poly(0, x, domain='ZZ[t]'),
  212. [(Poly(_w**2 + _w + t, _w, domain='ZZ[t]'),
  213. Lambda(_a, -2*_a*t/(4*t - 1) - t/(4*t - 1)),
  214. Lambda(_a, -_a + x),
  215. 1)])
  216. >>> assemble_partfrac_list(pfd)
  217. RootSum(_w**2 + _w + t, Lambda(_a, (-2*_a*t/(4*t - 1) - t/(4*t - 1))/(-_a + x)))
  218. This example is taken from Bronstein's original paper:
  219. >>> f = 36 / (x**5 - 2*x**4 - 2*x**3 + 4*x**2 + x - 2)
  220. >>> pfd = apart_list(f)
  221. >>> pfd
  222. (1,
  223. Poly(0, x, domain='ZZ'),
  224. [(Poly(_w - 2, _w, domain='ZZ'), Lambda(_a, 4), Lambda(_a, -_a + x), 1),
  225. (Poly(_w**2 - 1, _w, domain='ZZ'), Lambda(_a, -3*_a - 6), Lambda(_a, -_a + x), 2),
  226. (Poly(_w + 1, _w, domain='ZZ'), Lambda(_a, -4), Lambda(_a, -_a + x), 1)])
  227. >>> assemble_partfrac_list(pfd)
  228. -4/(x + 1) - 3/(x + 1)**2 - 9/(x - 1)**2 + 4/(x - 2)
  229. See also
  230. ========
  231. apart, assemble_partfrac_list
  232. References
  233. ==========
  234. .. [1] [Bronstein93]_
  235. """
  236. allowed_flags(options, [])
  237. f = sympify(f)
  238. if f.is_Atom:
  239. return f
  240. else:
  241. P, Q = f.as_numer_denom()
  242. options = set_defaults(options, extension=True)
  243. (P, Q), opt = parallel_poly_from_expr((P, Q), x, **options)
  244. if P.is_multivariate:
  245. raise NotImplementedError(
  246. "multivariate partial fraction decomposition")
  247. common, P, Q = P.cancel(Q)
  248. poly, P = P.div(Q, auto=True)
  249. P, Q = P.rat_clear_denoms(Q)
  250. polypart = poly
  251. if dummies is None:
  252. def dummies(name):
  253. d = Dummy(name)
  254. while True:
  255. yield d
  256. dummies = dummies("w")
  257. rationalpart = apart_list_full_decomposition(P, Q, dummies)
  258. return (common, polypart, rationalpart)
  259. def apart_list_full_decomposition(P, Q, dummygen):
  260. """
  261. Bronstein's full partial fraction decomposition algorithm.
  262. Given a univariate rational function ``f``, performing only GCD
  263. operations over the algebraic closure of the initial ground domain
  264. of definition, compute full partial fraction decomposition with
  265. fractions having linear denominators.
  266. Note that no factorization of the initial denominator of ``f`` is
  267. performed. The final decomposition is formed in terms of a sum of
  268. :class:`RootSum` instances.
  269. References
  270. ==========
  271. .. [1] [Bronstein93]_
  272. """
  273. f, x, U = P/Q, P.gen, []
  274. u = Function('u')(x)
  275. a = Dummy('a')
  276. partial = []
  277. for d, n in Q.sqf_list_include(all=True):
  278. b = d.as_expr()
  279. U += [ u.diff(x, n - 1) ]
  280. h = cancel(f*b**n) / u**n
  281. H, subs = [h], []
  282. for j in range(1, n):
  283. H += [ H[-1].diff(x) / j ]
  284. for j in range(1, n + 1):
  285. subs += [ (U[j - 1], b.diff(x, j) / j) ]
  286. for j in range(0, n):
  287. P, Q = cancel(H[j]).as_numer_denom()
  288. for i in range(0, j + 1):
  289. P = P.subs(*subs[j - i])
  290. Q = Q.subs(*subs[0])
  291. P = Poly(P, x)
  292. Q = Poly(Q, x)
  293. G = P.gcd(d)
  294. D = d.quo(G)
  295. B, g = Q.half_gcdex(D)
  296. b = (P * B.quo(g)).rem(D)
  297. Dw = D.subs(x, next(dummygen))
  298. numer = Lambda(a, b.as_expr().subs(x, a))
  299. denom = Lambda(a, (x - a))
  300. exponent = n-j
  301. partial.append((Dw, numer, denom, exponent))
  302. return partial
  303. @public
  304. def assemble_partfrac_list(partial_list):
  305. r"""Reassemble a full partial fraction decomposition
  306. from a structured result obtained by the function ``apart_list``.
  307. Examples
  308. ========
  309. This example is taken from Bronstein's original paper:
  310. >>> from sympy.polys.partfrac import apart_list, assemble_partfrac_list
  311. >>> from sympy.abc import x
  312. >>> f = 36 / (x**5 - 2*x**4 - 2*x**3 + 4*x**2 + x - 2)
  313. >>> pfd = apart_list(f)
  314. >>> pfd
  315. (1,
  316. Poly(0, x, domain='ZZ'),
  317. [(Poly(_w - 2, _w, domain='ZZ'), Lambda(_a, 4), Lambda(_a, -_a + x), 1),
  318. (Poly(_w**2 - 1, _w, domain='ZZ'), Lambda(_a, -3*_a - 6), Lambda(_a, -_a + x), 2),
  319. (Poly(_w + 1, _w, domain='ZZ'), Lambda(_a, -4), Lambda(_a, -_a + x), 1)])
  320. >>> assemble_partfrac_list(pfd)
  321. -4/(x + 1) - 3/(x + 1)**2 - 9/(x - 1)**2 + 4/(x - 2)
  322. If we happen to know some roots we can provide them easily inside the structure:
  323. >>> pfd = apart_list(2/(x**2-2))
  324. >>> pfd
  325. (1,
  326. Poly(0, x, domain='ZZ'),
  327. [(Poly(_w**2 - 2, _w, domain='ZZ'),
  328. Lambda(_a, _a/2),
  329. Lambda(_a, -_a + x),
  330. 1)])
  331. >>> pfda = assemble_partfrac_list(pfd)
  332. >>> pfda
  333. RootSum(_w**2 - 2, Lambda(_a, _a/(-_a + x)))/2
  334. >>> pfda.doit()
  335. -sqrt(2)/(2*(x + sqrt(2))) + sqrt(2)/(2*(x - sqrt(2)))
  336. >>> from sympy import Dummy, Poly, Lambda, sqrt
  337. >>> a = Dummy("a")
  338. >>> pfd = (1, Poly(0, x, domain='ZZ'), [([sqrt(2),-sqrt(2)], Lambda(a, a/2), Lambda(a, -a + x), 1)])
  339. >>> assemble_partfrac_list(pfd)
  340. -sqrt(2)/(2*(x + sqrt(2))) + sqrt(2)/(2*(x - sqrt(2)))
  341. See Also
  342. ========
  343. apart, apart_list
  344. """
  345. # Common factor
  346. common = partial_list[0]
  347. # Polynomial part
  348. polypart = partial_list[1]
  349. pfd = polypart.as_expr()
  350. # Rational parts
  351. for r, nf, df, ex in partial_list[2]:
  352. if isinstance(r, Poly):
  353. # Assemble in case the roots are given implicitly by a polynomials
  354. an, nu = nf.variables, nf.expr
  355. ad, de = df.variables, df.expr
  356. # Hack to make dummies equal because Lambda created new Dummies
  357. de = de.subs(ad[0], an[0])
  358. func = Lambda(tuple(an), nu/de**ex)
  359. pfd += RootSum(r, func, auto=False, quadratic=False)
  360. else:
  361. # Assemble in case the roots are given explicitly by a list of algebraic numbers
  362. for root in r:
  363. pfd += nf(root)/df(root)**ex
  364. return common*pfd