Skip to content

Commit

Permalink
Trac #10720: nth_root for (Laurent) power series
Browse files Browse the repository at this point in the history
There is a nth_root method defined on univariate polynomial (via Newton
method)
{{{
sage: R.<x> = QQ[]
sage: ((1 + x - x^2)**5).nth_root(5)
-x^2 + x + 1
}}}
We provide a more general implementation in a new method
`_nth_root_series` that compute the series expansion of the n-th root
for univariate polynomials. Using it we implement straightforward
`nth_root` for univariate (Laurent) power series.

This branch will not consider support for `extend=True` (see this
[[https://groups.google.com/forum/#!topic/sage-devel/ijYyZ4IduF0|sage-
devel thread]]). When `extend=True` the method will simply raise a
`NotImplementedError` while waiting for Puiseux series in Sage (see
#4618).

On multi-variate polynomials there is also a `nth_root` method but which
is implemented via factorization (sic)! The multivariate case should
just call the univariate case with appropriate variable ordering. This
will be dealt with in another ticket.

URL: https://trac.sagemath.org/10720
Reported by: pernici
Ticket author(s): Mario Pernici, Vincent Delecroix
Reviewer(s): Sébastien Labbé
  • Loading branch information
Release Manager authored and vbraun committed Dec 30, 2017
2 parents 4c8f71a + eddd45d commit 9423881
Show file tree
Hide file tree
Showing 3 changed files with 259 additions and 31 deletions.
60 changes: 60 additions & 0 deletions src/sage/rings/laurent_series_ring_element.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,22 @@ AUTHORS:
- Robert Bradshaw: Cython version
"""
#*****************************************************************************
# Copyright (C) 2005 William Stein <wstein@gmail.com>
# 2017 Vincent Delecroix <20100.delecroix@gmail.com>
#
# Distributed under the terms of the GNU General Public License (GPL)
#
# This code is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
# The full text of the GPL is available at:
#
# http://www.gnu.org/licenses/
#*****************************************************************************

from __future__ import print_function, absolute_import

import operator
Expand Down Expand Up @@ -1335,6 +1351,50 @@ cdef class LaurentSeries(AlgebraElement):
return type(self)(self._parent, u, n+1)


def nth_root(self, long n, prec=None):
r"""
Return the ``n``-th root of this Laurent power series.
INPUT:
- ``n`` -- integer
- ``prec`` -- integer (optional) - precision of the result. Though, if
this series has finite precision, then the result can not have larger
precision.
EXAMPLES::
sage: R.<x> = LaurentSeriesRing(QQ)
sage: (x^-2 + 1 + x).nth_root(2)
x^-1 + 1/2*x + 1/2*x^2 - ... - 19437/65536*x^18 + O(x^19)
sage: (x^-2 + 1 + x).nth_root(2)**2
x^-2 + 1 + x + O(x^18)
sage: j = j_invariant_qexp()
sage: q = j.parent().gen()
sage: j(q^3).nth_root(3)
q^-1 + 248*q^2 + 4124*q^5 + ... + O(q^29)
sage: (j(q^2) - 1728).nth_root(2)
q^-1 - 492*q - 22590*q^3 - ... + O(q^19)
"""
if prec is None:
prec = self.prec()
if prec == infinity:
prec = self.parent().default_prec()
else:
prec = min(self.prec(), prec)

if n <= 0:
raise ValueError('n must be positive')

i = self.valuation()
if i % n:
raise ValueError('valuation must be divisible by n')

q = self.__u.nth_root(n, prec)
return type(self)(self._parent, q + self.parent()(0).O(prec), i // n)

def power_series(self):
"""
EXAMPLES::
Expand Down
177 changes: 146 additions & 31 deletions src/sage/rings/polynomial/polynomial_element.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -9592,6 +9592,8 @@ cdef class Polynomial(CommutativeAlgebraElement):
sage: R.<x> = ZZ[]
sage: (x^12).nth_root(6)
x^2
sage: ((3*x)^15).nth_root(5)
27*x^3
sage: parent(R.one().nth_root(3))
Univariate Polynomial Ring in x over Integer Ring
sage: p = (x+1)**20 + x^20
Expand Down Expand Up @@ -9626,31 +9628,143 @@ cdef class Polynomial(CommutativeAlgebraElement):
....: rl = r.leading_coefficient()
....: assert p == r * pl/rl, "R={}\np={}\nr={}".format(R,p,r)
"""
cdef Integer c, cc, e, m
cdef Polynomial p, q, qi, r

R = self.base_ring()
S = self.parent()
if R not in sage.categories.integral_domains.IntegralDomains():
raise ValueError("n-th root of polynomials over rings with zero divisors not implemented")

if n <= 0:
raise ValueError("n (={}) must be positive".format(n))
elif n == 1 or self.is_zero() or self.is_one():
return self
elif self.degree() % n:
raise ValueError("not a %s power"%Integer(n).ordinal_str())
elif self[0].is_zero():
# p = x^k q
# p^(1/n) = x^(k/n) q^(1/n)
i = self.valuation()
if i%n:
raise ValueError("not a %s power"%Integer(n).ordinal_str())
return (self >> i).nth_root(n) << (i // n)

if self[0].is_one():
start = S.one()
else:
start = S(self[0].nth_root(n))

cdef Polynomial p, q
p = self.change_ring(R.fraction_field())
q = p._nth_root_series(n, self.degree() // n + 1, start)

# (possible) TODO: below we check that the result is the
# n-th root. But in ZZ[x] that can be anticipated: we can
# detect that a given polynomial is not a n-th root inside
# the iteration of Newton method by looking at denominators.
if q**n == p:
return S(q)
else:
raise ValueError("not a %s power"%Integer(n).ordinal_str())

def _nth_root_series(self, long n, long prec, start=None):
r"""
Return the first ``prec`` coefficients of the ``n``-th root series of this polynomial.
The method might fail if the exponent ``n`` or the coefficient of
lowest degree is not invertible in the base ring. In both cases an
``ArithmeticError`` is raised.
INPUT:
- ``n`` -- positive integer; the exponent of the root
- ``prec`` -- positive integer; the precision of the result
- ``start`` -- optional; the first term of the result. This
is only considered when the valuation is zero, i.e. when the
polynomial has a nonzero constant term.
.. ALGORITHM::
Let us denote by `a` the polynomial from which we wish to extract
a `n`-th root. The algorithm uses the Newton method for the fixed
point of `F(x) = x^{-n} - a^{-1}`. The advantage of this approach
compared to the more naive `x^n - a` is that it does require only
one polynomial inversion instead of one per iteration of the Newton
method.
EXAMPLES::
sage: R.<x> = QQ[]
sage: (1 + x)._nth_root_series(2, 5)
-5/128*x^4 + 1/16*x^3 - 1/8*x^2 + 1/2*x + 1
sage: R.zero()._nth_root_series(3, 5)
0
sage: R.one()._nth_root_series(3, 5)
1
sage: R.<x> = QQbar[]
sage: p = 2 + 3*x^2
sage: q = p._nth_root_series(3, 20)
sage: (q**3).truncate(20)
3*x^2 + 2
The exponent must be invertible in the base ring::
sage: R.<x> = ZZ[x]
sage: (1 + x)._nth_root_series(2, 5)
Traceback (most recent call last):
...
ArithmeticError: exponent not invertible in base ring
Though, the base ring needs not be a field::
sage: Ru.<u> = QQ[]
sage: Rux.<x> = Ru[]
sage: (4 + u*x)._nth_root_series(2,5)
-5/16384*u^4*x^4 + 1/512*u^3*x^3 - 1/64*u^2*x^2 + 1/4*u*x + 2
sage: ((4 + u*x)._nth_root_series(2,5)**2).truncate(5)
u*x + 4
Finite characteristic::
sage: R.<x> = GF(2)[]
sage: (1 + x)._nth_root_series(3, 10)
x^9 + x^8 + x^3 + x^2 + x + 1
sage: (1 + x^2)._nth_root_series(2, 10)
x + 1
sage: (1 + x)._nth_root_series(2, 10)
Traceback (most recent call last):
...
ValueError: not a 2nd power
TESTS::
sage: QQ['x'].zero()._nth_root_series(3, 5).parent()
Univariate Polynomial Ring in x over Rational Field
sage: QQ['x'].one()._nth_root_series(3, 5).parent()
Univariate Polynomial Ring in x over Rational Field
"""
cdef Integer c, cc, e, m, mp1
cdef Polynomial p, q

R = self.base_ring()
S = self.parent()

m = ZZ.coerce(n)
if m <= 0:
raise ValueError("n (={}) must be positive".format(m))
elif m.is_one() or self.is_zero() or self.is_one():
return self
elif self.degree() % m:
raise ValueError("not a %s power"%m.ordinal_str())
elif self[0].is_zero():
# p = x^k q
# p^(1/n) = x^(k/n) q^(1/n)
# p = x^i q
# p^(1/m) = x^(i/m) q^(1/m)
i = self.valuation()
if i%m:
if i % m:
raise ValueError("not a %s power"%m.ordinal_str())
S = self._parent
return S.gen()**(i//m) * (self>>i).nth_root(m)
return (self >> i)._nth_root_series(m, prec - i // m) << (i // m)
else:
c = R.characteristic()
if c and not n%c:
if c and not n % c:
# characteristic divides n
e = m.valuation(c)
cc = c**e
Expand All @@ -9668,30 +9782,31 @@ cdef class Polynomial(CommutativeAlgebraElement):
p = self

# beginning of Newton method
Sorig = p.parent()
if p[0].is_one():
q = Sorig.one()
# (we can safely assume that the valuation is 0)
S = p.parent()

if start is not None:
a = R(start)
elif p[0].is_one():
a = R.one()
else:
q = Sorig(p[0].nth_root(m))
a = p[0].nth_root(m)

R = R.fraction_field()
p = p.change_ring(R)
q = q.change_ring(R)
try:
q = S(a.inverse_of_unit())
except ArithmeticError:
raise ArithmeticError("constant coefficient not invertible in base ring")

try:
mi = R(m).inverse_of_unit()
except ArithmeticError:
raise ArithmeticError("exponent not invertible in base ring")

from sage.misc.misc import newton_method_sizes
x = p.parent().gen()
for i in newton_method_sizes(p.degree()//m+1):
qi = q._power_trunc(m-1, i).inverse_series_trunc(i)
q = ((m-1) * q + qi._mul_trunc_(p,i)) / m

# NOTE: if we knew that p was a n-th power we could remove the check
# below and just return q after the whole loop
r = q**m - p
if not r:
return Sorig(q)
elif not r.truncate(i).is_zero():
raise ValueError("not a %s power"%m.ordinal_str())
raise ValueError("not a %s power"%m.ordinal_str())
mp1 = m + 1
for i in newton_method_sizes(prec):
q = mi * (mp1 * q - p._mul_trunc_(q._power_trunc(mp1, i), i))
return q.inverse_series_trunc(prec)

def specialization(self, D=None, phi=None):
r"""
Expand Down
53 changes: 53 additions & 0 deletions src/sage/rings/power_series_ring_element.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ With power series the behavior is the same.

#*****************************************************************************
# Copyright (C) 2005 William Stein <wstein@gmail.com>
# 2017 Vincent Delecroix <20100.delecroix@gmail.com>
#
# Distributed under the terms of the GNU General Public License (GPL)
#
Expand Down Expand Up @@ -1406,6 +1407,58 @@ cdef class PowerSeries(AlgebraElement):
except TypeError:
raise ValueError("Square root does not live in this ring.")

def nth_root(self, n, prec=None):
r"""
Return the ``n``-th root of this power series.
INPUT:
- ``n`` -- integer
- ``prec`` -- integer (optional) - precision of the result. Though, if
this series has finite precision, then the result can not have larger
precision.
EXAMPLES::
sage: R.<x> = QQ[[]]
sage: (1+x).nth_root(5)
1 + 1/5*x - 2/25*x^2 + ... + 12039376311816/2384185791015625*x^19 + O(x^20)
sage: (1 + x + O(x^5)).nth_root(5)
1 + 1/5*x - 2/25*x^2 + 6/125*x^3 - 21/625*x^4 + O(x^5)
Check that the results are consistent with taking log and exponential::
sage: R.<x> = PowerSeriesRing(QQ, default_prec=100)
sage: p = (1 + 2*x - x^4)**200
sage: p1 = p.nth_root(1000, prec=100)
sage: p2 = (p.log()/1000).exp()
sage: p1.prec() == p2.prec() == 100
True
sage: p1.polynomial() == p2.polynomial()
True
Positive characteristic::
sage: R.<u> = GF(3)[[]]
sage: p = 1 + 2 * u^2
sage: p.nth_root(4)
1 + 2*u^2 + u^6 + 2*u^8 + u^12 + 2*u^14 + O(u^20)
sage: p.nth_root(4)**4
1 + 2*u^2 + O(u^20)
"""
if prec is None:
prec = self.prec()
if prec == infinity:
prec = self.parent().default_prec()
else:
prec = min(self.prec(), prec)

p = self.polynomial()
q = p._nth_root_series(n, prec)
return self.parent()(q) + self.parent()(0).O(prec)

def cos(self, prec=infinity):
r"""
Apply cos to the formal power series.
Expand Down

0 comments on commit 9423881

Please sign in to comment.