|
""" |
|
Puiseux rings. These are used by the ring_series module to represented |
|
truncated Puiseux series. Elements of a Puiseux ring are like polynomials |
|
except that the exponents can be negative or rational rather than just |
|
non-negative integers. |
|
""" |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
from __future__ import annotations |
|
|
|
from sympy.polys.domains import QQ |
|
from sympy.polys.rings import PolyRing, PolyElement |
|
from sympy.core.add import Add |
|
from sympy.core.mul import Mul |
|
from sympy.external.gmpy import gcd, lcm |
|
|
|
|
|
from typing import TYPE_CHECKING |
|
|
|
|
|
if TYPE_CHECKING: |
|
from typing import Any, Unpack |
|
from sympy.core.expr import Expr |
|
from sympy.polys.domains import Domain |
|
from collections.abc import Iterable, Iterator |
|
|
|
|
|
def puiseux_ring( |
|
symbols: str | list[Expr], domain: Domain |
|
) -> tuple[PuiseuxRing, Unpack[tuple[PuiseuxPoly, ...]]]: |
|
"""Construct a Puiseux ring. |
|
|
|
This function constructs a Puiseux ring with the given symbols and domain. |
|
|
|
>>> from sympy.polys.domains import QQ |
|
>>> from sympy.polys.puiseux import puiseux_ring |
|
>>> R, x, y = puiseux_ring('x y', QQ) |
|
>>> R |
|
PuiseuxRing((x, y), QQ) |
|
>>> p = 5*x**QQ(1,2) + 7/y |
|
>>> p |
|
7*y**(-1) + 5*x**(1/2) |
|
""" |
|
ring = PuiseuxRing(symbols, domain) |
|
return (ring,) + ring.gens |
|
|
|
|
|
class PuiseuxRing: |
|
"""Ring of Puiseux polynomials. |
|
|
|
A Puiseux polynomial is a truncated Puiseux series. The exponents of the |
|
monomials can be negative or rational numbers. This ring is used by the |
|
ring_series module: |
|
|
|
>>> from sympy.polys.domains import QQ |
|
>>> from sympy.polys.puiseux import puiseux_ring |
|
>>> from sympy.polys.ring_series import rs_exp, rs_nth_root |
|
>>> ring, x, y = puiseux_ring('x y', QQ) |
|
>>> f = x**2 + y**3 |
|
>>> f |
|
y**3 + x**2 |
|
>>> f.diff(x) |
|
2*x |
|
>>> rs_exp(x, x, 5) |
|
1 + x + 1/2*x**2 + 1/6*x**3 + 1/24*x**4 |
|
|
|
Importantly the Puiseux ring can represent truncated series with negative |
|
and fractional exponents: |
|
|
|
>>> f = 1/x + 1/y**2 |
|
>>> f |
|
x**(-1) + y**(-2) |
|
>>> f.diff(x) |
|
-1*x**(-2) |
|
|
|
>>> rs_nth_root(8*x + x**2 + x**3, 3, x, 5) |
|
2*x**(1/3) + 1/12*x**(4/3) + 23/288*x**(7/3) + -139/20736*x**(10/3) |
|
|
|
See Also |
|
======== |
|
|
|
sympy.polys.ring_series.rs_series |
|
PuiseuxPoly |
|
""" |
|
def __init__(self, symbols: str | list[Expr], domain: Domain): |
|
|
|
poly_ring = PolyRing(symbols, domain) |
|
|
|
domain = poly_ring.domain |
|
ngens = poly_ring.ngens |
|
|
|
self.poly_ring = poly_ring |
|
self.domain = domain |
|
|
|
self.symbols = poly_ring.symbols |
|
self.gens = tuple([self.from_poly(g) for g in poly_ring.gens]) |
|
self.ngens = ngens |
|
|
|
self.zero = self.from_poly(poly_ring.zero) |
|
self.one = self.from_poly(poly_ring.one) |
|
|
|
self.zero_monom = poly_ring.zero_monom |
|
self.monomial_mul = poly_ring.monomial_mul |
|
|
|
def __repr__(self) -> str: |
|
return f"PuiseuxRing({self.symbols}, {self.domain})" |
|
|
|
def __eq__(self, other: Any) -> bool: |
|
if not isinstance(other, PuiseuxRing): |
|
return NotImplemented |
|
return self.symbols == other.symbols and self.domain == other.domain |
|
|
|
def from_poly(self, poly: PolyElement) -> PuiseuxPoly: |
|
"""Create a Puiseux polynomial from a polynomial. |
|
|
|
>>> from sympy.polys.domains import QQ |
|
>>> from sympy.polys.rings import ring |
|
>>> from sympy.polys.puiseux import puiseux_ring |
|
>>> R1, x1 = ring('x', QQ) |
|
>>> R2, x2 = puiseux_ring('x', QQ) |
|
>>> R2.from_poly(x1**2) |
|
x**2 |
|
""" |
|
return PuiseuxPoly(poly, self) |
|
|
|
def from_dict(self, terms: dict[tuple[int, ...], Any]) -> PuiseuxPoly: |
|
"""Create a Puiseux polynomial from a dictionary of terms. |
|
|
|
>>> from sympy.polys.domains import QQ |
|
>>> from sympy.polys.puiseux import puiseux_ring |
|
>>> R, x = puiseux_ring('x', QQ) |
|
>>> R.from_dict({(QQ(1,2),): QQ(3)}) |
|
3*x**(1/2) |
|
""" |
|
return PuiseuxPoly.from_dict(terms, self) |
|
|
|
def from_int(self, n: int) -> PuiseuxPoly: |
|
"""Create a Puiseux polynomial from an integer. |
|
|
|
>>> from sympy.polys.domains import QQ |
|
>>> from sympy.polys.puiseux import puiseux_ring |
|
>>> R, x = puiseux_ring('x', QQ) |
|
>>> R.from_int(3) |
|
3 |
|
""" |
|
return self.from_poly(self.poly_ring(n)) |
|
|
|
def domain_new(self, arg: Any) -> Any: |
|
"""Create a new element of the domain. |
|
|
|
>>> from sympy.polys.domains import QQ |
|
>>> from sympy.polys.puiseux import puiseux_ring |
|
>>> R, x = puiseux_ring('x', QQ) |
|
>>> R.domain_new(3) |
|
3 |
|
>>> QQ.of_type(_) |
|
True |
|
""" |
|
return self.poly_ring.domain_new(arg) |
|
|
|
def ground_new(self, arg: Any) -> PuiseuxPoly: |
|
"""Create a new element from a ground element. |
|
|
|
>>> from sympy.polys.domains import QQ |
|
>>> from sympy.polys.puiseux import puiseux_ring, PuiseuxPoly |
|
>>> R, x = puiseux_ring('x', QQ) |
|
>>> R.ground_new(3) |
|
3 |
|
>>> isinstance(_, PuiseuxPoly) |
|
True |
|
""" |
|
return self.from_poly(self.poly_ring.ground_new(arg)) |
|
|
|
def __call__(self, arg: Any) -> PuiseuxPoly: |
|
"""Coerce an element into the ring. |
|
|
|
>>> from sympy.polys.domains import QQ |
|
>>> from sympy.polys.puiseux import puiseux_ring |
|
>>> R, x = puiseux_ring('x', QQ) |
|
>>> R(3) |
|
3 |
|
>>> R({(QQ(1,2),): QQ(3)}) |
|
3*x**(1/2) |
|
""" |
|
if isinstance(arg, dict): |
|
return self.from_dict(arg) |
|
else: |
|
return self.from_poly(self.poly_ring(arg)) |
|
|
|
def index(self, x: PuiseuxPoly) -> int: |
|
"""Return the index of a generator. |
|
|
|
>>> from sympy.polys.domains import QQ |
|
>>> from sympy.polys.puiseux import puiseux_ring |
|
>>> R, x, y = puiseux_ring('x y', QQ) |
|
>>> R.index(x) |
|
0 |
|
>>> R.index(y) |
|
1 |
|
""" |
|
return self.gens.index(x) |
|
|
|
|
|
def _div_poly_monom(poly: PolyElement, monom: Iterable[int]) -> PolyElement: |
|
ring = poly.ring |
|
div = ring.monomial_div |
|
return ring.from_dict({div(m, monom): c for m, c in poly.terms()}) |
|
|
|
|
|
def _mul_poly_monom(poly: PolyElement, monom: Iterable[int]) -> PolyElement: |
|
ring = poly.ring |
|
mul = ring.monomial_mul |
|
return ring.from_dict({mul(m, monom): c for m, c in poly.terms()}) |
|
|
|
|
|
def _div_monom(monom: Iterable[int], div: Iterable[int]) -> tuple[int, ...]: |
|
return tuple(mi - di for mi, di in zip(monom, div)) |
|
|
|
|
|
class PuiseuxPoly: |
|
"""Puiseux polynomial. Represents a truncated Puiseux series. |
|
|
|
See the :class:`PuiseuxRing` class for more information. |
|
|
|
>>> from sympy import QQ |
|
>>> from sympy.polys.puiseux import puiseux_ring |
|
>>> R, x, y = puiseux_ring('x, y', QQ) |
|
>>> p = 5*x**2 + 7*y**3 |
|
>>> p |
|
7*y**3 + 5*x**2 |
|
|
|
The internal representation of a Puiseux polynomial wraps a normal |
|
polynomial. To support negative powers the polynomial is considered to be |
|
divided by a monomial. |
|
|
|
>>> p2 = 1/x + 1/y**2 |
|
>>> p2.monom # x*y**2 |
|
(1, 2) |
|
>>> p2.poly |
|
x + y**2 |
|
>>> (y**2 + x) / (x*y**2) == p2 |
|
True |
|
|
|
To support fractional powers the polynomial is considered to be a function |
|
of ``x**(1/nx), y**(1/ny), ...``. The representation keeps track of a |
|
monomial and a list of exponent denominators so that the polynomial can be |
|
used to represent both negative and fractional powers. |
|
|
|
>>> p3 = x**QQ(1,2) + y**QQ(2,3) |
|
>>> p3.ns |
|
(2, 3) |
|
>>> p3.poly |
|
x + y**2 |
|
|
|
See Also |
|
======== |
|
|
|
sympy.polys.puiseux.PuiseuxRing |
|
sympy.polys.rings.PolyElement |
|
""" |
|
|
|
ring: PuiseuxRing |
|
poly: PolyElement |
|
monom: tuple[int, ...] | None |
|
ns: tuple[int, ...] | None |
|
|
|
def __new__(cls, poly: PolyElement, ring: PuiseuxRing) -> PuiseuxPoly: |
|
return cls._new(ring, poly, None, None) |
|
|
|
@classmethod |
|
def _new( |
|
cls, |
|
ring: PuiseuxRing, |
|
poly: PolyElement, |
|
monom: tuple[int, ...] | None, |
|
ns: tuple[int, ...] | None, |
|
) -> PuiseuxPoly: |
|
poly, monom, ns = cls._normalize(poly, monom, ns) |
|
return cls._new_raw(ring, poly, monom, ns) |
|
|
|
@classmethod |
|
def _new_raw( |
|
cls, |
|
ring: PuiseuxRing, |
|
poly: PolyElement, |
|
monom: tuple[int, ...] | None, |
|
ns: tuple[int, ...] | None, |
|
) -> PuiseuxPoly: |
|
obj = object.__new__(cls) |
|
obj.ring = ring |
|
obj.poly = poly |
|
obj.monom = monom |
|
obj.ns = ns |
|
return obj |
|
|
|
def __eq__(self, other: Any) -> bool: |
|
if isinstance(other, PuiseuxPoly): |
|
return ( |
|
self.poly == other.poly |
|
and self.monom == other.monom |
|
and self.ns == other.ns |
|
) |
|
elif self.monom is None and self.ns is None: |
|
return self.poly.__eq__(other) |
|
else: |
|
return NotImplemented |
|
|
|
@classmethod |
|
def _normalize( |
|
cls, |
|
poly: PolyElement, |
|
monom: tuple[int, ...] | None, |
|
ns: tuple[int, ...] | None, |
|
) -> tuple[PolyElement, tuple[int, ...] | None, tuple[int, ...] | None]: |
|
if monom is None and ns is None: |
|
return poly, None, None |
|
|
|
if monom is not None: |
|
degs = [max(d, 0) for d in poly.tail_degrees()] |
|
if all(di >= mi for di, mi in zip(degs, monom)): |
|
poly = _div_poly_monom(poly, monom) |
|
monom = None |
|
elif any(degs): |
|
poly = _div_poly_monom(poly, degs) |
|
monom = _div_monom(monom, degs) |
|
|
|
if ns is not None: |
|
factors_d, [poly_d] = poly.deflate() |
|
degrees = poly.degrees() |
|
monom_d = monom if monom is not None else [0] * len(degrees) |
|
ns_new = [] |
|
monom_new = [] |
|
inflations = [] |
|
for fi, ni, di, mi in zip(factors_d, ns, degrees, monom_d): |
|
if di == 0: |
|
g = gcd(ni, mi) |
|
else: |
|
g = gcd(fi, ni, mi) |
|
ns_new.append(ni // g) |
|
monom_new.append(mi // g) |
|
inflations.append(fi // g) |
|
|
|
if any(infl > 1 for infl in inflations): |
|
poly_d = poly_d.inflate(inflations) |
|
|
|
poly = poly_d |
|
|
|
if monom is not None: |
|
monom = tuple(monom_new) |
|
|
|
if all(n == 1 for n in ns_new): |
|
ns = None |
|
else: |
|
ns = tuple(ns_new) |
|
|
|
return poly, monom, ns |
|
|
|
@classmethod |
|
def _monom_fromint( |
|
cls, |
|
monom: tuple[int, ...], |
|
dmonom: tuple[int, ...] | None, |
|
ns: tuple[int, ...] | None, |
|
) -> tuple[Any, ...]: |
|
if dmonom is not None and ns is not None: |
|
return tuple(QQ(mi - di, ni) for mi, di, ni in zip(monom, dmonom, ns)) |
|
elif dmonom is not None: |
|
return tuple(QQ(mi - di) for mi, di in zip(monom, dmonom)) |
|
elif ns is not None: |
|
return tuple(QQ(mi, ni) for mi, ni in zip(monom, ns)) |
|
else: |
|
return tuple(QQ(mi) for mi in monom) |
|
|
|
@classmethod |
|
def _monom_toint( |
|
cls, |
|
monom: tuple[Any, ...], |
|
dmonom: tuple[int, ...] | None, |
|
ns: tuple[int, ...] | None, |
|
) -> tuple[int, ...]: |
|
if dmonom is not None and ns is not None: |
|
return tuple( |
|
int((mi * ni).numerator + di) for mi, di, ni in zip(monom, dmonom, ns) |
|
) |
|
elif dmonom is not None: |
|
return tuple(int(mi.numerator + di) for mi, di in zip(monom, dmonom)) |
|
elif ns is not None: |
|
return tuple(int((mi * ni).numerator) for mi, ni in zip(monom, ns)) |
|
else: |
|
return tuple(int(mi.numerator) for mi in monom) |
|
|
|
def itermonoms(self) -> Iterator[tuple[Any, ...]]: |
|
"""Iterate over the monomials of a Puiseux polynomial. |
|
|
|
>>> from sympy import QQ |
|
>>> from sympy.polys.puiseux import puiseux_ring |
|
>>> R, x, y = puiseux_ring('x, y', QQ) |
|
>>> p = 5*x**2 + 7*y**3 |
|
>>> list(p.itermonoms()) |
|
[(2, 0), (0, 3)] |
|
>>> p[(2, 0)] |
|
5 |
|
""" |
|
monom, ns = self.monom, self.ns |
|
for m in self.poly.itermonoms(): |
|
yield self._monom_fromint(m, monom, ns) |
|
|
|
def monoms(self) -> list[tuple[Any, ...]]: |
|
"""Return a list of the monomials of a Puiseux polynomial.""" |
|
return list(self.itermonoms()) |
|
|
|
def __iter__(self) -> Iterator[tuple[tuple[Any, ...], Any]]: |
|
return self.itermonoms() |
|
|
|
def __getitem__(self, monom: tuple[int, ...]) -> Any: |
|
monom = self._monom_toint(monom, self.monom, self.ns) |
|
return self.poly[monom] |
|
|
|
def __len__(self) -> int: |
|
return len(self.poly) |
|
|
|
def iterterms(self) -> Iterator[tuple[tuple[Any, ...], Any]]: |
|
"""Iterate over the terms of a Puiseux polynomial. |
|
|
|
>>> from sympy import QQ |
|
>>> from sympy.polys.puiseux import puiseux_ring |
|
>>> R, x, y = puiseux_ring('x, y', QQ) |
|
>>> p = 5*x**2 + 7*y**3 |
|
>>> list(p.iterterms()) |
|
[((2, 0), 5), ((0, 3), 7)] |
|
""" |
|
monom, ns = self.monom, self.ns |
|
for m, coeff in self.poly.iterterms(): |
|
mq = self._monom_fromint(m, monom, ns) |
|
yield mq, coeff |
|
|
|
def terms(self) -> list[tuple[tuple[Any, ...], Any]]: |
|
"""Return a list of the terms of a Puiseux polynomial.""" |
|
return list(self.iterterms()) |
|
|
|
@property |
|
def is_term(self) -> bool: |
|
"""Return True if the Puiseux polynomial is a single term.""" |
|
return self.poly.is_term |
|
|
|
def to_dict(self) -> dict[tuple[int, ...], Any]: |
|
"""Return a dictionary representation of a Puiseux polynomial.""" |
|
return dict(self.iterterms()) |
|
|
|
@classmethod |
|
def from_dict( |
|
cls, terms: dict[tuple[Any, ...], Any], ring: PuiseuxRing |
|
) -> PuiseuxPoly: |
|
"""Create a Puiseux polynomial from a dictionary of terms. |
|
|
|
>>> from sympy import QQ |
|
>>> from sympy.polys.puiseux import puiseux_ring, PuiseuxPoly |
|
>>> R, x = puiseux_ring('x', QQ) |
|
>>> PuiseuxPoly.from_dict({(QQ(1,2),): QQ(3)}, R) |
|
3*x**(1/2) |
|
>>> R.from_dict({(QQ(1,2),): QQ(3)}) |
|
3*x**(1/2) |
|
""" |
|
ns = [1] * ring.ngens |
|
mon = [0] * ring.ngens |
|
for mo in terms: |
|
ns = [lcm(n, m.denominator) for n, m in zip(ns, mo)] |
|
mon = [min(m, n) for m, n in zip(mo, mon)] |
|
|
|
if not any(mon): |
|
monom = None |
|
else: |
|
monom = tuple(-int((m * n).numerator) for m, n in zip(mon, ns)) |
|
|
|
if all(n == 1 for n in ns): |
|
ns_final = None |
|
else: |
|
ns_final = tuple(ns) |
|
|
|
terms_p = {cls._monom_toint(m, monom, ns_final): coeff for m, coeff in terms.items()} |
|
|
|
poly = ring.poly_ring.from_dict(terms_p) |
|
|
|
return cls._new(ring, poly, monom, ns_final) |
|
|
|
def as_expr(self) -> Expr: |
|
"""Convert a Puiseux polynomial to :class:`~sympy.core.expr.Expr`. |
|
|
|
>>> from sympy import QQ, Expr |
|
>>> from sympy.polys.puiseux import puiseux_ring |
|
>>> R, x = puiseux_ring('x', QQ) |
|
>>> p = 5*x**2 + 7*x**3 |
|
>>> p.as_expr() |
|
7*x**3 + 5*x**2 |
|
>>> isinstance(_, Expr) |
|
True |
|
""" |
|
ring = self.ring |
|
dom = ring.domain |
|
symbols = ring.symbols |
|
terms = [] |
|
for monom, coeff in self.iterterms(): |
|
coeff_expr = dom.to_sympy(coeff) |
|
monoms_expr = [] |
|
for i, m in enumerate(monom): |
|
monoms_expr.append(symbols[i] ** m) |
|
terms.append(Mul(coeff_expr, *monoms_expr)) |
|
return Add(*terms) |
|
|
|
def __repr__(self) -> str: |
|
|
|
def format_power(base: str, exp: int) -> str: |
|
if exp == 1: |
|
return base |
|
elif exp >= 0 and int(exp) == exp: |
|
return f"{base}**{exp}" |
|
else: |
|
return f"{base}**({exp})" |
|
|
|
ring = self.ring |
|
dom = ring.domain |
|
|
|
syms = [str(s) for s in ring.symbols] |
|
terms_str = [] |
|
for monom, coeff in sorted(self.terms()): |
|
monom_str = "*".join(format_power(s, e) for s, e in zip(syms, monom) if e) |
|
if coeff == dom.one: |
|
if monom_str: |
|
terms_str.append(monom_str) |
|
else: |
|
terms_str.append("1") |
|
elif not monom_str: |
|
terms_str.append(str(coeff)) |
|
else: |
|
terms_str.append(f"{coeff}*{monom_str}") |
|
|
|
return " + ".join(terms_str) |
|
|
|
def _unify( |
|
self, other: PuiseuxPoly |
|
) -> tuple[ |
|
PolyElement, PolyElement, tuple[int, ...] | None, tuple[int, ...] | None |
|
]: |
|
"""Bring two Puiseux polynomials to a common monom and ns.""" |
|
poly1, monom1, ns1 = self.poly, self.monom, self.ns |
|
poly2, monom2, ns2 = other.poly, other.monom, other.ns |
|
|
|
if monom1 == monom2 and ns1 == ns2: |
|
return poly1, poly2, monom1, ns1 |
|
|
|
if ns1 == ns2: |
|
ns = ns1 |
|
elif ns1 is not None and ns2 is not None: |
|
ns = tuple(lcm(n1, n2) for n1, n2 in zip(ns1, ns2)) |
|
f1 = [n // n1 for n, n1 in zip(ns, ns1)] |
|
f2 = [n // n2 for n, n2 in zip(ns, ns2)] |
|
poly1 = poly1.inflate(f1) |
|
poly2 = poly2.inflate(f2) |
|
if monom1 is not None: |
|
monom1 = tuple(m * f for m, f in zip(monom1, f1)) |
|
if monom2 is not None: |
|
monom2 = tuple(m * f for m, f in zip(monom2, f2)) |
|
elif ns2 is not None: |
|
ns = ns2 |
|
poly1 = poly1.inflate(ns) |
|
if monom1 is not None: |
|
monom1 = tuple(m * n for m, n in zip(monom1, ns)) |
|
elif ns1 is not None: |
|
ns = ns1 |
|
poly2 = poly2.inflate(ns) |
|
if monom2 is not None: |
|
monom2 = tuple(m * n for m, n in zip(monom2, ns)) |
|
else: |
|
assert False |
|
|
|
if monom1 == monom2: |
|
monom = monom1 |
|
elif monom1 is not None and monom2 is not None: |
|
monom = tuple(max(m1, m2) for m1, m2 in zip(monom1, monom2)) |
|
poly1 = _mul_poly_monom(poly1, _div_monom(monom, monom1)) |
|
poly2 = _mul_poly_monom(poly2, _div_monom(monom, monom2)) |
|
elif monom2 is not None: |
|
monom = monom2 |
|
poly1 = _mul_poly_monom(poly1, monom2) |
|
elif monom1 is not None: |
|
monom = monom1 |
|
poly2 = _mul_poly_monom(poly2, monom1) |
|
else: |
|
assert False |
|
|
|
return poly1, poly2, monom, ns |
|
|
|
def __pos__(self) -> PuiseuxPoly: |
|
return self |
|
|
|
def __neg__(self) -> PuiseuxPoly: |
|
return self._new_raw(self.ring, -self.poly, self.monom, self.ns) |
|
|
|
def __add__(self, other: Any) -> PuiseuxPoly: |
|
if isinstance(other, PuiseuxPoly): |
|
if self.ring != other.ring: |
|
raise ValueError("Cannot add Puiseux polynomials from different rings") |
|
return self._add(other) |
|
domain = self.ring.domain |
|
if isinstance(other, int): |
|
return self._add_ground(domain.convert_from(QQ(other), QQ)) |
|
elif domain.of_type(other): |
|
return self._add_ground(other) |
|
else: |
|
return NotImplemented |
|
|
|
def __radd__(self, other: Any) -> PuiseuxPoly: |
|
domain = self.ring.domain |
|
if isinstance(other, int): |
|
return self._add_ground(domain.convert_from(QQ(other), QQ)) |
|
elif domain.of_type(other): |
|
return self._add_ground(other) |
|
else: |
|
return NotImplemented |
|
|
|
def __sub__(self, other: Any) -> PuiseuxPoly: |
|
if isinstance(other, PuiseuxPoly): |
|
if self.ring != other.ring: |
|
raise ValueError( |
|
"Cannot subtract Puiseux polynomials from different rings" |
|
) |
|
return self._sub(other) |
|
domain = self.ring.domain |
|
if isinstance(other, int): |
|
return self._sub_ground(domain.convert_from(QQ(other), QQ)) |
|
elif domain.of_type(other): |
|
return self._sub_ground(other) |
|
else: |
|
return NotImplemented |
|
|
|
def __rsub__(self, other: Any) -> PuiseuxPoly: |
|
domain = self.ring.domain |
|
if isinstance(other, int): |
|
return self._rsub_ground(domain.convert_from(QQ(other), QQ)) |
|
elif domain.of_type(other): |
|
return self._rsub_ground(other) |
|
else: |
|
return NotImplemented |
|
|
|
def __mul__(self, other: Any) -> PuiseuxPoly: |
|
if isinstance(other, PuiseuxPoly): |
|
if self.ring != other.ring: |
|
raise ValueError( |
|
"Cannot multiply Puiseux polynomials from different rings" |
|
) |
|
return self._mul(other) |
|
domain = self.ring.domain |
|
if isinstance(other, int): |
|
return self._mul_ground(domain.convert_from(QQ(other), QQ)) |
|
elif domain.of_type(other): |
|
return self._mul_ground(other) |
|
else: |
|
return NotImplemented |
|
|
|
def __rmul__(self, other: Any) -> PuiseuxPoly: |
|
domain = self.ring.domain |
|
if isinstance(other, int): |
|
return self._mul_ground(domain.convert_from(QQ(other), QQ)) |
|
elif domain.of_type(other): |
|
return self._mul_ground(other) |
|
else: |
|
return NotImplemented |
|
|
|
def __pow__(self, other: Any) -> PuiseuxPoly: |
|
if isinstance(other, int): |
|
if other >= 0: |
|
return self._pow_pint(other) |
|
else: |
|
return self._pow_nint(-other) |
|
elif QQ.of_type(other): |
|
return self._pow_rational(other) |
|
else: |
|
return NotImplemented |
|
|
|
def __truediv__(self, other: Any) -> PuiseuxPoly: |
|
if isinstance(other, PuiseuxPoly): |
|
if self.ring != other.ring: |
|
raise ValueError( |
|
"Cannot divide Puiseux polynomials from different rings" |
|
) |
|
return self._mul(other._inv()) |
|
domain = self.ring.domain |
|
if isinstance(other, int): |
|
return self._mul_ground(domain.convert_from(QQ(1, other), QQ)) |
|
elif domain.of_type(other): |
|
return self._div_ground(other) |
|
else: |
|
return NotImplemented |
|
|
|
def __rtruediv__(self, other: Any) -> PuiseuxPoly: |
|
if isinstance(other, int): |
|
return self._inv()._mul_ground(self.ring.domain.convert_from(QQ(other), QQ)) |
|
elif self.ring.domain.of_type(other): |
|
return self._inv()._mul_ground(other) |
|
else: |
|
return NotImplemented |
|
|
|
def _add(self, other: PuiseuxPoly) -> PuiseuxPoly: |
|
poly1, poly2, monom, ns = self._unify(other) |
|
return self._new(self.ring, poly1 + poly2, monom, ns) |
|
|
|
def _add_ground(self, ground: Any) -> PuiseuxPoly: |
|
return self._add(self.ring.ground_new(ground)) |
|
|
|
def _sub(self, other: PuiseuxPoly) -> PuiseuxPoly: |
|
poly1, poly2, monom, ns = self._unify(other) |
|
return self._new(self.ring, poly1 - poly2, monom, ns) |
|
|
|
def _sub_ground(self, ground: Any) -> PuiseuxPoly: |
|
return self._sub(self.ring.ground_new(ground)) |
|
|
|
def _rsub_ground(self, ground: Any) -> PuiseuxPoly: |
|
return self.ring.ground_new(ground)._sub(self) |
|
|
|
def _mul(self, other: PuiseuxPoly) -> PuiseuxPoly: |
|
poly1, poly2, monom, ns = self._unify(other) |
|
if monom is not None: |
|
monom = tuple(2 * e for e in monom) |
|
return self._new(self.ring, poly1 * poly2, monom, ns) |
|
|
|
def _mul_ground(self, ground: Any) -> PuiseuxPoly: |
|
return self._new_raw(self.ring, self.poly * ground, self.monom, self.ns) |
|
|
|
def _div_ground(self, ground: Any) -> PuiseuxPoly: |
|
return self._new_raw(self.ring, self.poly / ground, self.monom, self.ns) |
|
|
|
def _pow_pint(self, n: int) -> PuiseuxPoly: |
|
assert n >= 0 |
|
monom = self.monom |
|
if monom is not None: |
|
monom = tuple(m * n for m in monom) |
|
return self._new(self.ring, self.poly**n, monom, self.ns) |
|
|
|
def _pow_nint(self, n: int) -> PuiseuxPoly: |
|
return self._inv()._pow_pint(n) |
|
|
|
def _pow_rational(self, n: Any) -> PuiseuxPoly: |
|
if not self.is_term: |
|
raise ValueError("Only monomials can be raised to a rational power") |
|
[(monom, coeff)] = self.terms() |
|
domain = self.ring.domain |
|
if not domain.is_one(coeff): |
|
raise ValueError("Only monomials can be raised to a rational power") |
|
monom = tuple(m * n for m in monom) |
|
return self.ring.from_dict({monom: domain.one}) |
|
|
|
def _inv(self) -> PuiseuxPoly: |
|
if not self.is_term: |
|
raise ValueError("Only terms can be inverted") |
|
[(monom, coeff)] = self.terms() |
|
domain = self.ring.domain |
|
if not domain.is_Field and not domain.is_one(coeff): |
|
raise ValueError("Cannot invert non-unit coefficient") |
|
monom = tuple(-m for m in monom) |
|
coeff = 1 / coeff |
|
return self.ring.from_dict({monom: coeff}) |
|
|
|
def diff(self, x: PuiseuxPoly) -> PuiseuxPoly: |
|
"""Differentiate a Puiseux polynomial with respect to a variable. |
|
|
|
>>> from sympy import QQ |
|
>>> from sympy.polys.puiseux import puiseux_ring |
|
>>> R, x, y = puiseux_ring('x, y', QQ) |
|
>>> p = 5*x**2 + 7*y**3 |
|
>>> p.diff(x) |
|
10*x |
|
>>> p.diff(y) |
|
21*y**2 |
|
""" |
|
ring = self.ring |
|
i = ring.index(x) |
|
g = {} |
|
for expv, coeff in self.iterterms(): |
|
n = expv[i] |
|
if n: |
|
e = list(expv) |
|
e[i] -= 1 |
|
g[tuple(e)] = coeff * n |
|
return ring(g) |
|
|