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