Skip to content

Commit

Permalink
polys: adopt recursive algorithm to use for Poly.from_expr()
Browse files Browse the repository at this point in the history
Closes sympy/sympy#6322
Closes sympy/sympy#12531
A partial fix for diofant#992
  • Loading branch information
skirpichev committed Mar 1, 2022
1 parent 6849e03 commit ac3c182
Show file tree
Hide file tree
Showing 5 changed files with 126 additions and 148 deletions.
4 changes: 2 additions & 2 deletions diofant/__init__.py
Expand Up @@ -44,7 +44,7 @@
interpolating_poly, invert, itermonomials, jacobi_poly,
laguerre_poly, lcm, legendre_poly, lex,
minimal_polynomial, monic, nroots, parallel_poly_from_expr,
poly, primitive, primitive_element, quo, random_poly,
primitive, primitive_element, quo, random_poly,
real_roots, reduced, rem, resultant, ring, roots,
spherical_bessel_fn, sqf, sqf_list, sqf_norm, sqf_part,
subresultants, swinnerton_dyer_poly, symmetric_poly,
Expand Down Expand Up @@ -206,7 +206,7 @@
'ilex', 'interpolate', 'interpolating_poly', 'invert', 'itermonomials',
'jacobi_poly', 'laguerre_poly', 'lcm', 'legendre_poly', 'lex',
'minimal_polynomial', 'monic', 'nroots', 'parallel_poly_from_expr',
'poly', 'primitive', 'primitive_element', 'quo', 'random_poly',
'primitive', 'primitive_element', 'quo', 'random_poly',
'real_roots', 'reduced', 'rem', 'resultant', 'ring', 'roots',
'spherical_bessel_fn', 'sqf', 'sqf_list', 'sqf_norm', 'sqf_part',
'subresultants', 'swinnerton_dyer_poly', 'symmetric_poly',
Expand Down
4 changes: 2 additions & 2 deletions diofant/polys/__init__.py
Expand Up @@ -9,7 +9,7 @@
primitive, compose, decompose,
sqf_norm, sqf_part, sqf_list, sqf, factor_list,
factor, count_roots, real_roots, nroots,
cancel, reduced, groebner, GroebnerBasis, poly)
cancel, reduced, groebner, GroebnerBasis)
from .polyfuncs import symmetrize, horner, interpolate, viete
from .rationaltools import together
from .polyerrors import (BasePolynomialError,
Expand Down Expand Up @@ -52,7 +52,7 @@
'trunc', 'monic', 'content', 'primitive', 'compose', 'decompose',
'sqf_norm', 'sqf_part', 'sqf_list', 'sqf', 'factor_list',
'factor', 'count_roots', 'real_roots', 'nroots', 'cancel',
'reduced', 'groebner', 'GroebnerBasis', 'poly', 'symmetrize',
'reduced', 'groebner', 'GroebnerBasis', 'symmetrize',
'horner', 'interpolate', 'viete', 'together', 'BasePolynomialError',
'ExactQuotientFailed', 'PolynomialDivisionFailed',
'OperationNotSupported', 'HeuristicGCDFailed',
Expand Down
169 changes: 73 additions & 96 deletions diofant/polys/polytools.py
Expand Up @@ -6,8 +6,8 @@

import mpmath

from ..core import (Add, Basic, E, Expr, Integer, Mul, Tuple, oo,
preorder_traversal)
from ..core import (Add, Basic, E, Expr, Integer, Mul, Tuple, expand_log,
expand_power_exp, oo, preorder_traversal)
from ..core.compatibility import iterable
from ..core.decorators import _sympifyit
from ..core.mul import _keep_coeff
Expand Down Expand Up @@ -41,7 +41,7 @@
'sqf_norm', 'sqf_part', 'sqf_list', 'sqf',
'factor_list', 'factor', 'count_roots',
'real_roots', 'nroots',
'cancel', 'reduced', 'groebner', 'GroebnerBasis', 'poly')
'cancel', 'reduced', 'groebner', 'GroebnerBasis')


class Poly(Expr):
Expand Down Expand Up @@ -176,8 +176,76 @@ def _from_poly(cls, rep, opt):
@classmethod
def _from_expr(cls, rep, opt):
"""Construct a polynomial from an expression."""
(rep,), opt = _parallel_dict_from_expr([rep], opt)
return cls._from_dict(rep, opt)
def _poly(expr, opt):
terms, poly_terms = [], []

for term in Add.make_args(expr):
factors, poly_factors = [], []

for factor in Mul.make_args(term):
if factor.is_Add:
poly_factors.append(_poly(factor, opt))
elif (factor.is_Pow and factor.base.is_Add and
factor.exp.is_Integer and factor.exp >= 0):
poly_factors.append(_poly(factor.base, opt)**factor.exp)
else:
factors.append(factor)

if not poly_factors:
terms.append(term)
else:
product = poly_factors[0]

for factor in poly_factors[1:]:
product *= factor

if factors:
factor = Mul(*factors)

if factor.is_Number:
product *= factor
else:
(factor,), _opt = _parallel_dict_from_expr([factor], opt)
factor = cls._from_dict(factor, _opt)
product *= factor

poly_terms.append(product)

if not poly_terms:
(expr,), _opt = _parallel_dict_from_expr([expr], opt)
result = cls._from_dict(expr, _opt)
else:
result = poly_terms[0]

for term in poly_terms[1:]:
result += term

if terms:
term = Add(*terms)

if term.is_Number:
result += term
else:
(term,), _opt = _parallel_dict_from_expr([term], opt)
term = cls._from_dict(term, _opt)
result += term

return result.reorder(*opt.gens, sort=opt.sort, wrt=opt.wrt)

rep = sympify(rep)
rep = rep.replace(lambda e: e.is_Pow and e.base != E and
not e.exp.is_number, expand_power_exp)
rep = expand_log(rep)

if not opt.gens:
gens = _find_gens([rep], opt)
opt = opt.clone({'gens': gens})

if opt.expand is False:
(rep,), opt = _parallel_dict_from_expr([rep], opt)
return cls._from_dict(rep, opt)
else:
return _poly(rep, opt.clone({'expand': False}))

def _hashable_content(self):
"""Allow Diofant to hash Poly instances."""
Expand Down Expand Up @@ -2466,9 +2534,6 @@ def parallel_poly_from_expr(exprs, *gens, **args):

expr = expr.__class__._from_poly(expr, opt)
else:
if opt.expand:
expr = expr.expand()

try:
expr = Poly._from_expr(expr, opt)
_exprs.append(i)
Expand Down Expand Up @@ -4244,91 +4309,3 @@ def contains(self, poly):
"""
return self.reduce(poly)[1] == 0


def poly(expr, *gens, **args):
"""
Efficiently transform an expression into a polynomial.
Examples
========
>>> poly(x*(x**2 + x - 1)**2)
Poly(x**5 + 2*x**4 - x**3 - 2*x**2 + x, x, domain='ZZ')
"""
allowed_flags(args, [])

def _poly(expr, opt):
terms, poly_terms = [], []

for term in Add.make_args(expr):
factors, poly_factors = [], []

for factor in Mul.make_args(term):
if factor.is_Add:
poly_factors.append(_poly(factor, opt))
elif (factor.is_Pow and factor.base.is_Add and
factor.exp.is_Integer and factor.exp >= 0):
poly_factors.append(_poly(factor.base,
opt)**factor.exp)
else:
factors.append(factor)

if not poly_factors:
terms.append(term)
else:
product = poly_factors[0]

for factor in poly_factors[1:]:
product *= factor

if factors:
factor = Mul(*factors)

if factor.is_Number:
product *= factor
else:
product *= Poly._from_expr(factor, opt)

poly_terms.append(product)

if not poly_terms:
result = Poly._from_expr(expr, opt)
else:
result = poly_terms[0]

for term in poly_terms[1:]:
result += term

if terms:
term = Add(*terms)

if term.is_Number:
result += term
else:
result += Poly._from_expr(term, opt)

return result.reorder(*opt.get('gens', ()), **args)

expr = sympify(expr)

if expr.is_Poly:
return Poly(expr, *gens, **args)

opt = build_options(gens, args)
no_gens = not opt.gens

if no_gens:
gens = _find_gens([expr], opt)
opt = opt.clone({'gens': gens})

if 'expand' not in args:
opt = opt.clone({'expand': False})

res = _poly(expr, opt)

if no_gens:
res = res.exclude()

return res
92 changes: 44 additions & 48 deletions diofant/tests/polys/test_polytools.py
Expand Up @@ -18,7 +18,7 @@
div, exp, expand, exquo, factor, factor_list, false, gcd,
gcdex, grevlex, grlex, groebner, half_gcdex, im, invert,
lcm, lex, log, monic, nroots, oo, parallel_poly_from_expr,
pi, poly, primitive, quo, re, real_roots, reduced, rem,
pi, primitive, quo, re, real_roots, reduced, rem,
resultant, sin, sqf, sqf_list, sqf_norm, sqf_part, sqrt,
subresultants, symbols, sympify, tan, tanh, terms_gcd,
true, trunc)
Expand Down Expand Up @@ -1120,6 +1120,11 @@ def test_Poly_degree():
assert degree(x*(x + 1) - x**2 - x, x) == -oo


@pytest.mark.timeout(30)
def test_sympyissue_6322():
assert degree((1 + x)**10000) == 10000


def test_Poly_degree_list():
assert [Integer(0).as_poly(x, y).degree(_) for _ in (x, y)] == [-math.inf]*2
assert [Integer(0).as_poly(x, y, z).degree(_) for _ in (x, y, z)] == [-math.inf]*3
Expand Down Expand Up @@ -2670,6 +2675,12 @@ def test_cancel():
assert cancel(((x - 1)**2/(x - 1), (x + 2*x**2)/x,
(x - x**3)/x)) == (x - 1, 2*x + 1, -x**2 + 1)

# issue sympy/sympy#12531
e = (x**4/24 - x*(x**3/24 + Rational(7, 8)) +
13*x/12)/((x**3/24 + Rational(7, 8))*(-x**4/6 - x/3) +
(x**3/6 - Rational(1, 2))*(x**4/24 + 13*x/12))
assert cancel(e) == Rational(-1, 4)


def test_reduced():
f = 2*x**4 + y**2 - x**2 + y**3
Expand Down Expand Up @@ -2957,68 +2968,53 @@ def test_GroebnerBasis():
assert (G == 1) is False


def test_poly():
assert poly(x) == x.as_poly()
assert poly(y) == y.as_poly()

assert poly(x + y) == (x + y).as_poly()
assert poly(x + sin(x)) == (x + sin(x)).as_poly()

assert poly(x + y, wrt=y) == (x + y).as_poly(y, x)
assert poly(x + sin(x), wrt=sin(x)) == (x + sin(x)).as_poly(sin(x), x)
def test_Poly_from_expr_recursive():
assert (x*(x**2 + x - 1)**2).as_poly() == (x**5 + 2*x**4 - x**3 -
2*x**2 + x).as_poly()

assert poly(x*y + 2*x*z**2 + 17) == (x*y + 2*x*z**2 + 17).as_poly()
assert (x + y).as_poly(wrt=y) == (x + y).as_poly(y, x)
assert (x + sin(x)).as_poly(wrt=sin(x)) == (x + sin(x)).as_poly(sin(x), x)

assert poly(2*(y + z)**2 - 1) == (2*y**2 + 4*y*z + 2*z**2 - 1).as_poly()
assert poly(
x*(y + z)**2 - 1) == (x*y**2 + 2*x*y*z + x*z**2 - 1).as_poly()
assert poly(2*x*(
y + z)**2 - 1) == (2*x*y**2 + 4*x*y*z + 2*x*z**2 - 1).as_poly()
assert (2*(y + z)**2 - 1).as_poly() == (2*y**2 + 4*y*z +
2*z**2 - 1).as_poly()
assert (x*(y + z)**2 - 1).as_poly() == (x*y**2 + 2*x*y*z +
x*z**2 - 1).as_poly()
assert (2*x*(y + z)**2 - 1).as_poly() == (2*x*y**2 + 4*x*y*z +
2*x*z**2 - 1).as_poly()

assert poly(2*(
y + z)**2 - x - 1) == (2*y**2 + 4*y*z + 2*z**2 - x - 1).as_poly()
assert poly(x*(
y + z)**2 - x - 1) == (x*y**2 + 2*x*y*z + x*z**2 - x - 1).as_poly()
assert poly(2*x*(y + z)**2 - x - 1) == (2*x*y**2 + 4*x*y*z + 2 *
x*z**2 - x - 1).as_poly()
assert (2*(y + z)**2 - x - 1).as_poly() == (2*y**2 + 4*y*z + 2*z**2 -
x - 1).as_poly()
assert (x*(y + z)**2 - x - 1).as_poly() == (x*y**2 + 2*x*y*z +
x*z**2 - x - 1).as_poly()
assert (2*x*(y + z)**2 - x - 1).as_poly() == (2*x*y**2 + 4*x*y*z + 2 *
x*z**2 - x - 1).as_poly()

assert poly(x*y + (x + y)**2 + (x + z)**2) == \
(2*x*z + 3*x*y + y**2 + z**2 + 2*x**2).as_poly()
assert poly(x*y*(x + y)*(x + z)**2) == \
(x**3*y**2 + x*y**2*z**2 + y*x**2*z**2 + 2*z*x**2 *
y**2 + 2*y*z*x**3 + y*x**4).as_poly()
assert (x*y + (x + y)**2 + (x + z)**2).as_poly() == (2*x*z + 3*x*y + y**2 +
z**2 + 2*x**2).as_poly()
assert (x*y*(x + y)*(x + z)**2).as_poly() == (x**3*y**2 + x*y**2*z**2 +
y*x**2*z**2 + 2*z*x**2*y**2 +
2*y*z*x**3 + y*x**4).as_poly()

assert poly((x + y + z).as_poly(y, x, z)) == (x + y + z).as_poly(y, x, z)

assert poly((x + y)**2, x) == (x**2 + 2*x*y + y**2).as_poly(x, domain=ZZ.inject(y))
assert poly((x + y)**2, x, expand=True) == (x**2 + 2*x*y +
y**2).as_poly(x, domain=ZZ.inject(y))
assert poly((x + y)**2, y) == (x**2 + 2*x*y + y**2).as_poly(y, domain=ZZ.inject(x))

assert poly(1, x) == Integer(1).as_poly(x)

pytest.raises(GeneratorsNeeded, lambda: poly(1))

assert poly((x + y)**2 - y**2 - 2*x*y) == (x**2).as_poly()
assert poly((x + y)**2 - y**2 - 2*x*y, x, y) == (x**2).as_poly(x, y)
assert ((x + y)**2).as_poly(x) == (x**2 + 2*x*y + y**2).as_poly(x)
assert ((x + y)**2).as_poly(x, expand=True) == (x**2 + 2*x*y +
y**2).as_poly(x)
assert ((x + y)**2).as_poly(y) == (x**2 + 2*x*y + y**2).as_poly(y)
assert ((x + y)**2 - y**2 - 2*x*y).as_poly() == (x**2).as_poly(x, y)

e = x**2 + (1 + sqrt(2))*x + 1

assert (poly(e, greedy=False) == poly(e, x, greedy=False) ==
assert (e.as_poly(x, greedy=False) ==
e.as_poly(x, domain=QQ.algebraic_field(sqrt(2))))

# issue sympy/sympy#6184
assert poly(x + y, x, y) == (x + y).as_poly()
assert poly(x + y, y, x) == (x + y).as_poly(y, x)

# issue sympy/sympy#12400
assert (poly(1/(1 + sqrt(2)), x) ==
assert ((1/(1 + sqrt(2))).as_poly(x) ==
(1/(1 + sqrt(2))).as_poly(x, domain=QQ.algebraic_field(1/(1 + sqrt(2)))))

# issue sympy/sympy#19755
assert (poly(x + (2*x + 3)**2/5 + Rational(6, 5)) ==
assert ((x + (2*x + 3)**2/5 + Rational(6, 5)).as_poly() ==
(4*x**2/5 + 17*x/5 + 3).as_poly(domain=QQ))
assert poly(((x + 1)**2)/2) == (x**2/2 + x + Rational(1, 2)).as_poly(domain=QQ)
assert (((x + 1)**2)/2).as_poly() == (x**2/2 + x +
Rational(1, 2)).as_poly(domain=QQ)


def test_keep_coeff():
Expand Down
5 changes: 5 additions & 0 deletions docs/release/notes-0.14.rst
Expand Up @@ -10,11 +10,14 @@ New features
Major changes
=============

* Use recursive (former ``poly()`` method, without using :func:`~diofant.core.function.expand`) algorithm of creating polynomials from expressions, see :pull:`1047`.

Compatibility breaks
====================

* Removed support for CPython 3.9, see :pull:`1192`.
* Removed ``to_mpi()`` method of :class:`~diofant.sets.sets.Interval`, see :pull:`1194`.
* Removed ``poly()`` function, use :meth:`~diofant.core.expr.Expr.as_poly` method to create a :class:`~diofant.polys.polytools.Poly` instance from :class:`~diofant.core.expr.Expr`, see :pull:`1047`.

Minor changes
=============
Expand Down Expand Up @@ -52,3 +55,5 @@ These Sympy issues also were addressed:
* :sympyissue:`23174`: Problem with gf_edf_zassenhaus()
* :sympyissue:`21409`: Printing of polynomial over FF
* :sympyissue:`22673`: Roots of a polynomial over a finite field computed regardless of specified polynomial domain
* :sympyissue:`12531`: cancel does not return expanded form
* :sympyissue:`6322`: degree((x+1)**10000) takes too long

0 comments on commit ac3c182

Please sign in to comment.