diff --git a/src/sage/arith/misc.py b/src/sage/arith/misc.py index 9fe86a04c6b..6969cf26cc5 100644 --- a/src/sage/arith/misc.py +++ b/src/sage/arith/misc.py @@ -2775,9 +2775,7 @@ def radical(n, *args, **kwds): sage: radical(2 * 3^2 * 5^5) 30 sage: radical(0) - Traceback (most recent call last): - ... - ArithmeticError: radical of 0 is not defined + 0 sage: K. = QuadraticField(-1) # needs sage.rings.number_field sage: radical(K(2)) # needs sage.rings.number_field i - 1 diff --git a/src/sage/categories/unique_factorization_domains.py b/src/sage/categories/unique_factorization_domains.py index a9704872d84..d0af431e855 100644 --- a/src/sage/categories/unique_factorization_domains.py +++ b/src/sage/categories/unique_factorization_domains.py @@ -262,9 +262,7 @@ def radical(self, *args, **kwds): sage: Integer(-100).radical() 10 sage: Integer(0).radical() - Traceback (most recent call last): - ... - ArithmeticError: radical of 0 is not defined + 0 The next example shows how to compute the radical of a number, assuming no prime > 100000 has exponent > 1 in the factorization:: @@ -281,7 +279,7 @@ def radical(self, *args, **kwds): 10 """ if self.is_zero(): - raise ArithmeticError("radical of 0 is not defined") + return self try: decomp = self.squarefree_decomposition() except AttributeError: diff --git a/src/sage/rings/power_series_ring_element.pyx b/src/sage/rings/power_series_ring_element.pyx index fc456150e23..5fd8217bbc2 100644 --- a/src/sage/rings/power_series_ring_element.pyx +++ b/src/sage/rings/power_series_ring_element.pyx @@ -1592,14 +1592,71 @@ cdef class PowerSeries(AlgebraElement): sage: f = (1+t)^100 sage: f.is_square() True + + Over a ring with nilpotent elements, odd valuation is possible for squares:: + + sage: R. = PowerSeriesRing(Zmod(4)) + sage: (2*x).is_square() # (2*x)^2 has odd valuation but 2^2 = 0 mod 4 + Traceback (most recent call last): + ... + NotImplementedError: is_square() not implemented for power series over rings with nonzero nilradical + + Power series over `\Zmod{n}` where `n` is squarefree:: + + sage: R. = PowerSeriesRing(Zmod(6)) + sage: ((x + 1)^2).is_square() + True + sage: (x^2).is_square() + True + sage: R(0).is_square() + True + sage: R(4).is_square() + True + sage: R. = PowerSeriesRing(Zmod(15)) + sage: ((x + 2)^2).is_square() + True + sage: (5 + x).is_square() + False + + For prime power moduli with exponent > 1, a :exc:`NotImplementedError` is raised:: + + sage: R. = PowerSeriesRing(Zmod(8)) + sage: ((x + 1)^2).is_square() + Traceback (most recent call last): + ... + NotImplementedError: is_square() not implemented for power series over rings with nonzero nilradical """ + import sage.rings.abc + + # Zero is always a square + if self.is_zero(): + return True + val = self.valuation() if val is not infinity and val % 2 == 1: + # Odd valuation can only happen for a square if the leading coefficient + # squares to zero, i.e., the base ring has nonzero nilradical. + # Check if the base ring has zero nilradical. + try: + nilrad = self.base_ring().nilradical() + if not nilrad.is_zero(): + raise NotImplementedError( + "is_square() not implemented for power series over rings with nonzero nilradical" + ) + except (AttributeError, NotImplementedError, ArithmeticError): + # If nilradical() is not available, try to check if leading coeff^2 = 0 + if self[val]**2 == 0: + raise NotImplementedError( + "is_square() not implemented for power series over rings with nonzero nilradical" + ) return False elif not self[val].is_square(): return False elif self.base_ring() in _Fields: return True + # Check if base ring is IntegerModRing - use CRT for squarefree moduli + elif isinstance(self.base_ring(), sage.rings.abc.IntegerModRing): + return self._is_square_crt() else: try: self.parent()(self.sqrt()) @@ -1607,6 +1664,82 @@ cdef class PowerSeries(AlgebraElement): except TypeError: return False + def _is_square_crt(self): + r""" + Check if this power series is a square over `\Zmod{n}` using CRT. + + This method works for squarefree moduli (products of distinct primes). + For prime power moduli with exponent > 1, it raises NotImplementedError. + + EXAMPLES:: + + sage: R. = PowerSeriesRing(Zmod(6)) + sage: ((x + 1)^2)._is_square_crt() + True + sage: (5 + x)._is_square_crt() + False + sage: R. = PowerSeriesRing(Zmod(15)) + sage: ((2*x + 3)^2)._is_square_crt() + True + + Elements with even valuation:: + + sage: R. = PowerSeriesRing(Zmod(6)) + sage: (x^2)._is_square_crt() + True + sage: (4*x^2)._is_square_crt() + True + + The zero element is always a square:: + + sage: R. = PowerSeriesRing(Zmod(6)) + sage: R(0)._is_square_crt() + True + + Constant elements:: + + sage: R. = PowerSeriesRing(Zmod(6)) + sage: R(4)._is_square_crt() + True + sage: R(3)._is_square_crt() + True + sage: R(2)._is_square_crt() + False + """ + from sage.rings.finite_rings.integer_mod_ring import IntegerModRing + + # Zero is always a square + if self.is_zero(): + return True + + base = self.base_ring() + n = base.order() + + # Factor n + factorization = n.factor() + + # Check if any prime power has exponent > 1 + for p, e in factorization: + if e > 1: + raise NotImplementedError( + "is_square() not implemented for power series over rings with nonzero nilradical" + ) + + # n is squarefree - use CRT + # For each prime p, reduce mod p and check if square + primes = [p for p, e in factorization] + + for p in primes: + Zp = IntegerModRing(p) + Rp = self.parent().change_ring(Zp) + # Map self to Rp + fp = Rp([Zp(c) for c in self.list()]).add_bigoh(self.prec()) + # Check if square over the prime field + if not fp.is_square(): + return False + + return True + def sqrt(self, prec=None, extend=False, all=False, name=None): r""" Return a square root of ``self``.