ratsimp.py 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221
  1. from itertools import combinations_with_replacement
  2. from sympy.core import symbols, Add, Dummy
  3. from sympy.core.numbers import Rational
  4. from sympy.polys import cancel, ComputationFailed, parallel_poly_from_expr, reduced, Poly
  5. from sympy.polys.monomials import Monomial, monomial_div
  6. from sympy.polys.polyerrors import DomainError, PolificationFailed
  7. from sympy.utilities.misc import debug
  8. def ratsimp(expr):
  9. """
  10. Put an expression over a common denominator, cancel and reduce.
  11. Examples
  12. ========
  13. >>> from sympy import ratsimp
  14. >>> from sympy.abc import x, y
  15. >>> ratsimp(1/x + 1/y)
  16. (x + y)/(x*y)
  17. """
  18. f, g = cancel(expr).as_numer_denom()
  19. try:
  20. Q, r = reduced(f, [g], field=True, expand=False)
  21. except ComputationFailed:
  22. return f/g
  23. return Add(*Q) + cancel(r/g)
  24. def ratsimpmodprime(expr, G, *gens, quick=True, polynomial=False, **args):
  25. """
  26. Simplifies a rational expression ``expr`` modulo the prime ideal
  27. generated by ``G``. ``G`` should be a Groebner basis of the
  28. ideal.
  29. Examples
  30. ========
  31. >>> from sympy.simplify.ratsimp import ratsimpmodprime
  32. >>> from sympy.abc import x, y
  33. >>> eq = (x + y**5 + y)/(x - y)
  34. >>> ratsimpmodprime(eq, [x*y**5 - x - y], x, y, order='lex')
  35. (-x**2 - x*y - x - y)/(-x**2 + x*y)
  36. If ``polynomial`` is ``False``, the algorithm computes a rational
  37. simplification which minimizes the sum of the total degrees of
  38. the numerator and the denominator.
  39. If ``polynomial`` is ``True``, this function just brings numerator and
  40. denominator into a canonical form. This is much faster, but has
  41. potentially worse results.
  42. References
  43. ==========
  44. .. [1] M. Monagan, R. Pearce, Rational Simplification Modulo a Polynomial
  45. Ideal, http://citeseer.ist.psu.edu/viewdoc/summary?doi=10.1.1.163.6984
  46. (specifically, the second algorithm)
  47. """
  48. from sympy.solvers.solvers import solve
  49. debug('ratsimpmodprime', expr)
  50. # usual preparation of polynomials:
  51. num, denom = cancel(expr).as_numer_denom()
  52. try:
  53. polys, opt = parallel_poly_from_expr([num, denom] + G, *gens, **args)
  54. except PolificationFailed:
  55. return expr
  56. domain = opt.domain
  57. if domain.has_assoc_Field:
  58. opt.domain = domain.get_field()
  59. else:
  60. raise DomainError(
  61. "Cannot compute rational simplification over %s" % domain)
  62. # compute only once
  63. leading_monomials = [g.LM(opt.order) for g in polys[2:]]
  64. tested = set()
  65. def staircase(n):
  66. """
  67. Compute all monomials with degree less than ``n`` that are
  68. not divisible by any element of ``leading_monomials``.
  69. """
  70. if n == 0:
  71. return [1]
  72. S = []
  73. for mi in combinations_with_replacement(range(len(opt.gens)), n):
  74. m = [0]*len(opt.gens)
  75. for i in mi:
  76. m[i] += 1
  77. if all(monomial_div(m, lmg) is None for lmg in
  78. leading_monomials):
  79. S.append(m)
  80. return [Monomial(s).as_expr(*opt.gens) for s in S] + staircase(n - 1)
  81. def _ratsimpmodprime(a, b, allsol, N=0, D=0):
  82. r"""
  83. Computes a rational simplification of ``a/b`` which minimizes
  84. the sum of the total degrees of the numerator and the denominator.
  85. Explanation
  86. ===========
  87. The algorithm proceeds by looking at ``a * d - b * c`` modulo
  88. the ideal generated by ``G`` for some ``c`` and ``d`` with degree
  89. less than ``a`` and ``b`` respectively.
  90. The coefficients of ``c`` and ``d`` are indeterminates and thus
  91. the coefficients of the normalform of ``a * d - b * c`` are
  92. linear polynomials in these indeterminates.
  93. If these linear polynomials, considered as system of
  94. equations, have a nontrivial solution, then `\frac{a}{b}
  95. \equiv \frac{c}{d}` modulo the ideal generated by ``G``. So,
  96. by construction, the degree of ``c`` and ``d`` is less than
  97. the degree of ``a`` and ``b``, so a simpler representation
  98. has been found.
  99. After a simpler representation has been found, the algorithm
  100. tries to reduce the degree of the numerator and denominator
  101. and returns the result afterwards.
  102. As an extension, if quick=False, we look at all possible degrees such
  103. that the total degree is less than *or equal to* the best current
  104. solution. We retain a list of all solutions of minimal degree, and try
  105. to find the best one at the end.
  106. """
  107. c, d = a, b
  108. steps = 0
  109. maxdeg = a.total_degree() + b.total_degree()
  110. if quick:
  111. bound = maxdeg - 1
  112. else:
  113. bound = maxdeg
  114. while N + D <= bound:
  115. if (N, D) in tested:
  116. break
  117. tested.add((N, D))
  118. M1 = staircase(N)
  119. M2 = staircase(D)
  120. debug('%s / %s: %s, %s' % (N, D, M1, M2))
  121. Cs = symbols("c:%d" % len(M1), cls=Dummy)
  122. Ds = symbols("d:%d" % len(M2), cls=Dummy)
  123. ng = Cs + Ds
  124. c_hat = Poly(
  125. sum([Cs[i] * M1[i] for i in range(len(M1))]), opt.gens + ng)
  126. d_hat = Poly(
  127. sum([Ds[i] * M2[i] for i in range(len(M2))]), opt.gens + ng)
  128. r = reduced(a * d_hat - b * c_hat, G, opt.gens + ng,
  129. order=opt.order, polys=True)[1]
  130. S = Poly(r, gens=opt.gens).coeffs()
  131. sol = solve(S, Cs + Ds, particular=True, quick=True)
  132. if sol and not all(s == 0 for s in sol.values()):
  133. c = c_hat.subs(sol)
  134. d = d_hat.subs(sol)
  135. # The "free" variables occurring before as parameters
  136. # might still be in the substituted c, d, so set them
  137. # to the value chosen before:
  138. c = c.subs(dict(list(zip(Cs + Ds, [1] * (len(Cs) + len(Ds))))))
  139. d = d.subs(dict(list(zip(Cs + Ds, [1] * (len(Cs) + len(Ds))))))
  140. c = Poly(c, opt.gens)
  141. d = Poly(d, opt.gens)
  142. if d == 0:
  143. raise ValueError('Ideal not prime?')
  144. allsol.append((c_hat, d_hat, S, Cs + Ds))
  145. if N + D != maxdeg:
  146. allsol = [allsol[-1]]
  147. break
  148. steps += 1
  149. N += 1
  150. D += 1
  151. if steps > 0:
  152. c, d, allsol = _ratsimpmodprime(c, d, allsol, N, D - steps)
  153. c, d, allsol = _ratsimpmodprime(c, d, allsol, N - steps, D)
  154. return c, d, allsol
  155. # preprocessing. this improves performance a bit when deg(num)
  156. # and deg(denom) are large:
  157. num = reduced(num, G, opt.gens, order=opt.order)[1]
  158. denom = reduced(denom, G, opt.gens, order=opt.order)[1]
  159. if polynomial:
  160. return (num/denom).cancel()
  161. c, d, allsol = _ratsimpmodprime(
  162. Poly(num, opt.gens, domain=opt.domain), Poly(denom, opt.gens, domain=opt.domain), [])
  163. if not quick and allsol:
  164. debug('Looking for best minimal solution. Got: %s' % len(allsol))
  165. newsol = []
  166. for c_hat, d_hat, S, ng in allsol:
  167. sol = solve(S, ng, particular=True, quick=False)
  168. newsol.append((c_hat.subs(sol), d_hat.subs(sol)))
  169. c, d = min(newsol, key=lambda x: len(x[0].terms()) + len(x[1].terms()))
  170. if not domain.is_Field:
  171. cn, c = c.clear_denoms(convert=True)
  172. dn, d = d.clear_denoms(convert=True)
  173. r = Rational(cn, dn)
  174. else:
  175. r = Rational(1)
  176. return (c*r.q)/(d*r.p)