Skip to content

Commit

Permalink
Merge pull request #1319 from skirpichev/0.14
Browse files Browse the repository at this point in the history
v0.14
  • Loading branch information
skirpichev committed Apr 11, 2023
2 parents eb9739f + d7e02a5 commit f4cc0e4
Show file tree
Hide file tree
Showing 16 changed files with 209 additions and 122 deletions.
2 changes: 1 addition & 1 deletion diofant/domains/algebraicfield.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ def algebraic_field(self, *extension):

def to_expr(self, element):
rep = element.rep
return rep.ring.to_expr(rep)
return rep.ring.to_expr(rep).expand()

def from_expr(self, expr):
from ..polys import primitive_element
Expand Down
168 changes: 120 additions & 48 deletions diofant/polys/rings.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@
from .factortools import _Factor
from .monomials import Monomial
from .orderings import lex
from .polyerrors import (CoercionFailedError, ExactQuotientFailedError,
GeneratorsError, GeneratorsNeededError,
PolynomialDivisionFailedError)
from .polyerrors import (CoercionFailedError, DomainError,
ExactQuotientFailedError, GeneratorsError,
GeneratorsNeededError, PolynomialDivisionFailedError)
from .polyoptions import Domain as DomainOpt
from .polyoptions import Order as OrderOpt
from .specialpolys import _TestPolys
Expand Down Expand Up @@ -73,7 +73,7 @@ def _parse_symbols(symbols):


class PolynomialRing(_GCD, CommutativeRing, CompositeDomain, _SQF, _Factor, _TestPolys):
"""A class for representing multivariate polynomial rings."""
"""Return a multivariate polynomial ring."""

is_PolynomialRing = True

Expand Down Expand Up @@ -188,7 +188,10 @@ def __call__(self, element):
if isinstance(element, dict):
return self.from_dict(element)
if isinstance(element, list):
return self.from_terms(element)
try:
return self.from_terms(element)
except (TypeError, ValueError):
return self.from_list(element)
if isinstance(element, Expr):
return self.convert(element)
return self.ground_new(element)
Expand All @@ -208,6 +211,18 @@ def from_dict(self, element):
def from_terms(self, element):
return self.from_dict(dict(element))

def from_list(self, element):
if self.is_univariate:
domain = self.domain
if any(isinstance(c, list) for c in element):
return self.from_dict({(i,): domain.from_list(c)
for i, c in enumerate(element)})
return self.from_dict({(i,): domain.convert(c)
for i, c in enumerate(element)})
new_ring = self.eject(*self.gens[1:])
poly = new_ring.from_list(element)
return poly.inject()

def from_expr(self, expr):
expr = sympify(expr)
mapping = dict(zip(self.symbols, self.gens))
Expand Down Expand Up @@ -337,34 +352,10 @@ def half_gcdex(self, a, b):


class PolyElement(DomainElement, CantSympify, dict):
"""Element of multivariate distributed polynomial ring.
Polynomial element is mutable, until the hash is computed, e.g.
when the polynomial was added to the :class:`set`.
If one is interested in preserving a polynomial, and one plans
to use inplace operations, one can copy the polynomial first.
Examples
========
>>> _, x, y = ring('x y', ZZ)
"""Represent a polynomial in a multivariate polynomial ring.
>>> p = (x + y)**2
>>> p1 = p.copy()
>>> p2 = p
>>> p[(0, 0)] = 3
>>> p1
x**2 + 2*x*y + y**2
>>> p2
x**2 + 2*x*y + y**2 + 3
>>> _ = hash(p)
>>> p[(1, 1)] = 2
Traceback (most recent call last):
...
RuntimeError: ... Polynomial element ... can't be modified ...
A polynomial is mutable, until its hash is computed, e.g. for
using an instance as a dictionary key.
See Also
========
Expand Down Expand Up @@ -457,13 +448,57 @@ def _strip_zero(self):
if not v:
del self[k]

def __setitem__(self, key, item):
def __setitem__(self, monom, coeff, /):
"""Set the coefficient for the given monomial.
Parameters
==========
monom : Monomial or PolyElement (with ``is_monomial = True``) or 1
coeff : DomainElement
Examples
========
>>> _, x, y = ring('x y', ZZ)
>>> p = (x + y)**2
>>> p1 = p.copy()
>>> p2 = p
>>> p[x*y] = 0
>>> p1
x**2 + 2*x*y + y**2
>>> p2
x**2 + y**2
>>> _ = hash(p)
>>> p[x*y] = 1
Traceback (most recent call last):
...
RuntimeError: Polynomial x**2 + y**2 can't be modified
"""
if self._hash is not None:
raise RuntimeError(f"Polynomial element {self} can't be"
' modified anymore.')
if not isinstance(key, Monomial):
key = Monomial(key)
super().__setitem__(key, item)
raise RuntimeError(f"polynomial {self} can't be modified")

ring = self.ring

if isinstance(monom, Monomial):
pass
elif isinstance(monom, (tuple, list)):
monom = Monomial(monom)
elif isinstance(monom, ring.dtype) and monom.is_monomial:
monom, = monom
elif monom == 1:
monom = ring.zero_monom
else:
raise TypeError(f'monomial expected, got {monom}')

if coeff:
super().__setitem__(monom, coeff)
elif monom in self:
del self[monom]

def __eq__(self, other):
"""Equality test for polynomials.
Expand Down Expand Up @@ -1039,14 +1074,13 @@ def leading_expv(self, order=None):
def _get_coeff(self, expv):
return self.get(expv, self.ring.domain.zero)

def __getitem__(self, element):
"""
Returns the coefficient that stands next to the given monomial.
def __getitem__(self, monom, /):
"""Return the coefficient for the given monomial.
Parameters
==========
element : PolyElement (with ``is_monomial = True``) or 1
monom : Monomial or PolyElement (with ``is_monomial = True``) or 1
Examples
========
Expand All @@ -1064,15 +1098,15 @@ def __getitem__(self, element):
"""
ring = self.ring

if isinstance(element, tuple):
return self._get_coeff(element)
if element == 1:
if isinstance(monom, tuple):
return self._get_coeff(monom)
if monom == 1:
return self._get_coeff(ring.zero_monom)
if isinstance(element, ring.dtype) and element.is_monomial:
monom, = element
if isinstance(monom, ring.dtype) and monom.is_monomial:
monom, = monom
return self._get_coeff(monom)

raise ValueError(f'expected a monomial, got {element}')
raise TypeError(f'expected a monomial, got {monom}')

@property
def LC(self):
Expand Down Expand Up @@ -1530,6 +1564,44 @@ def subresultants(self, other):
"""
return self.resultant(other, includePRS=True)[1]

def half_gcdex(self, other):
"""
Half extended Euclidean algorithm in `F[x]`.
Returns ``(s, h)`` such that ``h = gcd(self, other)``
and ``s*self = h (mod other)``.
Examples
========
>>> _, x = ring('x', QQ)
>>> f = x**4 - 2*x**3 - 6*x**2 + 12*x + 15
>>> g = x**3 + x**2 - 4*x - 4
>>> f.half_gcdex(g)
(-1/5*x + 3/5, x + 1)
"""
ring = self.ring
domain = ring.domain

if not domain.is_Field:
raise DomainError(f"can't compute half extended GCD over {domain}")

a, b = ring.one, ring.zero
f, g = self, other

while g:
q, r = divmod(f, g)
f, g = g, r
a, b = b, a - q*b

a = a.quo_ground(f.LC)
f = f.monic()

return a, f

def gcdex(self, other):
"""
Extended Euclidean algorithm in `F[x]`.
Expand Down
2 changes: 1 addition & 1 deletion diofant/polys/solvers.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
class RawMatrix(Matrix):
"""Dummy class with overriden sympify() helper."""

_sympify = staticmethod(lambda x: x)
_sympify = staticmethod(lambda x: x) # type: ignore[misc,arg-type] # see e.g. python/mypy#13818


def eqs_to_matrix(eqs, ring):
Expand Down
2 changes: 1 addition & 1 deletion diofant/polys/sqfreetools.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ def _gf_sqf_list(self, f):

g = self.zero
for monom, coeff in f.items():
g[(_ // p for _ in monom)] = coeff**m
g[tuple(_ // p for _ in monom)] = coeff**m
f = g

return factors
Expand Down
51 changes: 1 addition & 50 deletions diofant/polys/univar.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,14 @@

from ..config import query
from ..domains import ZZ
from .polyerrors import CoercionFailedError, DomainError
from .polyerrors import CoercionFailedError
from .rings import PolyElement, PolynomialRing
from .rootisolation import _FindRoot


class UnivarPolynomialRing(PolynomialRing, _FindRoot):
"""A class for representing univariate polynomial rings."""

def __call__(self, element):
if isinstance(element, list):
try:
return self.from_terms(element)
except (TypeError, ValueError):
return self.from_list(element)
return super().__call__(element)

def from_list(self, element):
return self.from_dict({(i,): c for i, c in enumerate(element)})

def _random(self, n, a, b, percent=None):
domain = self.domain

Expand Down Expand Up @@ -170,44 +159,6 @@ class UnivarPolyElement(PolyElement):
def shift(self, a):
return self.compose(0, self.ring.gens[0] + a)

def half_gcdex(self, other):
"""
Half extended Euclidean algorithm in `F[x]`.
Returns ``(s, h)`` such that ``h = gcd(self, other)``
and ``s*self = h (mod other)``.
Examples
========
>>> _, x = ring('x', QQ)
>>> f = x**4 - 2*x**3 - 6*x**2 + 12*x + 15
>>> g = x**3 + x**2 - 4*x - 4
>>> f.half_gcdex(g)
(-1/5*x + 3/5, x + 1)
"""
ring = self.ring
domain = ring.domain

if not domain.is_Field:
raise DomainError(f"can't compute half extended GCD over {domain}")

a, b = ring.one, ring.zero
f, g = self, other

while g:
q, r = divmod(f, g)
f, g = g, r
a, b = b, a - q*b

a = a.quo_ground(f.LC)
f = f.monic()

return a, f

@property
def is_cyclotomic(self):
return self.ring._cyclotomic_p(self)
Expand Down
5 changes: 5 additions & 0 deletions diofant/printing/latex.py
Original file line number Diff line number Diff line change
Expand Up @@ -547,6 +547,11 @@ def _print_Integral(self, expr):
def _print_Limit(self, expr):
e, z, z0, dir = expr.args

if dir not in [Reals, 1, -1]:
e = e.subs({z: z0 - dir*z})
z0 = 0
dir = -1

tex = r'\lim_{%s \to ' % self._print(z)
if dir == Reals or z0 in (oo, -oo):
tex += r'%s}' % self._print(z0)
Expand Down
5 changes: 5 additions & 0 deletions diofant/printing/pretty/pretty.py
Original file line number Diff line number Diff line change
Expand Up @@ -486,6 +486,11 @@ def asum(hrequired, lower, upper, use_ascii):
def _print_Limit(self, l):
e, z, z0, dir = l.args

if dir not in [Reals, 1, -1]:
e = e.subs({z: z0 - dir*z})
z0 = 0
dir = -1

E = self._print(e)
if e.is_Add or e.is_Relational:
E = prettyForm(*E.parens())
Expand Down
6 changes: 3 additions & 3 deletions diofant/tests/polys/test_factorization_alg_field.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ def test_efactor_1():
f = x**4 + y**4

assert f1*f2*f3*f4 == f
assert efactor(f) == (1, [(f1, 1), (f2, 1), (f3, 1), (f4, 1)])
assert efactor(f) == (1, [(f3, 1), (f2, 1), (f4, 1), (f1, 1)])

R, x, y, z = ring('x y z', QQ.algebraic_field(root(2, 5)))

Expand Down Expand Up @@ -124,7 +124,7 @@ def test_efactor_1():
f2 = x**2 - 2*a*x - (a**3 + a**2 + a + 1)*z**2 + a**2*y + 12*a**3
f = f1*f2

assert efactor(f) == (1, [(f1, 1), (f2, 1)])
assert efactor(f) == (1, [(f2, 1), (f1, 1)])

R, x, y = ring('x y', QQ.algebraic_field(root(1, 5, 3)))
A = R.domain
Expand Down Expand Up @@ -174,7 +174,7 @@ def test_efactor_wang():
f4 = x**2 - 2*a*x - (a**3 + a**2 + a + 1)*z**2 + a**2*y + 12*a**3
f = f1*f2*f3*f4

assert efactor(f) == (1, [(f1, 1), (f2, 1), (f3, 1), (f4, 1)])
assert efactor(f) == (1, [(f2, 1), (f1, 1), (f4, 1), (f3, 1)])


@pytest.mark.timeout(60)
Expand Down

0 comments on commit f4cc0e4

Please sign in to comment.