diff --git a/src/sage/data_structures/stream.py b/src/sage/data_structures/stream.py index c2a9b90296d..cbef85d64dd 100644 --- a/src/sage/data_structures/stream.py +++ b/src/sage/data_structures/stream.py @@ -112,8 +112,9 @@ class Stream(): - ``sparse`` -- boolean; whether the implementation of the stream is sparse - ``approximate_order`` -- integer; a lower bound for the order of the stream + - ``true_order`` -- boolean; if the approximate order is the actual order """ - def __init__(self, sparse, approximate_order, true_order=None): + def __init__(self, sparse, approximate_order, true_order=False): """ Initialize ``self``. @@ -376,20 +377,22 @@ def order(self): sage: f.order() 1 """ - if self._true_order is not None: - return self._true_order + if self._true_order: + return self._approximate_order if self._is_sparse: n = self._approximate_order cache = self._cache while True: if n in cache: if cache[n]: - self._approximate_order = self._true_order = n + self._approximate_order = n + self._true_order = True return n n += 1 else: if self[n]: - self._approximate_order = self._true_order = n + self._approximate_order = n + self._true_order = True return n n += 1 else: @@ -398,12 +401,14 @@ def order(self): while True: if n - self._offset < len(cache): if cache[n - self._offset]: - self._approximate_order = self._true_order = n + self._approximate_order = n + self._true_order = True return n n += 1 else: if self[n]: - self._approximate_order = self._true_order = n + self._approximate_order = n + self._true_order = True return n n += 1 @@ -542,28 +547,40 @@ def __init__(self, initial_coefficients, is_sparse, constant=None, degree=None, AssertionError: Stream_exact should only be used for non-zero streams sage: s = Stream_exact([0, 0, 1, 0, 0], False) - sage: s._initial_coefficients, s._true_order, s._degree - ((1,), 2, 3) + sage: s._initial_coefficients, s._approximate_order, s._degree, s._true_order + ((1,), 2, 3, True) sage: s = Stream_exact([0, 0, 1, 0, 0], False, constant=0) - sage: s._initial_coefficients, s._true_order, s._degree - ((1,), 2, 3) + sage: s._initial_coefficients, s._approximate_order, s._degree, s._true_order + ((1,), 2, 3, True) sage: s = Stream_exact([0, 0, 1, 0, 0], False, constant=0, degree=10) - sage: s._initial_coefficients, s._true_order, s._degree - ((1,), 2, 3) + sage: s._initial_coefficients, s._approximate_order, s._degree, s._true_order + ((1,), 2, 3, True) sage: s = Stream_exact([0, 0, 1, 0, 0], False, constant=1) - sage: s._initial_coefficients, s._true_order, s._degree - ((1,), 2, 5) + sage: s._initial_coefficients, s._approximate_order, s._degree, s._true_order + ((1,), 2, 5, True) + + sage: s = Stream_exact([0, 0, 1, 0, 1], False, constant=1, degree=10) + sage: s._initial_coefficients, s._approximate_order, s._degree, s._true_order + ((1, 0, 1), 2, 10, True) + + sage: s = Stream_exact([0, 0, 1, 0, 1], False, constant=1, degree=5) + sage: s._initial_coefficients, s._approximate_order, s._degree, s._true_order + ((1,), 2, 4, True) + + sage: s = Stream_exact([0, 0, 1, 2, 0, 1], False, constant=1) + sage: s._initial_coefficients, s._approximate_order, s._degree, s._true_order + ((1, 2), 2, 5, True) sage: s = Stream_exact([0, 0, 1, 2, 1, 1], False, constant=1) - sage: s._initial_coefficients, s._true_order, s._degree - ((1, 2), 2, 4) + sage: s._initial_coefficients, s._approximate_order, s._degree, s._true_order + ((1, 2), 2, 4, True) sage: s = Stream_exact([0, 0, 1, 2, 1, 1], False, constant=1, order=-2) - sage: s._initial_coefficients, s._true_order, s._degree - ((1, 2), 0, 2) + sage: s._initial_coefficients, s._approximate_order, s._degree, s._true_order + ((1, 2), 0, 2, True) """ if constant is None: self._constant = ZZ.zero() @@ -588,18 +605,20 @@ def __init__(self, initial_coefficients, is_sparse, constant=None, degree=None, # different from constant, because __eq__ below would become # complicated otherwise - # TODO: simplify for i, v in enumerate(initial_coefficients): if v: + # We have found the first nonzero coefficient order += i initial_coefficients = initial_coefficients[i:] if order + len(initial_coefficients) == self._degree: - for j, w in enumerate(reversed(initial_coefficients)): + # Strip off the constant values at the end + for w in reversed(initial_coefficients): if w != self._constant: break initial_coefficients.pop() self._degree -= 1 - for j, w in enumerate(reversed(initial_coefficients)): + # Strip off all remaining zeros at the end + for w in reversed(initial_coefficients): if w: break initial_coefficients.pop() @@ -611,7 +630,7 @@ def __init__(self, initial_coefficients, is_sparse, constant=None, degree=None, assert self._initial_coefficients or self._constant, "Stream_exact should only be used for non-zero streams" - super().__init__(is_sparse, order, true_order=order) + super().__init__(is_sparse, order, true_order=True) def __getitem__(self, n): """ @@ -676,7 +695,7 @@ def order(self): 0 """ - return self._true_order + return self._approximate_order def __hash__(self): """ @@ -780,7 +799,7 @@ def __ne__(self, other): if self[i] != other._cache[i]: return True else: - if other._offset > self._true_order: + if other._offset > self._approximate_order: return False return any(self[i] != c for i, c in enumerate(other._cache, other._offset)) @@ -1267,7 +1286,7 @@ def order(self): sage: s.order() +Infinity """ - return self._true_order + return self._approximate_order # == infinity def __eq__(self, other): """ @@ -1702,7 +1721,7 @@ def __init__(self, f, g): sage: g = Stream_function(lambda n: n^2, True, 1) sage: h = Stream_cauchy_compose(f, g) """ - if g._true_order is not None and g._true_order <= 0: + if g._true_order and g._approximate_order <= 0: raise ValueError("can only compose with a series of positive valuation") if f._approximate_order < 0: ginv = Stream_cauchy_invert(g) @@ -1853,7 +1872,7 @@ def __init__(self, f, g, p, ring=None, include=None, exclude=None): self._degree_f = f._degree else: self._degree_f = None - if g._true_order is not None and g._true_order == 0 and self._degree_f is None: + if g._true_order and g._approximate_order == 0 and self._degree_f is None: raise ValueError("can only compute plethysm with a series of valuation 0 for symmetric functions of finite support") val = f._approximate_order * g._approximate_order @@ -2000,9 +2019,9 @@ def stretched_power_restrict_degree(self, i, m, d): sage: g = Stream_function(lambda n: sum(tensor([p[k], p[n-k]]) for k in range(n+1)), True, 1) sage: h = Stream_plethysm(f, g, p2) sage: A = h.stretched_power_restrict_degree(2, 3, 6) - sage: B = p[2,2,2](sum(g[n] for n in range(7))) - sage: B = p2.element_class(p2, {m: c for m, c in B if sum(mu.size() for mu in m) == 12}) - sage: A == B + sage: B = p[2,2,2](sum(g[n] for n in range(7))) # long time + sage: B = p2.element_class(p2, {m: c for m, c in B if sum(mu.size() for mu in m) == 12}) # long time + sage: A == B # long time True """ while len(self._powers) < m: diff --git a/src/sage/rings/lazy_series.py b/src/sage/rings/lazy_series.py index abb08d7b47d..da3f266ee96 100644 --- a/src/sage/rings/lazy_series.py +++ b/src/sage/rings/lazy_series.py @@ -218,6 +218,7 @@ from sage.combinat.partition import Partition, Partitions from sage.misc.misc_c import prod from sage.misc.derivative import derivative_parse +from sage.categories.integral_domains import IntegralDomains from sage.rings.infinity import infinity from sage.rings.integer_ring import ZZ from sage.rings.polynomial.laurent_polynomial_ring import LaurentPolynomialRing @@ -2882,17 +2883,14 @@ def _div_(self, other): sage: t / L(1) t - sage: t^3*(1+2*t+3*t^2+4*t^3)/(t-t^2) + sage: t^3 * (1+2*t+3*t^2+4*t^3) / (t-t^2) t^2 + 3*t^3 + 6*t^4 + 10*t^5 + 10*t^6 + 10*t^7 + O(t^8) - sage: t^3*((1+2*t+3*t^2+4*t^3)/(t-t^2)) - Traceback (most recent call last): - ... - ZeroDivisionError: cannot divide by a series of larger valuation + sage: t^3 * ((1+2*t+3*t^2+4*t^3) / (t-t^2)) + t^2 + 3*t^3 + 6*t^4 + 10*t^5 + 10*t^6 + 10*t^7 + O(t^8) sage: L(lambda n: n) / (t + t^2) 1 + t + 2*t^2 + 2*t^3 + 3*t^4 + 3*t^5 + O(t^6) - """ if isinstance(other._coeff_stream, Stream_zero): raise ZeroDivisionError("cannot divide by 0") @@ -2903,9 +2901,12 @@ def _div_(self, other): return P.zero() right = other._coeff_stream if (P._minimal_valuation is not None - and left._true_order is not None - and left._true_order < right._approximate_order): - raise ZeroDivisionError("cannot divide by a series of larger valuation") + and left._true_order + and left._approximate_order < right._approximate_order): + F = P.fraction_field() + num = F.element_class(F, left) + den = F.element_class(F, right) + return num / den R = P._internal_poly_ring if (isinstance(left, Stream_exact) @@ -2973,6 +2974,37 @@ def _div_(self, other): return P.element_class(P, Stream_cauchy_mul(left, right_inverse)) + def _floordiv_(self, other): + r""" + Return ``self`` floor divided by ``other``. + + INPUT: + + - ``other`` -- nonzero series + + EXAMPLES:: + + sage: L. = LazyLaurentSeriesRing(QQ) + sage: g = (x + 2*x^2) / (1 - x - x^2) + sage: x // g + 1 - 3*x + 5*x^2 - 10*x^3 + 20*x^4 - 40*x^5 + 80*x^6 + O(x^7) + sage: 1 // g + x^-1 - 3 + 5*x - 10*x^2 + 20*x^3 - 40*x^4 + 80*x^5 + O(x^6) + sage: x^-3 // g + x^-4 - 3*x^-3 + 5*x^-2 - 10*x^-1 + 20 - 40*x + 80*x^2 + O(x^3) + sage: f = (x + x^2) / (1 - x) + sage: f // g + 1 - x + x^2 - 4*x^3 + 6*x^4 - 14*x^5 + 26*x^6 + O(x^7) + sage: g // f + 1 + x + 3*x^3 + x^4 + 6*x^5 + 5*x^6 + O(x^7) + """ + if isinstance(other._coeff_stream, Stream_zero): + raise ZeroDivisionError("cannot divide by 0") + P = self.parent() + if P not in IntegralDomains(): + raise TypeError("must be an integral domain") + return P(self / other) + class LazyLaurentSeries(LazyCauchyProductSeries): r""" A Laurent series where the coefficients are computed lazily. @@ -4689,7 +4721,6 @@ def polynomial(self, degree=None, names=None): sage: f = z-z^2 sage: f.polynomial() -z^2 + z - """ from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing S = self.parent() @@ -4712,6 +4743,57 @@ def polynomial(self, degree=None, names=None): return R(self[0:m]) return R.sum(self[0:m]) + def _floordiv_(self, other): + r""" + Return ``self`` floor divided by ``other``. + + INPUT: + + - ``other`` -- nonzero series + + EXAMPLES:: + + sage: L. = LazyPowerSeriesRing(ZZ) + sage: g = x^2 + y*x + sage: x // g + 0 + sage: g = (x^2 + y*x) / (1 - x + x*y) + sage: x // g + 0 + sage: f = (x + y) / (1 - x - y + x*y) + sage: f // g + 0 + + sage: L. = LazyPowerSeriesRing(QQ) + sage: g = (x + 2*x^2) / (1 - x - x^2) + sage: 3 // g + 0 + sage: x // g + 1 - 3*x + 5*x^2 - 10*x^3 + 20*x^4 - 40*x^5 + 80*x^6 + O(x^7) + sage: x^2 // g + x - 3*x^2 + 5*x^3 - 10*x^4 + 20*x^5 - 40*x^6 + 80*x^7 + O(x^8) + sage: f = (x + x^2) / (1 - x) + sage: f // g + 1 - x + x^2 - 4*x^3 + 6*x^4 - 14*x^5 + 26*x^6 + O(x^7) + """ + if isinstance(other._coeff_stream, Stream_zero): + raise ZeroDivisionError("cannot divide by 0") + P = self.parent() + if P not in IntegralDomains(): + raise TypeError("must be an integral domain") + left = self._coeff_stream + right_order = other._coeff_stream._approximate_order + if left._approximate_order < right_order: + if left._true_order: + return P.zero() + while left._approximate_order < right_order: + # TODO: Implement a bound on computing the order of a Stream + if left[left._approximate_order]: + left._true_order = True + return P.zero() + left._approximate_order += 1 + return super()._floordiv_(other) + class LazyPowerSeries_gcd(LazyPowerSeries): """ A lazy power series that also implements the GCD algorithm. diff --git a/src/sage/rings/lazy_series_ring.py b/src/sage/rings/lazy_series_ring.py index a23b4d89dac..51a021feead 100644 --- a/src/sage/rings/lazy_series_ring.py +++ b/src/sage/rings/lazy_series_ring.py @@ -81,6 +81,24 @@ class LazySeriesRing(UniqueRepresentation, Parent): """ Abstract base class for lazy series. """ + # This will never be called directly (as it is an ABC), but we copy it + # for use in other subclasses. + @staticmethod + def __classcall_private__(cls, base_ring, names, sparse=True, *args, **kwds): + """ + Normalize input to ensure a unique representation. + + EXAMPLES:: + + sage: L. = LazyLaurentSeriesRing(QQ) + sage: Lp = LazyLaurentSeriesRing(QQ, 'z') + sage: L is Lp + True + """ + from sage.structure.category_object import normalize_names + names = normalize_names(-1, names) + return super().__classcall__(cls, base_ring, names, sparse, *args, **kwds) + def _element_constructor_(self, x=None, valuation=None, degree=None, constant=None, coefficients=None): r""" Construct a lazy series from ``x``. @@ -269,6 +287,49 @@ def _element_constructor_(self, x=None, valuation=None, degree=None, constant=No sage: s[1] 0 + Converting various series from a univariate power series:: + + sage: L = LazyLaurentSeriesRing(GF(2), 'z') + sage: R = LazyPowerSeriesRing(ZZ, 'z') + sage: L.has_coerce_map_from(R) + True + sage: L(R(lambda n: n)) + z + z^3 + z^5 + O(z^7) + sage: L(R([2,4,6])) == L.zero() + True + sage: L(R([2,4,6], valuation=2, constant=4)) == L.zero() + True + sage: L(R([2,4,6], valuation=2, constant=5)) + z^5 + z^6 + z^7 + O(z^8) + sage: L(R([2,3,4], valuation=2, constant=4)) + z^3 + sage: L(R([2,3,4], valuation=2, constant=5)) + z^3 + z^5 + z^6 + z^7 + O(z^8) + + Can only convert from known to be constant multivariate power series:: + + sage: L = LazyLaurentSeriesRing(QQ, 'z') + sage: R. = LazyPowerSeriesRing(QQ) + sage: L(R(2)) + 2 + sage: L(R.zero()) + 0 + sage: L(x) + Traceback (most recent call last): + ... + ValueError: unable to convert ... + sage: L(1 / (1 - x - y)) + Traceback (most recent call last): + ... + ValueError: unable to convert ... + sage: P. = QQ[] + sage: f = R(lambda n: (x+y)^n if n == 0 else P.zero()); f + 1 + O(x,y)^7 + sage: L(f) + Traceback (most recent call last): + ... + ValueError: unable to convert ... + TESTS: Checking the valuation is consistent:: @@ -463,6 +524,36 @@ def _element_constructor_(self, x=None, valuation=None, degree=None, constant=No return ret return ret.shift(valuation - x._coeff_stream.order()) + # Handle when it is a power series + if isinstance(x, LazyPowerSeries): + stream = x._coeff_stream + if isinstance(stream, Stream_zero): + return self.zero() + elif isinstance(stream, Stream_exact): + BR = self.base_ring() + if x.parent()._arity != 1: + # Special case for constant series + if stream._degree == 1: + return self(BR(stream[0])) + else: + coeffs = [BR(val) for val in stream._initial_coefficients] + valuation = stream._approximate_order + for i, c in enumerate(coeffs): + if c: + valuation += i + coeffs = coeffs[i:] + break + else: + valuation += len(coeffs) + coeffs = [] + return self(coeffs, + degree=stream._degree, + constant=BR(stream._constant), + valuation=valuation) + elif x.parent()._arity == 1: + return self.element_class(self, stream) + raise ValueError(f"unable to convert {x} into {self}") + else: x = coefficients @@ -677,6 +768,20 @@ def _coerce_map_from_(self, S): True sage: L.has_coerce_map_from(GF(2)) True + sage: R = LazyPowerSeriesRing(ZZ, 'z') + sage: L.has_coerce_map_from(R) + True + + sage: L = LazyLaurentSeriesRing(QQ, 'z') + sage: R = LazyPowerSeriesRing(QQ, 'z') + sage: L.has_coerce_map_from(R) + True + sage: R = LazyPowerSeriesRing(ZZ, 'z') + sage: L.has_coerce_map_from(R) + True + sage: R = LazyPowerSeriesRing(ZZ['t'], 'z') + sage: L.has_coerce_map_from(R) + False sage: L = LazyPowerSeriesRing(GF(2), 'z') sage: L.has_coerce_map_from(ZZ) @@ -695,7 +800,14 @@ def _coerce_map_from_(self, S): return True R = self._laurent_poly_ring - return R.has_coerce_map_from(S) + if R.has_coerce_map_from(S): + return True + + if (isinstance(S, LazySeriesRing) + and self._laurent_poly_ring.has_coerce_map_from(S._laurent_poly_ring)): + return True + + return None def _coerce_map_from_base_ring(self): """ @@ -767,7 +879,8 @@ def _test_invert(self, **options): EXAMPLES:: - sage: Zp(3)._test_invert() + sage: L = LazyLaurentSeriesRing(QQ, 'z') + sage: L._test_invert() .. SEEALSO:: @@ -932,17 +1045,19 @@ class LazyLaurentSeriesRing(LazySeriesRing): sage: L. = LazyLaurentSeriesRing(ZZ, sparse=False) sage: L.is_sparse() False - """ Element = LazyLaurentSeries + # Follow the "generic" normalization + __classcall_private__ = LazySeriesRing.__classcall_private__ + def __init__(self, base_ring, names, sparse=True, category=None): """ Initialize ``self``. TESTS:: - sage: LazyLaurentSeriesRing.options.halting_precision(15) + sage: LazyLaurentSeriesRing.options.halting_precision(12) sage: L = LazyLaurentSeriesRing(ZZ, 't') sage: TestSuite(L).run() @@ -1286,23 +1401,26 @@ class LazyPowerSeriesRing(LazySeriesRing): """ Element = LazyPowerSeries + # Follow the "generic" normalization + __classcall_private__ = LazySeriesRing.__classcall_private__ + def __init__(self, base_ring, names, sparse=True, category=None): """ Initialize ``self``. TESTS:: - sage: LazyPowerSeriesRing.options.halting_precision(15) + sage: LazyPowerSeriesRing.options.halting_precision(12) sage: L = LazyPowerSeriesRing(ZZ, 't') - sage: TestSuite(L).run() + sage: TestSuite(L).run(skip="_test_fraction_field") sage: L = LazyPowerSeriesRing(ZZ, 's, t') - sage: TestSuite(L).run() + sage: TestSuite(L).run(skip="_test_fraction_field") sage: L = LazyPowerSeriesRing(QQ, 't') - sage: TestSuite(L).run() + sage: TestSuite(L).run(skip="_test_fraction_field") sage: L = LazyPowerSeriesRing(QQ, 's, t') - sage: TestSuite(L).run() + sage: TestSuite(L).run(skip="_test_fraction_field") sage: L = LazyPowerSeriesRing(Zmod(6), 't') sage: TestSuite(L).run() @@ -1310,14 +1428,14 @@ def __init__(self, base_ring, names, sparse=True, category=None): sage: TestSuite(L).run() sage: L = LazyPowerSeriesRing(QQ['q'], 't') - sage: TestSuite(L).run() + sage: TestSuite(L).run(skip="_test_fraction_field") sage: L = LazyPowerSeriesRing(QQ['q'], 's, t') - sage: TestSuite(L).run() # long time + sage: TestSuite(L).run(skip="_test_fraction_field") # long time sage: L = LazyPowerSeriesRing(ZZ['q'], 't') - sage: TestSuite(L).run() + sage: TestSuite(L).run(skip="_test_fraction_field") sage: L = LazyPowerSeriesRing(ZZ['q'], 's, t') - sage: TestSuite(L).run() # long time + sage: TestSuite(L).run(skip="_test_fraction_field") # long time sage: LazyPowerSeriesRing.options._reset() # reset the options @@ -1341,8 +1459,6 @@ def __init__(self, base_ring, names, sparse=True, category=None): sage: L in PrincipalIdealDomains False """ - from sage.structure.category_object import normalize_names - names = normalize_names(-1, names) self._sparse = sparse self._minimal_valuation = 0 self._laurent_poly_ring = PolynomialRing(base_ring, names) @@ -1597,6 +1713,7 @@ def _element_constructor_(self, x=None, valuation=None, constant=None, degree=No assert degree is None coeff_stream = Stream_uninitialized(self._sparse, valuation) return self.element_class(self, coeff_stream) + try: # Try to build stuff using the polynomial ring constructor x = R(x) @@ -1608,6 +1725,7 @@ def _element_constructor_(self, x=None, valuation=None, constant=None, degree=No if self._arity > 1 and constant: raise ValueError("constant must be zero for multivariate Taylor series") constant = BR(constant) + if x in R: if not x and not constant: coeff_stream = Stream_zero(self._sparse) @@ -1639,9 +1757,30 @@ def _element_constructor_(self, x=None, valuation=None, constant=None, degree=No if isinstance(x, LazyPowerSeries): if x._coeff_stream._is_sparse is self._sparse: - return self.element_class(self, x._coeff_stream) + stream = x._coeff_stream + if isinstance(stream, Stream_exact): + if self._arity == 1: + BR = self.base_ring() + else: + BR = self._laurent_poly_ring + coeffs = [BR(val) for val in stream._initial_coefficients] + valuation = stream._approximate_order + for i, c in enumerate(coeffs): + if c: + valuation += i + coeffs = coeffs[i:] + break + else: + valuation += len(coeffs) + coeffs = [] + return self(coeffs, + degree=stream._degree, + constant=self.base_ring()(stream._constant), + valuation=valuation) + return self.element_class(self, stream) # TODO: Implement a way to make a self._sparse copy raise NotImplementedError("cannot convert between sparse and dense") + if callable(x) or isinstance(x, (GeneratorType, map, filter)): if valuation is None: valuation = 0 @@ -1732,6 +1871,30 @@ def residue_field(self): raise TypeError("the arity must be one") return R + def fraction_field(self): + """ + Return the fraction field of ``self``. + + If this is with a single variable over a field, then the fraction + field is the field of (lazy) formal Laurent series. + + .. TODO:: + + Implement other fraction fields. + + EXAMPLES:: + + sage: L. = LazyPowerSeriesRing(QQ) + sage: L.fraction_field() + Lazy Laurent Series Ring in x over Rational Field + """ + if self not in IntegralDomains(): + raise TypeError("must be an integral domain") + R = self.base_ring() + if self._arity == 1 and R in Fields(): + return LazyLaurentSeriesRing(R, names=self.variable_names()) + raise NotImplementedError("the fraction field is not yet implemented") + def some_elements(self): """ Return a list of elements of ``self``. @@ -1825,7 +1988,7 @@ def __init__(self, basis, sparse=True, category=None): TESTS:: - sage: LazySymmetricFunctions.options.halting_precision(7) + sage: LazySymmetricFunctions.options.halting_precision(6) sage: s = SymmetricFunctions(ZZ).s() sage: L = LazySymmetricFunctions(s) @@ -1922,7 +2085,7 @@ def _monomial(self, c, n): L = self._laurent_poly_ring return L(c) - def _element_constructor_(self, x=None, valuation=None, degree=None, check=True): + def _element_constructor_(self, x=None, valuation=None, degree=None, constant=None, check=True): r""" Construct a lazy element in ``self`` from ``x``. @@ -2190,7 +2353,7 @@ class LazySymmetricFunctions(LazyCompletionGradedAlgebra): ###################################################################### class LazyDirichletSeriesRing(LazySeriesRing): - """ + r""" The ring of lazy Dirichlet series. INPUT: @@ -2199,20 +2362,70 @@ class LazyDirichletSeriesRing(LazySeriesRing): - ``names`` -- name of the generator of this Dirichlet series ring - ``sparse`` -- (default: ``True``) whether this series is sparse or not + Unlike formal univariate Laurent/power series (over a field), + the ring of formal Dirichlet series is not a + :wikipedia:`discrete_valuation_ring`. On the other hand, it + is a :wikipedia:`local_ring`. The unique maximal ideal + consists of all non-invertible series, i.e., series with + vanishing constant term. + + .. TODO:: + + According to the answers in + https://mathoverflow.net/questions/5522/dirichlet-series-with-integer-coefficients-as-a-ufd, + (which, in particular, references :arxiv:`math/0105219`) + the ring of formal Dirichlet series is actually a + :wikipedia:`Unique_factorization_domain` over `\ZZ`. + + .. NOTE:: + + An interesting valuation is described in Emil Daniel + Schwab; Gheorghe Silberberg *A note on some discrete + valuation rings of arithmetical functions*, Archivum + Mathematicum, Vol. 36 (2000), No. 2, 103-109, + http://dml.cz/dmlcz/107723. Let `J_k` be the ideal of + Dirichlet series whose coefficient `f[n]` of `n^s` + vanishes if `n` has less than `k` prime factors, counting + multiplicities. For any Dirichlet series `f`, let `D(f)` + be the largest integer `k` such that `f` is in `J_k`. + Then `D` is surjective, `D(f g) = D(f) + D(g)` for + nonzero `f` and `g`, and `D(f + g) \geq \min(D(f), D(g))` + provided that `f + g` is nonzero. + + For example, `J_1` are series with no constant term, and + `J_2` are series such that `f[1]` and `f[p]` for prime + `p` vanish. + + Since this is a chain of increasing ideals, the ring of + formal Dirichlet series is not a + :wikipedia:`Noetherian_ring`. + + Evidently, this valuation cannot be computed for a given + series. + EXAMPLES:: sage: LazyDirichletSeriesRing(ZZ, 't') Lazy Dirichlet Series Ring in t over Integer Ring + + The ideal generated by `2^-s` and `3^-s` is not principal:: + + sage: L = LazyDirichletSeriesRing(QQ, 's') + sage: L in PrincipalIdealDomains + False """ Element = LazyDirichletSeries + # Follow the "generic" normalization + __classcall_private__ = LazySeriesRing.__classcall_private__ + def __init__(self, base_ring, names, sparse=True, category=None): """ Initialize the ring. TESTS:: - sage: LazyDirichletSeriesRing.options.halting_precision(15) + sage: LazyDirichletSeriesRing.options.halting_precision(12) sage: L = LazyDirichletSeriesRing(ZZ, 't') sage: TestSuite(L).run() @@ -2221,21 +2434,6 @@ def __init__(self, base_ring, names, sparse=True, category=None): sage: TestSuite(L).run() sage: LazyDirichletSeriesRing.options._reset() # reset the options - - The ideal generated by `2^-s` and `3^-s` is not principal:: - - sage: L = LazyDirichletSeriesRing(QQ, 's') - sage: L in PrincipalIdealDomains - False - - In particular, it is not a :wikipedia:`discrete_valuation_ring`. - - .. TODO:: - - According to the answers in - https://mathoverflow.net/questions/5522/dirichlet-series-with-integer-coefficients-as-a-ufd, - in particular, see :arxiv:`math/0105219`, the ring of formal - Dirichlet series is actually in :class:`UniqueFactorizationDomains`. """ if base_ring.characteristic() > 0: raise ValueError("positive characteristic not allowed for Dirichlet series") @@ -2247,8 +2445,6 @@ def __init__(self, base_ring, names, sparse=True, category=None): self._internal_poly_ring = PolynomialRing(base_ring, names, sparse=True) category = Algebras(base_ring.category()) - #if base_ring in Fields(): - # category &= UniqueFactorizationDomains() if base_ring in IntegralDomains(): category &= IntegralDomains() elif base_ring in Rings().Commutative(): @@ -2498,3 +2694,4 @@ def _skip_leading_zeros(iterator): yield c break yield from iterator +