diff --git a/src/doc/en/reference/index.rst b/src/doc/en/reference/index.rst index 7ecf7af1929..6d323491f01 100644 --- a/src/doc/en/reference/index.rst +++ b/src/doc/en/reference/index.rst @@ -99,11 +99,12 @@ Geometry and Topology * :doc:`Parametrized Surfaces ` * :doc:`Knot Theory ` -Number Fields and Function Fields ---------------------------------- +Number Fields, Function Fields, and Valuations +---------------------------------------------- * :doc:`Number Fields ` * :doc:`Function Fields ` +* :doc:`Discrete Valuations ` Number Theory ------------- diff --git a/src/doc/en/reference/padics/index.rst b/src/doc/en/reference/padics/index.rst index 84c04b77952..ddc9211fb00 100644 --- a/src/doc/en/reference/padics/index.rst +++ b/src/doc/en/reference/padics/index.rst @@ -44,7 +44,6 @@ p-Adics sage/rings/padics/misc sage/rings/padics/common_conversion - sage/rings/padics/discrete_value_group sage/rings/padics/morphism .. include:: ../footer.txt diff --git a/src/doc/en/reference/references/index.rst b/src/doc/en/reference/references/index.rst index 85455672864..655429d4d4b 100644 --- a/src/doc/en/reference/references/index.rst +++ b/src/doc/en/reference/references/index.rst @@ -1053,6 +1053,9 @@ REFERENCES: of Hecke points. Forum Mathematicum, 15:2, pp. 165--189, De Gruyter, 2003. +.. [GMN2008] Jordi Guardia, Jesus Montes, Enric Nart. *Newton polygons of higher + order in algebraic number theory* (2008). arXiv:0807.2620 [math.NT] + .. [GNL2011] \Z. Gong, S. Nikova, and Y. W. Law, *KLEIN: A new family of lightweight block ciphers*; in RFIDSec, (2011), p. 1-18. @@ -1698,6 +1701,14 @@ REFERENCES: Atoms, Journal of Algebraic Combinatorics, Vol. 29, (2009), No. 3, p.295-313. :arXiv:`0707.4267` +.. [Mac1936I] Saunders MacLane, *A construction for prime ideals as absolute + values of an algebraic field*. Duke Mathematical Journal, 2(3) + (1936), 492-510. + +.. [Mac1936II] Saunders MacLane, *A construction for absolute values in + polynomial rings*. Transactions of the American Mathematical + Society, 40(3)(1936), 363-395. + .. [Mac1915] Percy A. MacMahon, *Combinatory Analysis*, Cambridge University Press (1915--1916). (Reprinted: Chelsea, New York, 1960). @@ -2048,6 +2059,10 @@ REFERENCES: .. [Rud1958] \M. E. Rudin. *An unshellable triangulation of a tetrahedron*. Bull. Amer. Math. Soc. 64 (1958), 90-91. +.. [Rüt2014] Julian Rüth, *Models of Curves and Valuations*. Open Access + Repositorium der Universität Ulm. Dissertation (2014). + http://dx.doi.org/10.18725/OPARU-3275 + .. _ref-S: **S** diff --git a/src/doc/en/reference/valuations/conf.py b/src/doc/en/reference/valuations/conf.py new file mode 120000 index 00000000000..2bdf7e68470 --- /dev/null +++ b/src/doc/en/reference/valuations/conf.py @@ -0,0 +1 @@ +../conf_sub.py \ No newline at end of file diff --git a/src/doc/en/reference/valuations/index.rst b/src/doc/en/reference/valuations/index.rst new file mode 100644 index 00000000000..391a7e58162 --- /dev/null +++ b/src/doc/en/reference/valuations/index.rst @@ -0,0 +1,207 @@ +Discrete Valuations and Discrete Pseudo-Valuations +================================================== + +.. linkall + +High-Level Interface +-------------------- +Valuations can be defined conveniently on some Sage rings such as p-adic rings +and function fields. + +p-adic valuations +~~~~~~~~~~~~~~~~~ +Valuations on number fields can be easily specified if they uniquely extend +the valuation of a rational prime:: + + sage: v = QQ.valuation(2) + sage: v(1024) + 10 + +They are normalized such that the rational prime has valuation 1:: + + sage: K. = NumberField(x^2 + x + 1) + sage: v = K.valuation(2) + sage: v(1024) + 10 + +If there are multiple valuations over a prime, they can be obtained by +extending a valuation from a smaller ring:: + + sage: K. = NumberField(x^2 + x + 1) + sage: K.valuation(7) + Traceback (most recent call last): + ... + ValueError: The valuation Gauss valuation induced by 7-adic valuation does not approximate a unique extension of 7-adic valuation with respect to x^2 + x + 1 + sage: w,ww = QQ.valuation(7).extensions(K) + sage: w(a + 3), ww(a + 3) + (1, 0) + sage: w(a + 5), ww(a + 5) + (0, 1) + +Valuations on Function Fields +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Similarly, valuations can be defined on function fields:: + + sage: K. = FunctionField(QQ) + sage: v = K.valuation(x) + sage: v(1/x) + -1 + + sage: v = K.valuation(1/x) + sage: v(1/x) + 1 + +On extensions of function fields, valuations can be created by providing a +prime on the underlying rational function field when the extension is unique:: + + sage: K. = FunctionField(QQ) + sage: R. = K[] + sage: L. = K.extension(y^2 - x) + sage: v = L.valuation(x) + sage: v(x) + 1 + +Valuations can also be extended from smaller function fields:: + + sage: K. = FunctionField(QQ) + sage: v = K.valuation(x - 4) + sage: R. = K[] + sage: L. = K.extension(y^2 - x) + sage: v.extensions(L) + [[ (x - 4)-adic valuation, v(y + 2) = 1 ]-adic valuation, + [ (x - 4)-adic valuation, v(y - 2) = 1 ]-adic valuation] + +Low-Level Interface +------------------- + +Mac Lane valuations +~~~~~~~~~~~~~~~~~~~ +Internally, all the above is backed by the algorithms described in +[Mac1936I]_ and [Mac1936II]_. Let us consider the extensions of +``K.valuation(x - 4)`` to the field `L` above to outline how this works +internally. + +First, the valuation on `K` is induced by a valuation on `\QQ[x]`. To construct +this valuation, we start from the trivial valuation on `\\Q` and consider its +induced Gauss valuation on `\\Q[x]`, i.e., the valuation that assigns to a +polynomial the minimum of the coefficient valuations:: + + sage: R. = QQ[] + sage: v = GaussValuation(R, valuations.TrivialValuation(QQ)) + +The Gauss valuation can be augmented by specifying that `x - 4` has valuation 1:: + + sage: v = v.augmentation(x - 4, 1); v + [ Gauss valuation induced by Trivial valuation on Rational Field, v(x - 4) = 1 ] + +This valuation then extends uniquely to the fraction field:: + + sage: K. = FunctionField(QQ) + sage: v = v.extension(K); v + (x - 4)-adic valuation + +Over the function field we repeat the above process, i.e., we define the Gauss +valuation induced by it and augment it to approximate an extension to `L`:: + + sage: R. = K[] + sage: w = GaussValuation(R, v) + sage: w = w.augmentation(y - 2, 1); w + [ Gauss valuation induced by (x - 4)-adic valuation, v(y - 2) = 1 ] + sage: L. = K.extension(y^2 - x) + sage: ww = w.extension(L); ww + [ (x - 4)-adic valuation, v(y - 2) = 1 ]-adic valuation + +Limit valuations +~~~~~~~~~~~~~~~~ +In the previous example the final valuation ``ww`` is not merely given by +evaluating ``w`` on the ring `K[y]`:: + + sage: ww(y^2 - x) + +Infinity + sage: y = R.gen() + sage: w(y^2 - x) + 1 + +Instead ``ww`` is given by a limit, i.e., an infinite sequence of +augmentations of valuations:: + + sage: ww._base_valuation + [ Gauss valuation induced by (x - 4)-adic valuation, v(y - 2) = 1 , … ] + +The terms of this infinite sequence are computed on demand:: + + sage: ww._base_valuation._approximation + [ Gauss valuation induced by (x - 4)-adic valuation, v(y - 2) = 1 ] + sage: ww(y - 1/4*x - 1) + 2 + sage: ww._base_valuation._approximation + [ Gauss valuation induced by (x - 4)-adic valuation, v(y + 1/64*x^2 - 3/8*x - 3/4) = 3 ] + +Non-classical valuations +~~~~~~~~~~~~~~~~~~~~~~~~ +Using the low-level interface we are not limited to classical valuations on +function fields that correspond to points on the corresponding projective +curves. Instead we can start with a non-trivial valuation on the field of +constants:: + + sage: v = QQ.valuation(2) + sage: R. = QQ[] + sage: w = GaussValuation(R, v) # v is not trivial + sage: K. = FunctionField(QQ) + sage: w = w.extension(K) + sage: w.residue_field() + Fraction Field of Univariate Polynomial Ring in x over Finite Field of size 2 (using ...) + +Mac Lane Approximants +--------------------- +The main tool underlying this package is an algorithm by Mac Lane to compute, +starting from a Gauss valuation on a polynomial ring and a monic squarefree +polynomial G, approximations to the limit valuation which send G to infinity:: + + sage: v = QQ.valuation(2) + sage: R. = QQ[] + sage: f = x^5 + 3*x^4 + 5*x^3 + 8*x^2 + 6*x + 12 + sage: v.mac_lane_approximants(f) # random output (order may vary) + [[ Gauss valuation induced by 2-adic valuation, v(x^2 + x + 1) = 3 ], + [ Gauss valuation induced by 2-adic valuation, v(x) = 1/2 ], + [ Gauss valuation induced by 2-adic valuation, v(x) = 1 ]] + +From these approximants one can already see the residual degrees and +ramification indices of the corresponding extensions. The approximants can be +pushed to arbitrary precision, corresponding to a factorization of ``f``:: + + sage: v.mac_lane_approximants(f, required_precision=10) # random output + [[ Gauss valuation induced by 2-adic valuation, v(x^2 + 193*x + 13/21) = 10 ], + [ Gauss valuation induced by 2-adic valuation, v(x + 86) = 10 ], + [ Gauss valuation induced by 2-adic valuation, v(x) = 1/2, v(x^2 + 36/11*x + 2/17) = 11 ]] + +References +---------- + +The theory was originally described in [Mac1936I]_ and [Mac1936II]_. A summary and some algorithmic details can also be found in Chapter 4 of [Rüt2014]_. + +More Details +============ + +.. toctree:: + :maxdepth: 2 + + sage/rings/valuation/value_group + sage/rings/valuation/valuation + sage/rings/valuation/valuation_space + + sage/rings/valuation/trivial_valuation + sage/rings/valuation/gauss_valuation + + sage/rings/valuation/developing_valuation + sage/rings/valuation/inductive_valuation + sage/rings/valuation/augmented_valuation + sage/rings/valuation/limit_valuation + + sage/rings/valuation/mapped_valuation + sage/rings/valuation/scaled_valuation + + sage/rings/function_field/function_field_valuation + sage/rings/padics/padic_valuation + +.. include:: ../footer.txt diff --git a/src/sage/rings/all.py b/src/sage/rings/all.py index e05b4c6e54c..41294cb3ef6 100644 --- a/src/sage/rings/all.py +++ b/src/sage/rings/all.py @@ -63,6 +63,9 @@ from .padics.all import * from .padics.padic_printing import _printer_defaults as padic_printing +# valuations +from .valuation.all import * + # Semirings from .semirings.all import * diff --git a/src/sage/rings/function_field/function_field.py b/src/sage/rings/function_field/function_field.py index 1a01fc8217f..f407baf909f 100644 --- a/src/sage/rings/function_field/function_field.py +++ b/src/sage/rings/function_field/function_field.py @@ -573,6 +573,123 @@ def rational_function_field(self): """ return self if is_RationalFunctionField(self) else self.base_field().rational_function_field() + def valuation(self, prime): + r""" + Return the discrete valuation on this function field defined by + ``prime``. + + INPUT: + + - ``prime`` -- a place of the function field, a valuation on a subring, + or a valuation on another function field together with information + for isomorphisms to and from that function field + + EXAMPLES: + + We create valuations that correspond to finite rational places of a + function field:: + + sage: K. = FunctionField(QQ) + sage: v = K.valuation(1); v + (x - 1)-adic valuation + sage: v(x) + 0 + sage: v(x - 1) + 1 + + A place can also be specified with an irreducible polynomial:: + + sage: v = K.valuation(x - 1); v + (x - 1)-adic valuation + + Similarly, for a finite non-rational place:: + + sage: v = K.valuation(x^2 + 1); v + (x^2 + 1)-adic valuation + sage: v(x^2 + 1) + 1 + sage: v(x) + 0 + + Or for the infinite place:: + + sage: v = K.valuation(1/x); v + Valuation at the infinite place + sage: v(x) + -1 + + Instead of specifying a generator of a place, we can define a valuation on a + rational function field by giving a discrete valuation on the underlying + polynomial ring:: + + sage: R. = QQ[] + sage: w = valuations.GaussValuation(R, valuations.TrivialValuation(QQ)).augmentation(x - 1, 1) + sage: v = K.valuation(w); v + (x - 1)-adic valuation + + Note that this allows us to specify valuations which do not correspond to a + place of the function field:: + + sage: w = valuations.GaussValuation(R, QQ.valuation(2)) + sage: v = K.valuation(w); v + 2-adic valuation + + The same is possible for valuations with `v(1/x) > 0` by passing in an + extra pair of parameters, an isomorphism between this function field and an + isomorphic function field. That way you can, for example, indicate that the + valuation is to be understood as a valuation on `K[1/x]`, i.e., after + applying the substitution `x \mapsto 1/x` (here, the inverse map is also `x + \mapsto 1/x`):: + + sage: w = valuations.GaussValuation(R, QQ.valuation(2)).augmentation(x, 1) + sage: w = K.valuation(w) + sage: v = K.valuation((w, K.hom([~K.gen()]), K.hom([~K.gen()]))); v + Valuation on rational function field induced by [ Gauss valuation induced by 2-adic valuation, v(x) = 1 ] (in Rational function field in x over Rational Field after x |--> 1/x) + + Note that classical valuations at finite places or the infinite place are + always normalized such that the uniformizing element has valuation 1:: + + sage: K. = FunctionField(GF(3)) + sage: M. = FunctionField(K) + sage: v = M.valuation(x^3 - t) + sage: v(x^3 - t) + 1 + + However, if such a valuation comes out of a base change of the ground + field, this is not the case anymore. In the example below, the unique + extension of ``v`` to ``L`` still has valuation 1 on `x^3 - t` but it has + valuation ``1/3`` on its uniformizing element `x - w`:: + + sage: R. = K[] + sage: L. = K.extension(w^3 - t) + sage: N. = FunctionField(L) + sage: w = v.extension(N) # missing factorization, :trac:`16572` + Traceback (most recent call last): + ... + NotImplementedError + sage: w(x^3 - t) # not tested + 1 + sage: w(x - w) # not tested + 1/3 + + There are several ways to create valuations on extensions of rational + function fields:: + + sage: K. = FunctionField(QQ) + sage: R. = K[] + sage: L. = K.extension(y^2 - x); L + Function field in y defined by y^2 - x + + A place that has a unique extension can just be defined downstairs:: + + sage: v = L.valuation(x); v + (x)-adic valuation + + """ + from sage.rings.function_field.function_field_valuation import FunctionFieldValuation + return FunctionFieldValuation(self, prime) + + class FunctionField_polymod(FunctionField): """ A function field defined by a univariate polynomial, as an diff --git a/src/sage/rings/function_field/function_field_valuation.py b/src/sage/rings/function_field/function_field_valuation.py new file mode 100644 index 00000000000..64d3ca3ac71 --- /dev/null +++ b/src/sage/rings/function_field/function_field_valuation.py @@ -0,0 +1,1251 @@ +# -*- coding: utf-8 -*- +r""" +Discrete valuations on function fields + +AUTHORS: + +- Julian Rüth (2016-10-16): initial version + +EXAMPLES: + +We can create classical valuations that correspond to finite and infinite +places on a rational function field:: + + sage: K. = FunctionField(QQ) + sage: v = K.valuation(1); v + (x - 1)-adic valuation + sage: v = K.valuation(x^2 + 1); v + (x^2 + 1)-adic valuation + sage: v = K.valuation(1/x); v + Valuation at the infinite place + +Note that we can also specify valuations which do not correspond to a place of +the function field:: + + sage: R. = QQ[] + sage: w = valuations.GaussValuation(R, QQ.valuation(2)) + sage: v = K.valuation(w); v + 2-adic valuation + +Valuations on a rational function field can then be extended to finite +extensions:: + + sage: v = K.valuation(x - 1); v + (x - 1)-adic valuation + sage: R. = K[] + sage: L. = K.extension(y^2 - x) + sage: w = v.extensions(L); w + [[ (x - 1)-adic valuation, v(y + 1) = 1 ]-adic valuation, + [ (x - 1)-adic valuation, v(y - 1) = 1 ]-adic valuation] + +TESTS: + +Run test suite for classical places over rational function fields:: + + sage: K. = FunctionField(QQ) + sage: v = K.valuation(1) + sage: TestSuite(v).run(max_runs=100) # long time + + sage: v = K.valuation(x^2 + 1) + sage: TestSuite(v).run(max_runs=100) # long time + + sage: v = K.valuation(1/x) + sage: TestSuite(v).run(max_runs=100) # long time + +Run test suite over classical places of finite extensions:: + + sage: K. = FunctionField(QQ) + sage: v = K.valuation(x - 1) + sage: R. = K[] + sage: L. = K.extension(y^2 - x) + sage: ws = v.extensions(L) + sage: for w in ws: TestSuite(w).run(max_runs=100) # long time + +Run test suite for valuations that do not correspond to a classical place:: + + sage: K. = FunctionField(QQ) + sage: R. = QQ[] + sage: v = GaussValuation(R, QQ.valuation(2)) + sage: w = K.valuation(v) + sage: TestSuite(w).run() # long time + +Run test suite for some other classical places over large ground fields:: + + sage: K. = FunctionField(GF(3)) + sage: M. = FunctionField(K) + sage: v = M.valuation(x^3 - t) + sage: TestSuite(v).run(max_runs=10) # long time + +Run test suite for extensions over the infinite place:: + + sage: K. = FunctionField(QQ) + sage: v = K.valuation(1/x) + sage: R. = K[] + sage: L. = K.extension(y^2 - 1/(x^2 + 1)) + sage: w = v.extensions(L) + sage: TestSuite(w).run() # long time + +Run test suite for a valuation with `v(1/x) > 0` which does not come from a +classical valuation of the infinite place:: + + sage: K. = FunctionField(QQ) + sage: R. = QQ[] + sage: w = GaussValuation(R, QQ.valuation(2)).augmentation(x, 1) + sage: w = K.valuation(w) + sage: v = K.valuation((w, K.hom([~K.gen()]), K.hom([~K.gen()]))) + sage: TestSuite(v).run() # long time + +Run test suite for extensions which come from the splitting in the base field:: + + sage: K. = FunctionField(QQ) + sage: v = K.valuation(x^2 + 1) + sage: L. = FunctionField(GaussianIntegers().fraction_field()) + sage: ws = v.extensions(L) + sage: for w in ws: TestSuite(w).run(max_runs=100) # long time + +Run test suite for a finite place with residual degree and ramification:: + + sage: K. = FunctionField(GF(3)) + sage: L. = FunctionField(K) + sage: v = L.valuation(x^6 - t) + sage: TestSuite(v).run(max_runs=10) # long time + +Run test suite for a valuation which is backed by limit valuation:: + + sage: K. = FunctionField(QQ) + sage: R. = K[] + sage: L. = K.extension(y^2 - (x^2 + x + 1)) + sage: v = K.valuation(x - 1) + sage: w = v.extension(L) + sage: TestSuite(w).run() # long time + +Run test suite for a valuation which sends an element to `-\infty`:: + + sage: R. = QQ[] + sage: v = GaussValuation(QQ['x'], QQ.valuation(2)).augmentation(x, infinity) + sage: K. = FunctionField(QQ) + sage: w = K.valuation(v) + sage: TestSuite(w).run() # long time + +REFERENCES: + +An overview of some computational tools relating to valuations on function +fields can be found in Section 4.6 of [Rüt2014]_. Most of this was originally +developed for number fields in [Mac1936I]_ and [Mac1936II]_. + +""" +#***************************************************************************** +# Copyright (C) 2016-2017 Julian Rüth +# +# Distributed under the terms of the GNU General Public License (GPL) +# as published by the Free Software Foundation; either version 2 of +# the License, or (at your option) any later version. +# http://www.gnu.org/licenses/ +#***************************************************************************** +from sage.structure.factory import UniqueFactory +from sage.rings.all import QQ, ZZ, infinity +from sage.misc.abstract_method import abstract_method + +from sage.rings.valuation.valuation import DiscreteValuation, DiscretePseudoValuation, InfiniteDiscretePseudoValuation, NegativeInfiniteDiscretePseudoValuation +from sage.rings.valuation.trivial_valuation import TrivialValuation +from sage.rings.valuation.mapped_valuation import FiniteExtensionFromLimitValuation, MappedValuation_base + +class FunctionFieldValuationFactory(UniqueFactory): + r""" + Create a valuation on ``domain`` corresponding to ``prime``. + + INPUT: + + - ``domain`` -- a function field + + - ``prime`` -- a place of the function field, a valuation on a subring, or + a valuation on another function field together with information for + isomorphisms to and from that function field + + EXAMPLES:: + + sage: K. = FunctionField(QQ) + sage: v = K.valuation(1); v # indirect doctest + (x - 1)-adic valuation + sage: v(x) + 0 + sage: v(x - 1) + 1 + + See :meth:`sage.rings.function_field.function_field.FunctionField.valuation` for further examples. + + """ + def create_key_and_extra_args(self, domain, prime): + r""" + Create a unique key which identifies the valuation given by ``prime`` + on ``domain``. + + TESTS: + + We specify a valuation on a function field by two different means and + get the same object:: + + sage: K. = FunctionField(QQ) + sage: v = K.valuation(x - 1) # indirect doctest + + sage: R. = QQ[] + sage: w = GaussValuation(R, valuations.TrivialValuation(QQ)).augmentation(x - 1, 1) + sage: K.valuation(w) is v + True + + The normalization is, however, not smart enough, to unwrap + substitutions that turn out to be trivial:: + + sage: w = GaussValuation(R, QQ.valuation(2)) + sage: w = K.valuation(w) + sage: w is K.valuation((w, K.hom([~K.gen()]), K.hom([~K.gen()]))) + False + + """ + from sage.categories.function_fields import FunctionFields + if domain not in FunctionFields(): + raise ValueError("Domain must be a function field.") + + if isinstance(prime, tuple): + if len(prime) == 3: + # prime is a triple of a valuation on another function field with + # isomorphism information + return self.create_key_and_extra_args_from_valuation_on_isomorphic_field(domain, prime[0], prime[1], prime[2]) + + from sage.rings.valuation.valuation_space import DiscretePseudoValuationSpace + if prime.parent() is DiscretePseudoValuationSpace(domain): + # prime is already a valuation of the requested domain + # if we returned (domain, prime), we would break caching + # because this element has been created from a different key + # Instead, we return the key that was used to create prime + # so the caller gets back a correctly cached version of prime + if not hasattr(prime, "_factory_data"): + raise NotImplementedError("Valuations on function fields must be unique and come out of the FunctionFieldValuation factory but %r has been created by other means"%(prime,)) + return prime._factory_data[2], {} + + if prime in domain: + # prime defines a place + return self.create_key_and_extra_args_from_place(domain, prime) + if prime.parent() is DiscretePseudoValuationSpace(domain._ring): + # prime is a discrete (pseudo-)valuation on the polynomial ring + # that the domain is constructed from + return self.create_key_and_extra_args_from_valuation(domain, prime) + if domain.base_field() is not domain: + # prime might define a valuation on a subring of domain and have a + # unique extension to domain + base_valuation = domain.base_field().valuation(prime) + return self.create_key_and_extra_args_from_valuation(domain, base_valuation) + from sage.rings.ideal import is_Ideal + if is_Ideal(prime): + raise NotImplementedError("a place can not be given by an ideal yet") + + raise NotImplementedError("argument must be a place or a pseudo-valuation on a supported subring but %r does not satisfy this for the domain %r"%(prime, domain)) + + def create_key_and_extra_args_from_place(self, domain, generator): + r""" + Create a unique key which identifies the valuation at the place + specified by ``generator``. + + TESTS: + + sage: K. = FunctionField(QQ) + sage: v = K.valuation(1/x) # indirect doctest + + """ + if generator not in domain.base_field(): + raise NotImplementedError("a place must be defined over a rational function field") + + if domain.base_field() is not domain: + # if this is an extension field, construct the unique place over + # the place on the subfield + return self.create_key_and_extra_args(domain, domain.base_field().valuation(generator)) + + if generator in domain.constant_base_field(): + # generator is a constant, we associate to it the place which + # corresponds to the polynomial (x - generator) + return self.create_key_and_extra_args(domain, domain.gen() - generator) + + if generator in domain._ring: + # generator is a polynomial + generator = domain._ring(generator) + if not generator.is_monic(): + raise ValueError("place must be defined by a monic polynomiala but %r is not monic"%(generator,)) + if not generator.is_irreducible(): + raise ValueError("place must be defined by an irreducible polynomial but %r factors over %r"%(generator, domain._ring)) + # we construct the corresponding valuation on the polynomial ring + # with v(generator) = 1 + from sage.rings.valuation.gauss_valuation import GaussValuation + valuation = GaussValuation(domain._ring, TrivialValuation(domain.constant_base_field())).augmentation(generator, 1) + return self.create_key_and_extra_args(domain, valuation) + elif generator == ~domain.gen(): + # generator is 1/x, the infinite place + return (domain, (domain.valuation(domain.gen()), domain.hom(~domain.gen()), domain.hom(~domain.gen()))), {} + else: + raise ValueError("a place must be given by an irreducible polynomial or the inverse of the generator; %r does not define a place over %r"%(generator, domain)) + + def create_key_and_extra_args_from_valuation(self, domain, valuation): + r""" + Create a unique key which identifies the valuation which extends + ``valuation``. + + TESTS: + + sage: K. = FunctionField(QQ) + sage: R. = QQ[] + sage: w = GaussValuation(R, valuations.TrivialValuation(QQ)).augmentation(x - 1, 1) + sage: v = K.valuation(w) # indirect doctest + + """ + # this should have been handled by create_key already + assert valuation.domain() is not domain + + if valuation.domain() is domain._ring: + if domain.base_field() is not domain: + vK = valuation.restriction(valuation.domain().base_ring()) + if vK.domain() is not domain.base_field(): + raise ValueError("valuation must extend a valuation on the base field but %r extends %r whose domain is not %r"%(valuation, vK, domain.base_field())) + # Valuation is an approximant that describes a single valuation + # on domain. + # For uniqueness of valuations (which provides better caching + # and easier pickling) we need to find a normal form of + # valuation, i.e., the smallest approximant that describes this + # valuation + approximants = vK.mac_lane_approximants(domain.polynomial()) + approximant = vK.mac_lane_approximant(domain.polynomial(), valuation, approximants) + return (domain, approximant), {'approximants': approximants} + else: + # on a rational function field K(x), any valuation on K[x] that + # does not have an element with valuation -infty extends to a + # pseudo-valuation on K(x) + if valuation.is_negative_pseudo_valuation(): + raise ValueError("there must not be an element of valuation -Infinity in the domain of valuation"%(valuation,)) + return (domain, valuation), {} + + if valuation.domain().is_subring(domain.base_field()): + # valuation is defined on a subring of this function field, try to lift it + return self.create_key_and_extra_args(domain, valuation.extension(domain)) + + raise NotImplementedError("extension of valuation from %r to %r not implemented yet"%(valuation.domain(), domain)) + + def create_key_and_extra_args_from_valuation_on_isomorphic_field(self, domain, valuation, to_valuation_domain, from_valuation_domain): + r""" + Create a unique key which identifies the valuation which is + ``valuation`` after mapping through ``to_valuation_domain``. + + TESTS:: + + sage: K. = FunctionField(GF(2)) + sage: R. = K[] + sage: L. = K.extension(y^2 + y + x^3) + sage: v = K.valuation(1/x) + sage: w = v.extension(L) # indirect doctest + + """ + from sage.categories.function_fields import FunctionFields + if valuation.domain() not in FunctionFields(): + raise ValueError("valuation must be defined over an isomorphic function field but %r is not a function field"%(valuation.domain(),)) + + from sage.categories.homset import Hom + if to_valuation_domain not in Hom(domain, valuation.domain()): + raise ValueError("to_valuation_domain must map from %r to %r but %r maps from %r to %r"%(domain, valuation.domain(), to_valuation_domain, to_valuation_domain.domain(), to_valuation_domain.codomain())) + if from_valuation_domain not in Hom(valuation.domain(), domain): + raise ValueError("from_valuation_domain must map from %r to %r but %r maps from %r to %r"%(valuation.domain(), domain, from_valuation_domain, from_valuation_domain.domain(), from_valuation_domain.codomain())) + + if domain is domain.base(): + # over rational function fields, we only support the map x |--> 1/x with another rational function field + if valuation.domain() is not valuation.domain().base() or valuation.domain().constant_base_field() != domain.constant_base_field(): + raise NotImplementedError("maps must be isomorphisms with a rational function field over the same base field, not with %r"%(valuation.domain(),)) + if to_valuation_domain != domain.hom([~valuation.domain().gen()]): + raise NotImplementedError("to_valuation_domain must be the map %r not %r"%(domain.hom([~valuation.domain().gen()]), to_valuation_domain)) + if from_valuation_domain != valuation.domain().hom([~domain.gen()]): + raise NotImplementedError("from_valuation_domain must be the map %r not %r"%(valuation.domain().hom([domain.gen()]), from_valuation_domain)) + if domain != valuation.domain(): + # make it harder to create different representations of the same valuation + # (nothing bad happens if we did, but >= and <= are only implemented when this is the case.) + raise NotImplementedError("domain and valuation.domain() must be the same rational function field but %r is not %r"%(domain, valuation.domain())) + else: + if domain.base() is not valuation.domain().base(): + raise NotImplementedError("domain and valuation.domain() must have the same base field but %r is not %r"%(domain.base(), valuation.domain().base())) + if to_valuation_domain != domain.hom([to_valuation_domain(domain.gen())]): + raise NotImplementedError("to_valuation_domain must be trivial on the base fields but %r is not %r"%(to_valuation_domain, domain.hom([to_valuation_domain(domain.gen())]))) + if from_valuation_domain != valuation.domain().hom([from_valuation_domain(valuation.domain().gen())]): + raise NotImplementedError("from_valuation_domain must be trivial on the base fields but %r is not %r"%(from_valuation_domain, valuation.domain().hom([from_valuation_domain(valuation.domain().gen())]))) + if to_valuation_domain(domain.gen()) == valuation.domain().gen(): + raise NotImplementedError("to_valuation_domain seems to be trivial but trivial maps would currently break partial orders of valuations") + + if from_valuation_domain(to_valuation_domain(domain.gen())) != domain.gen(): + # only a necessary condition + raise ValueError("to_valuation_domain and from_valuation_domain are not inverses of each other") + + return (domain, (valuation, to_valuation_domain, from_valuation_domain)), {} + + def create_object(self, version, key, **extra_args): + r""" + Create the valuation specified by ``key``. + + EXAMPLES:: + + sage: K. = FunctionField(QQ) + sage: R. = QQ[] + sage: w = valuations.GaussValuation(R, QQ.valuation(2)) + sage: v = K.valuation(w); v # indirect doctest + 2-adic valuation + + """ + domain, valuation = key + from sage.rings.valuation.valuation_space import DiscretePseudoValuationSpace + parent = DiscretePseudoValuationSpace(domain) + + if isinstance(valuation, tuple) and len(valuation) == 3: + valuation, to_valuation_domain, from_valuation_domain = valuation + if domain is domain.base() and valuation.domain() is valuation.domain().base() and to_valuation_domain == domain.hom([~valuation.domain().gen()]) and from_valuation_domain == valuation.domain().hom([~domain.gen()]): + # valuation on the rational function field after x |--> 1/x + if valuation == valuation.domain().valuation(valuation.domain().gen()): + # the classical valuation at the place 1/x + return parent.__make_element_class__(InfiniteRationalFunctionFieldValuation)(parent) + + from sage.structure.dynamic_class import dynamic_class + clazz = RationalFunctionFieldMappedValuation + if valuation.is_discrete_valuation(): + clazz = dynamic_class("RationalFunctionFieldMappedValuation_discrete", (clazz, DiscreteValuation)) + else: + clazz = dynamic_class("RationalFunctionFieldMappedValuation_infinite", (clazz, InfiniteDiscretePseudoValuation)) + return parent.__make_element_class__(clazz)(parent, valuation, to_valuation_domain, from_valuation_domain) + return parent.__make_element_class__(FunctionFieldExtensionMappedValuation)(parent, valuation, to_valuation_domain, from_valuation_domain) + + if domain is valuation.domain(): + # we can not just return valuation in this case + # as this would break uniqueness and pickling + raise ValueError("valuation must not be a valuation on domain yet but %r is a valuation on %r"%(valuation, domain)) + + if domain.base_field() is domain: + # valuation is a base valuation on K[x] that induces a valuation on K(x) + if valuation.restriction(domain.constant_base_field()).is_trivial() and valuation.is_discrete_valuation(): + # valuation corresponds to a finite place + return parent.__make_element_class__(FiniteRationalFunctionFieldValuation)(parent, valuation) + else: + from sage.structure.dynamic_class import dynamic_class + clazz = NonClassicalRationalFunctionFieldValuation + if valuation.is_discrete_valuation(): + clazz = dynamic_class("NonClassicalRationalFunctionFieldValuation_discrete", (clazz, DiscreteFunctionFieldValuation_base)) + else: + clazz = dynamic_class("NonClassicalRationalFunctionFieldValuation_negative_infinite", (clazz, NegativeInfiniteDiscretePseudoValuation)) + return parent.__make_element_class__(clazz)(parent, valuation) + else: + # valuation is a limit valuation that singles out an extension + return parent.__make_element_class__(FunctionFieldFromLimitValuation)(parent, valuation, domain.polynomial(), extra_args['approximants']) + + raise NotImplementedError("valuation on %r from %r on %r"%(domain, valuation, valuation.domain())) + +FunctionFieldValuation = FunctionFieldValuationFactory("sage.rings.function_field.function_field_valuation.FunctionFieldValuation") + + +class FunctionFieldValuation_base(DiscretePseudoValuation): + r""" + Abstract base class for any discrete (pseudo-)valuation on a function + field. + + TESTS:: + + sage: K. = FunctionField(QQ) + sage: v = K.valuation(x) # indirect doctest + sage: from sage.rings.function_field.function_field_valuation import FunctionFieldValuation_base + sage: isinstance(v, FunctionFieldValuation_base) + True + + """ + + +class DiscreteFunctionFieldValuation_base(DiscreteValuation): + r""" + Base class for discrete valuations on function fields. + + TESTS:: + + sage: K. = FunctionField(QQ) + sage: v = K.valuation(x) # indirect doctest + sage: from sage.rings.function_field.function_field_valuation import DiscreteFunctionFieldValuation_base + sage: isinstance(v, DiscreteFunctionFieldValuation_base) + True + + """ + def extensions(self, L): + r""" + Return the extensions of this valuation to ``L``. + + EXAMPLES:: + + sage: K. = FunctionField(QQ) + sage: v = K.valuation(x) + sage: R. = K[] + sage: L. = K.extension(y^2 - x) + sage: v.extensions(L) + [(x)-adic valuation] + + TESTS: + + Valuations over the infinite place:: + + sage: v = K.valuation(1/x) + sage: R. = K[] + sage: L. = K.extension(y^2 - 1/(x^2 + 1)) + sage: sorted(v.extensions(L), key=str) + [[ Valuation at the infinite place, v(y + 1/x) = 3 ]-adic valuation, + [ Valuation at the infinite place, v(y - 1/x) = 3 ]-adic valuation] + + Iterated extensions over the infinite place:: + + sage: K. = FunctionField(GF(2)) + sage: R. = K[] + sage: L. = K.extension(y^2 + y + x^3) + sage: v = K.valuation(1/x) + sage: w = v.extension(L) + sage: R. = L[] + sage: M. = L.extension(z^2 - y) + sage: w.extension(M) # squarefreeness is not implemented here + Traceback (most recent call last): + ... + NotImplementedError + + A case that caused some trouble at some point:: + + sage: R. = QQ[] + sage: v = GaussValuation(R, QQ.valuation(2)) + + sage: K. = FunctionField(QQ) + sage: v = K.valuation(v) + + sage: R. = K[] + sage: L. = K.extension(y^3 - x^4 - 1) + sage: v.extensions(L) + [2-adic valuation] + + """ + K = self.domain() + from sage.categories.function_fields import FunctionFields + if L is K: + return [self] + if L in FunctionFields(): + if K.is_subring(L): + if L.base() is K: + # L = K[y]/(G) is a simple extension of the domain of this valuation + G = L.polynomial() + if not G.is_monic(): + G = G / G.leading_coefficient() + if any(self(c) < 0 for c in G.coefficients()): + # rewrite L = K[u]/(H) with H integral and compute the extensions + from sage.rings.valuation.gauss_valuation import GaussValuation + g = GaussValuation(G.parent(), self) + y_to_u, u_to_y, H = g.monic_integral_model(G) + M = K.extension(H, names=L.variable_names()) + H_extensions = self.extensions(M) + + from sage.rings.morphism import RingHomomorphism_im_gens + if type(y_to_u) == RingHomomorphism_im_gens and type(u_to_y) == RingHomomorphism_im_gens: + return [L.valuation((w, L.hom([M(y_to_u(y_to_u.domain().gen()))]), M.hom([L(u_to_y(u_to_y.domain().gen()))]))) for w in H_extensions] + raise NotImplementedError + return [L.valuation(w) for w in self.mac_lane_approximants(L.polynomial())] + elif L.base() is not L and K.is_subring(L): + # recursively call this method for the tower of fields + from operator import add + return reduce(add, A, []) + elif L.constant_field() is not K.constant_field() and K.constant_field().is_subring(L): + # subclasses should override this method and handle this case, so we never get here + raise NotImplementedError("Can not compute the extensions of %r from %r to %r since the base ring changes."%(self, self.domain(), L)) + raise NotImplementedError("extension of %r from %r to %r not implemented"%(self, K, L)) + + +class RationalFunctionFieldValuation_base(FunctionFieldValuation_base): + r""" + Base class for valuations on rational function fields. + + TESTS:: + + sage: K. = FunctionField(GF(2)) + sage: v = K.valuation(x) # indirect doctest + sage: from sage.rings.function_field.function_field_valuation import RationalFunctionFieldValuation_base + sage: isinstance(v, RationalFunctionFieldValuation_base) + True + + """ + + +class ClassicalFunctionFieldValuation_base(DiscreteFunctionFieldValuation_base): + r""" + Base class for discrete valuations on rational function fields that come + from points on the projective line. + + TESTS:: + + sage: K. = FunctionField(GF(5)) + sage: v = K.valuation(x) # indirect doctest + sage: from sage.rings.function_field.function_field_valuation import ClassicalFunctionFieldValuation_base + sage: isinstance(v, ClassicalFunctionFieldValuation_base) + True + + """ + def _test_classical_residue_field(self, **options): + r""" + Check correctness of the residue field of a discrete valuation at a + classical point. + + TESTS:: + + sage: K. = FunctionField(QQ) + sage: v = K.valuation(x^2 + 1) + sage: v._test_classical_residue_field() + + """ + tester = self._tester(**options) + + tester.assertTrue(self.domain().constant_field().is_subring(self.residue_field())) + + def _ge_(self, other): + r""" + Return whether ``self`` is greater or equal to ``other`` everywhere. + + EXAMPLES:: + + sage: K. = FunctionField(QQ) + sage: v = K.valuation(x^2 + 1) + sage: w = K.valuation(x) + sage: v >= w + False + sage: w >= v + False + + """ + if other.is_trivial(): + return other.is_discrete_valuation() + if isinstance(other, ClassicalFunctionFieldValuation_base): + return self == other + super(ClassicalFunctionFieldValuation_base, self)._ge_(other) + + +class InducedFunctionFieldValuation_base(FunctionFieldValuation_base): + r""" + Base class for function field valuation induced by a valuation on the + underlying polynomial ring. + + TESTS:: + + sage: K. = FunctionField(QQ) + sage: v = K.valuation(x^2 + 1) # indirect doctest + + """ + def __init__(self, parent, base_valuation): + r""" + TESTS:: + + sage: K. = FunctionField(QQ) + sage: v = K.valuation(x) # indirect doctest + sage: from sage.rings.function_field.function_field_valuation import InducedFunctionFieldValuation_base + sage: isinstance(v, InducedFunctionFieldValuation_base) + True + + """ + FunctionFieldValuation_base.__init__(self, parent) + + domain = parent.domain() + if base_valuation.domain() is not domain._ring: + raise ValueError("base valuation must be defined on %r but %r is defined on %r"%(domain._ring, base_valuation, base_valuation.domain())) + + self._base_valuation = base_valuation + + def uniformizer(self): + r""" + Return a uniformizing element for this valuation. + + EXAMPLES:: + + sage: K. = FunctionField(QQ) + sage: K.valuation(x).uniformizer() + x + + """ + return self.domain()(self._base_valuation.uniformizer()) + + def lift(self, F): + r""" + Return a lift of ``F`` to the domain of this valuation such + that :meth:`reduce` returns the original element. + + EXAMPLES:: + + sage: K. = FunctionField(QQ) + sage: v = K.valuation(x) + sage: v.lift(0) + 0 + sage: v.lift(1) + 1 + + """ + F = self.residue_ring().coerce(F) + if F in self._base_valuation.residue_ring(): + num = self._base_valuation.residue_ring()(F) + den = self._base_valuation.residue_ring()(1) + elif F in self._base_valuation.residue_ring().fraction_field(): + num = self._base_valuation.residue_ring()(F.numerator()) + den = self._base_valuation.residue_ring()(F.denominator()) + else: + raise NotImplementedError("lifting not implemented for this valuation") + + return self.domain()(self._base_valuation.lift(num)) / self.domain()(self._base_valuation.lift(den)) + + def value_group(self): + r""" + Return the value group of this valuation. + + EXAMPLES:: + + sage: K. = FunctionField(QQ) + sage: K.valuation(x).value_group() + Additive Abelian Group generated by 1 + + """ + return self._base_valuation.value_group() + + def reduce(self, f): + r""" + Return the reduction of ``f`` in :meth:`residue_ring`. + + EXAMPLES:: + + sage: K. = FunctionField(QQ) + sage: v = K.valuation(x^2 + 1) + sage: v.reduce(x) + u1 + + """ + f = self.domain().coerce(f) + + if self(f) > 0: + return self.residue_field().zero() + if self(f) < 0: + raise ValueError("can not reduce element of negative valuation") + + base = self._base_valuation + + num = f.numerator() + den = f.denominator() + + assert base(num) == base(den) + shift = base.element_with_valuation(-base(num)) + num *= shift + den *= shift + ret = base.reduce(num) / base.reduce(den) + assert not ret.is_zero() + return self.residue_field()(ret) + + def _repr_(self): + r""" + Return a printable representation of this valuation. + + EXAMPLES:: + + sage: K. = FunctionField(QQ) + sage: K.valuation(x^2 + 1) # indirect doctest + (x^2 + 1)-adic valuation + + """ + from sage.rings.valuation.augmented_valuation import AugmentedValuation_base + from sage.rings.valuation.gauss_valuation import GaussValuation + if isinstance(self._base_valuation, AugmentedValuation_base): + if self._base_valuation._base_valuation == GaussValuation(self.domain()._ring, TrivialValuation(self.domain().constant_field())): + if self._base_valuation._mu == 1: + return "(%r)-adic valuation"%(self._base_valuation.phi()) + vK = self._base_valuation.restriction(self._base_valuation.domain().base_ring()) + if self._base_valuation == GaussValuation(self.domain()._ring, vK): + return repr(vK) + return "Valuation on rational function field induced by %s"%self._base_valuation + + def extensions(self, L): + r""" + Return all extensions of this valuation to ``L`` which has a larger + constant field than the domain of this valuation. + + EXAMPLES:: + + sage: K. = FunctionField(QQ) + sage: v = K.valuation(x^2 + 1) + sage: L. = FunctionField(GaussianIntegers().fraction_field()) + sage: v.extensions(L) # indirect doctest + [(x - I)-adic valuation, (x + I)-adic valuation] + + """ + K = self.domain() + if L is K: + return [self] + + from sage.categories.function_fields import FunctionFields + if L in FunctionFields() \ + and K.is_subring(L) \ + and L.base() is L \ + and L.constant_field() is not K.constant_field() \ + and K.constant_field().is_subring(L.constant_field()): + # The above condition checks whether L is an extension of K that + # comes from an extension of the field of constants + # Condition "L.base() is L" is important so we do not call this + # code for extensions from K(x) to K(x)(y) + + # We extend the underlying valuation on the polynomial ring + W = self._base_valuation.extensions(L._ring) + return [L.valuation(w) for w in W] + + return super(InducedFunctionFieldValuation_base, self).extensions(L) + + def _call_(self, f): + r""" + Evaluate this valuation at the function ``f``. + + EXAMPLES:: + + sage: K. = FunctionField(QQ) + sage: v = K.valuation(x) # indirect doctest + sage: v((x+1)/x^2) + -2 + + """ + return self._base_valuation(f.numerator()) - self._base_valuation(f.denominator()) + + def residue_ring(self): + r""" + Return the residue field of this valuation. + + EXAMPLES:: + + sage: K. = FunctionField(QQ) + sage: K.valuation(x).residue_ring() + Rational Field + + sage: K. = FunctionField(QQ) + sage: v = valuations.GaussValuation(QQ['x'], QQ.valuation(2)) + sage: w = K.valuation(v) + sage: w.residue_ring() + Fraction Field of Univariate Polynomial Ring in x over Finite Field of size 2 (using ...) + + sage: R. = QQ[] + sage: vv = v.augmentation(x, 1) + sage: w = K.valuation(vv) + sage: w.residue_ring() + Fraction Field of Univariate Polynomial Ring in x over Finite Field of size 2 (using ...) + + """ + return self._base_valuation.residue_ring().fraction_field() + + +class FiniteRationalFunctionFieldValuation(InducedFunctionFieldValuation_base, ClassicalFunctionFieldValuation_base, RationalFunctionFieldValuation_base): + r""" + Valuation of a finite place of a function field. + + EXAMPLES:: + + sage: K. = FunctionField(QQ) + sage: v = K.valuation(x + 1); v # indirect doctest + (x + 1)-adic valuation + + A finite place with residual degree:: + + sage: w = K.valuation(x^2 + 1); w + (x^2 + 1)-adic valuation + + A finite place with ramification:: + + sage: K. = FunctionField(GF(3)) + sage: L. = FunctionField(K) + sage: u = L.valuation(x^3 - t); u + (x^3 + 2*t)-adic valuation + + A finite place with residual degree and ramification:: + + sage: q = L.valuation(x^6 - t); q + (x^6 + 2*t)-adic valuation + + """ + def __init__(self, parent, base_valuation): + r""" + TESTS:: + + sage: K. = FunctionField(QQ) + sage: v = K.valuation(x + 1) + sage: from sage.rings.function_field.function_field_valuation import FiniteRationalFunctionFieldValuation + sage: isinstance(v, FiniteRationalFunctionFieldValuation) + True + + """ + InducedFunctionFieldValuation_base.__init__(self, parent, base_valuation) + ClassicalFunctionFieldValuation_base.__init__(self, parent) + RationalFunctionFieldValuation_base.__init__(self, parent) + + +class NonClassicalRationalFunctionFieldValuation(InducedFunctionFieldValuation_base, RationalFunctionFieldValuation_base): + r""" + Valuation induced by a valuation on the underlying polynomial ring which is + non-classical. + + EXAMPLES:: + + sage: K. = FunctionField(QQ) + sage: v = GaussValuation(QQ['x'], QQ.valuation(2)) + sage: w = K.valuation(v); w # indirect doctest + 2-adic valuation + + """ + def __init__(self, parent, base_valuation): + r""" + TESTS: + + There is some support for discrete pseudo-valuations on rational + function fields in the code. However, since these valuations must send + elments to `-\infty`, they are not supported yet:: + + sage: R. = QQ[] + sage: v = GaussValuation(QQ['x'], QQ.valuation(2)).augmentation(x, infinity) + sage: K. = FunctionField(QQ) + sage: w = K.valuation(v) + sage: from sage.rings.function_field.function_field_valuation import NonClassicalRationalFunctionFieldValuation + sage: isinstance(w, NonClassicalRationalFunctionFieldValuation) + True + + """ + InducedFunctionFieldValuation_base.__init__(self, parent, base_valuation) + RationalFunctionFieldValuation_base.__init__(self, parent) + + +class FunctionFieldFromLimitValuation(FiniteExtensionFromLimitValuation, DiscreteFunctionFieldValuation_base): + r""" + A valuation on a finite extensions of function fields `L=K[y]/(G)` where `K` is + another function field. + + EXAMPLES:: + + sage: K. = FunctionField(QQ) + sage: R. = K[] + sage: L. = K.extension(y^2 - (x^2 + x + 1)) + sage: v = K.valuation(x - 1) # indirect doctest + sage: w = v.extension(L); w + (x - 1)-adic valuation + + """ + def __init__(self, parent, approximant, G, approximants): + r""" + TESTS:: + + sage: K. = FunctionField(QQ) + sage: R. = K[] + sage: L. = K.extension(y^2 - (x^2 + x + 1)) + sage: v = K.valuation(x - 1) # indirect doctest + sage: w = v.extension(L) + sage: from sage.rings.function_field.function_field_valuation import FunctionFieldFromLimitValuation + sage: isinstance(w, FunctionFieldFromLimitValuation) + True + + """ + FiniteExtensionFromLimitValuation.__init__(self, parent, approximant, G, approximants) + DiscreteFunctionFieldValuation_base.__init__(self, parent) + + def _to_base_domain(self, f): + r""" + Return ``f`` as an element of the domain of the underlying limit valuation. + + EXAMPLES:: + + sage: K. = FunctionField(QQ) + sage: R. = K[] + sage: L. = K.extension(y^2 - (x^2 + x + 1)) + sage: v = K.valuation(x - 1) # indirect doctest + sage: w = v.extension(L) + sage: w._to_base_domain(y).parent() + Univariate Polynomial Ring in y over Rational function field in x over Rational Field + + """ + return f.element() + + def scale(self, scalar): + r""" + Return this valuation scaled by ``scalar``. + + EXAMPLES:: + + sage: K. = FunctionField(QQ) + sage: R. = K[] + sage: L. = K.extension(y^2 - (x^2 + x + 1)) + sage: v = K.valuation(x - 1) # indirect doctest + sage: w = v.extension(L) + sage: 3*w + 3 * (x - 1)-adic valuation + + """ + if scalar in QQ and scalar > 0 and scalar != 1: + return self.domain().valuation(self._base_valuation._initial_approximation.scale(scalar)) + return super(FunctionFieldFromLimitValuation, self).scale(scalar) + + +class FunctionFieldMappedValuation_base(FunctionFieldValuation_base, MappedValuation_base): + r""" + A valuation on a function field which relies on a ``base_valuation`` on an + isomorphic function field. + + EXAMPLES:: + + sage: K. = FunctionField(GF(2)) + sage: v = K.valuation(1/x); v + Valuation at the infinite place + + """ + def __init__(self, parent, base_valuation, to_base_valuation_domain, from_base_valuation_domain): + r""" + TESTS:: + + sage: K. = FunctionField(GF(2)) + sage: v = K.valuation(1/x) + sage: from sage.rings.function_field.function_field_valuation import FunctionFieldMappedValuation_base + sage: isinstance(v, FunctionFieldMappedValuation_base) + True + + """ + FunctionFieldValuation_base.__init__(self, parent) + MappedValuation_base.__init__(self, parent, base_valuation) + + self._to_base = to_base_valuation_domain + self._from_base = from_base_valuation_domain + + def _to_base_domain(self, f): + r""" + Return ``f`` as an element in the domain of ``_base_valuation``. + + EXAMPLES:: + + sage: K. = FunctionField(GF(2)) + sage: R. = K[] + sage: L. = K.extension(y^2 + y + x^3) + sage: v = K.valuation(1/x) + sage: w = v.extension(L) + sage: w._to_base_domain(y) + x^2*y + + """ + return self._to_base(f) + + def _from_base_domain(self, f): + r""" + Return ``f`` as an element in the domain of this valuation. + + EXAMPLES:: + + sage: K. = FunctionField(GF(2)) + sage: R. = K[] + sage: L. = K.extension(y^2 + y + x^3) + sage: v = K.valuation(1/x) + sage: w = v.extension(L) + sage: w._from_base_domain(w._to_base_domain(y)) + y + + r""" + return self._from_base(f) + + def scale(self, scalar): + r""" + Return this valuation scaled by ``scalar``. + + EXAMPLES:: + + sage: K. = FunctionField(GF(2)) + sage: R. = K[] + sage: L. = K.extension(y^2 + y + x^3) + sage: v = K.valuation(1/x) + sage: w = v.extension(L) + sage: 3*w + 3 * (x)-adic valuation (in Rational function field in x over Finite Field of size 2 after x |--> 1/x) + + """ + from sage.rings.all import QQ + if scalar in QQ and scalar > 0 and scalar != 1: + return self.domain().valuation((self._base_valuation.scale(scalar), self._to_base, self._from_base)) + return super(FunctionFieldMappedValuation_base, self).scale(scalar) + + def _repr_(self): + r""" + Return a printable representation of this valuation. + + EXAMPLES:: + + sage: K. = FunctionField(GF(2)) + sage: R. = K[] + sage: L. = K.extension(y^2 + y + x^3) + sage: v = K.valuation(1/x) + sage: v.extension(L) # indirect doctest + Valuation at the infinite place + + """ + to_base = repr(self._to_base) + if hasattr(self._to_base, '_repr_defn'): + to_base = self._to_base._repr_defn().replace('\n', ', ') + return "%r (in %r after %s)"%(self._base_valuation, self._base_valuation.domain(), to_base) + + def is_discrete_valuation(self): + r""" + Return whether this is a discrete valuation. + + EXAMPLES:: + + sage: K. = FunctionField(QQ) + sage: R. = K[] + sage: L. = K.extension(y^2 - x^4 - 1) + sage: v = K.valuation(1/x) + sage: w0,w1 = v.extensions(L) + sage: w0.is_discrete_valuation() + True + + """ + return self._base_valuation.is_discrete_valuation() + + +class RationalFunctionFieldMappedValuation(FunctionFieldMappedValuation_base, RationalFunctionFieldValuation_base): + r""" + Valuation on a rational function field that is implemented after a map to + an isomorphic (rational) function field. + + EXAMPLES:: + + sage: K. = FunctionField(QQ) + sage: R. = QQ[] + sage: w = GaussValuation(R, QQ.valuation(2)).augmentation(x, 1) + sage: w = K.valuation(w) + sage: v = K.valuation((w, K.hom([~K.gen()]), K.hom([~K.gen()]))); v + Valuation on rational function field induced by [ Gauss valuation induced by 2-adic valuation, v(x) = 1 ] (in Rational function field in x over Rational Field after x |--> 1/x) + + """ + def __init__(self, parent, base_valuation, to_base_valuation_doain, from_base_valuation_domain): + r""" + TESTS:: + + sage: K. = FunctionField(QQ) + sage: R. = QQ[] + sage: w = GaussValuation(R, QQ.valuation(2)).augmentation(x, 1) + sage: w = K.valuation(w) + sage: v = K.valuation((w, K.hom([~K.gen()]), K.hom([~K.gen()]))) + sage: from sage.rings.function_field.function_field_valuation import RationalFunctionFieldMappedValuation + sage: isinstance(v, RationalFunctionFieldMappedValuation) + True + + """ + FunctionFieldMappedValuation_base.__init__(self, parent, base_valuation, to_base_valuation_doain, from_base_valuation_domain) + RationalFunctionFieldValuation_base.__init__(self, parent) + + +class InfiniteRationalFunctionFieldValuation(FunctionFieldMappedValuation_base, RationalFunctionFieldValuation_base, ClassicalFunctionFieldValuation_base): + r""" + Valuation of the infinite place of a function field. + + EXAMPLES:: + + sage: K. = FunctionField(QQ) + sage: v = K.valuation(1/x) # indirect doctest + + """ + def __init__(self, parent): + r""" + TESTS:: + + sage: K. = FunctionField(QQ) + sage: v = K.valuation(1/x) # indirect doctest + sage: from sage.rings.function_field.function_field_valuation import InfiniteRationalFunctionFieldValuation + sage: isinstance(v, InfiniteRationalFunctionFieldValuation) + True + + """ + x = parent.domain().gen() + FunctionFieldMappedValuation_base.__init__(self, parent, FunctionFieldValuation(parent.domain(), x), parent.domain().hom([1/x]), parent.domain().hom([1/x])) + RationalFunctionFieldValuation_base.__init__(self, parent) + ClassicalFunctionFieldValuation_base.__init__(self, parent) + + def _repr_(self): + r""" + Return a printable representation of this valuation. + + EXAMPLES:: + + sage: K. = FunctionField(QQ) + sage: K.valuation(1/x) # indirect doctest + Valuation at the infinite place + + """ + return "Valuation at the infinite place" + + +class FunctionFieldExtensionMappedValuation(FunctionFieldMappedValuation_base): + r""" + A valuation on a finite extensions of function fields `L=K[y]/(G)` where `K` is + another function field which redirects to another ``base_valuation`` on an + isomorphism function field `M=K[y]/(H)`. + + The isomorphisms must be trivial on ``K``. + + EXAMPLES:: + + sage: K. = FunctionField(GF(2)) + sage: R. = K[] + sage: L. = K.extension(y^2 + y + x^3) + sage: v = K.valuation(1/x) + sage: w = v.extension(L) + + sage: w(x) + -1 + sage: w(y) + -3/2 + sage: w.uniformizer() + 1/x^2*y + + TESTS:: + + sage: from sage.rings.function_field.function_field_valuation import FunctionFieldExtensionMappedValuation + sage: isinstance(w, FunctionFieldExtensionMappedValuation) + True + + """ + def _repr_(self): + r""" + Return a printable representation of this valuation. + + EXAMPLES:: + + sage: K. = FunctionField(GF(2)) + sage: R. = K[] + sage: L. = K.extension(y^2 + y + x^3) + sage: v = K.valuation(1/x) + sage: w = v.extension(L); w + Valuation at the infinite place + + sage: K. = FunctionField(QQ) + sage: R. = K[] + sage: L. = K.extension(y^2 - 1/x^2 - 1) + sage: v = K.valuation(1/x) + sage: w = v.extensions(L); w + [[ Valuation at the infinite place, v(y + 1) = 2 ]-adic valuation, + [ Valuation at the infinite place, v(y - 1) = 2 ]-adic valuation] + + """ + assert(self.domain().base() is not self.domain()) + if repr(self._base_valuation) == repr(self.restriction(self.domain().base())): + return repr(self._base_valuation) + return super(FunctionFieldExtensionMappedValuation, self)._repr_() + + def restriction(self, ring): + r""" + Return the restriction of this valuation to ``ring``. + + EXAMPLES:: + + sage: K. = FunctionField(GF(2)) + sage: R. = K[] + sage: L. = K.extension(y^2 + y + x^3) + sage: v = K.valuation(1/x) + sage: w = v.extension(L) + sage: w.restriction(K) is v + True + + """ + if ring.is_subring(self.domain().base()): + return self._base_valuation.restriction(ring) + return super(FunctionFieldMappedValuation, self).restriction(ring) diff --git a/src/sage/rings/integer_ring.pyx b/src/sage/rings/integer_ring.pyx index 01aa776fb59..2db732fc261 100644 --- a/src/sage/rings/integer_ring.pyx +++ b/src/sage/rings/integer_ring.pyx @@ -1482,6 +1482,26 @@ cdef class IntegerRing_class(PrincipalIdealDomain): """ return sib.name('ZZ') + def valuation(self, p): + r""" + Return the discrete valuation with uniformizer ``p``. + + EXAMPLES:: + + sage: v = ZZ.valuation(3); v + 3-adic valuation + sage: v(3) + 1 + + .. SEEALSO:: + + :meth:`Order.valuation() `, + :meth:`RationalField.valuation() ` + + """ + from sage.rings.padics.padic_valuation import pAdicValuation + return pAdicValuation(self, p) + ZZ = IntegerRing_class() Z = ZZ diff --git a/src/sage/rings/number_field/number_field.py b/src/sage/rings/number_field/number_field.py index 21de6b1ae48..07ae9cd7397 100644 --- a/src/sage/rings/number_field/number_field.py +++ b/src/sage/rings/number_field/number_field.py @@ -6835,6 +6835,92 @@ def solve_CRT(self, reslist, Ilist, check=True): raise RuntimeError("Error in number field solve_CRT()") return self(x) + def valuation(self, prime): + r""" + Return the valuation on this field defined by ``prime``. + + INPUT: + + - ``prime`` -- a prime that does not split, a discrete + (pseudo-)valuation or a fractional ideal + + EXAMPLES: + + The valuation can be specified with an integer ``prime`` that is + completely ramified in ``R``:: + + sage: K. = NumberField(x^2 + 1) + sage: K.valuation(2) + 2-adic valuation + + It can also be unramified in ``R``:: + + sage: K.valuation(3) + 3-adic valuation + + A ``prime`` that factors into pairwise distinct factors, results in an error:: + + sage: K.valuation(5) + Traceback (most recent call last): + ... + ValueError: The valuation Gauss valuation induced by 5-adic valuation does not approximate a unique extension of 5-adic valuation with respect to x^2 + 1 + + The valuation can also be selected by giving a valuation on the base + ring that extends uniquely:: + + sage: CyclotomicField(5).valuation(ZZ.valuation(5)) + 5-adic valuation + + When the extension is not unique, this does not work:: + + sage: K.valuation(ZZ.valuation(5)) + Traceback (most recent call last): + ... + ValueError: The valuation Gauss valuation induced by 5-adic valuation does not approximate a unique extension of 5-adic valuation with respect to x^2 + 1 + + For a number field which is of the form `K[x]/(G)`, you can specify a + valuation by providing a discrete pseudo-valuation on `K[x]` which sends + `G` to infinity. This lets us specify which extension of the 5-adic + valuation we care about in the above example:: + + sage: R. = QQ[] + sage: v = K.valuation(GaussValuation(R, QQ.valuation(5)).augmentation(x + 2, infinity)) + sage: w = K.valuation(GaussValuation(R, QQ.valuation(5)).augmentation(x + 1/2, infinity)) + sage: v == w + False + + Note that you get the same valuation, even if you write down the + pseudo-valuation differently:: + + sage: ww = K.valuation(GaussValuation(R, QQ.valuation(5)).augmentation(x + 3, infinity)) + sage: w is ww + True + + The valuation ``prime`` does not need to send the defining polynomial `G` + to infinity. It is sufficient if it singles out one of the valuations on + the number field. This is important if the prime only factors over the + completion, i.e., if it is not possible to write down one of the factors + within the number field:: + + sage: v = GaussValuation(R, QQ.valuation(5)).augmentation(x + 3, 1) + sage: K.valuation(v) + [ 5-adic valuation, v(x + 3) = 1 ]-adic valuation + + Finally, ``prime`` can also be a fractional ideal of a number field if it + singles out an extension of a `p`-adic valuation of the base field:: + + sage: K.valuation(K.fractional_ideal(a + 1)) + 2-adic valuation + + .. SEEALSO:: + + :meth:`Order.valuation() `, + :meth:`pAdicGeneric.valuation() ` + + """ + from sage.rings.padics.padic_valuation import pAdicValuation + return pAdicValuation(self, prime) + def some_elements(self): """ Return a list of elements in the given number field. diff --git a/src/sage/rings/number_field/order.py b/src/sage/rings/number_field/order.py index d717eb7618d..b525a75612e 100644 --- a/src/sage/rings/number_field/order.py +++ b/src/sage/rings/number_field/order.py @@ -1003,6 +1003,67 @@ def absolute_degree(self): """ return self.number_field().absolute_degree() + def valuation(self, p): + r""" + Return the ``p``-adic valuation on this order. + + EXAMPLES: + + The valuation can be specified with an integer ``prime`` that is + completely ramified or unramified:: + + sage: K. = NumberField(x^2 + 1) + sage: O = K.order(2*a) + sage: valuations.pAdicValuation(O, 2) + 2-adic valuation + + sage: GaussianIntegers().valuation(2) + 2-adic valuation + + :: + + sage: GaussianIntegers().valuation(3) + 3-adic valuation + + A ``prime`` that factors into pairwise distinct factors, results in an error:: + + sage: GaussianIntegers().valuation(5) + Traceback (most recent call last): + ... + ValueError: The valuation Gauss valuation induced by 5-adic valuation does not approximate a unique extension of 5-adic valuation with respect to x^2 + 1 + + The valuation can also be selected by giving a valuation on the base + ring that extends uniquely:: + + sage: CyclotomicField(5).ring_of_integers().valuation(ZZ.valuation(5)) + 5-adic valuation + + When the extension is not unique, this does not work:: + + sage: GaussianIntegers().valuation(ZZ.valuation(5)) + Traceback (most recent call last): + ... + ValueError: The valuation Gauss valuation induced by 5-adic valuation does not approximate a unique extension of 5-adic valuation with respect to x^2 + 1 + + If the fraction field is of the form `K[x]/(G)`, you can specify a + valuation by providing a discrete pseudo-valuation on `K[x]` which + sends `G` to infinity:: + + sage: R. = QQ[] + sage: v = GaussianIntegers().valuation(GaussValuation(R, QQ.valuation(5)).augmentation(x + 2, infinity)) + sage: w = GaussianIntegers().valuation(GaussValuation(R, QQ.valuation(5)).augmentation(x + 1/2, infinity)) + sage: v == w + False + + .. SEEALSO:: + + :meth:`NumberField_generic.valuation() `, + :meth:`pAdicGeneric.valuation() ` + + """ + from sage.rings.padics.padic_valuation import pAdicValuation + return pAdicValuation(self, p) + def some_elements(self): """ Return a list of elements of the given order. diff --git a/src/sage/rings/padics/all.py b/src/sage/rings/padics/all.py index 809b4aafe9f..3e361a9a50c 100644 --- a/src/sage/rings/padics/all.py +++ b/src/sage/rings/padics/all.py @@ -6,4 +6,3 @@ from .padic_generic import local_print_mode from .pow_computer import PowComputer from .pow_computer_ext import PowComputer_ext_maker -from .discrete_value_group import DiscreteValueGroup diff --git a/src/sage/rings/padics/discrete_value_group.py b/src/sage/rings/padics/discrete_value_group.py deleted file mode 100644 index 89efaf4757a..00000000000 --- a/src/sage/rings/padics/discrete_value_group.py +++ /dev/null @@ -1,215 +0,0 @@ -r""" -Value groups of discrete valuations - -This file defines additive subgroups of `\QQ` generated by a rational number. - -AUTHORS: - -- Julian Rueth (2013-09-06): initial version - -""" -#***************************************************************************** -# Copyright (C) 2013 Julian Rueth -# -# Distributed under the terms of the GNU General Public License (GPL) -# as published by the Free Software Foundation; either version 2 of -# the License, or (at your option) any later version. -# http://www.gnu.org/licenses/ -#***************************************************************************** -from sage.rings.all import ZZ, QQ, Rational, infinity -from sage.categories.modules import Modules -from sage.structure.parent import Parent -from sage.structure.unique_representation import UniqueRepresentation - -category = Modules(ZZ) - - -class DiscreteValueGroup(UniqueRepresentation, Parent): - r""" - The value group of a discrete valuation, an additive subgroup of `\QQ` - generated by ``generator``. - - INPUT: - - - ``generator`` -- a rational number - - EXAMPLES:: - - sage: D1 = DiscreteValueGroup(0); D1 - DiscreteValueGroup(0) - sage: D2 = DiscreteValueGroup(4/3); D2 - DiscreteValueGroup(4/3) - sage: D3 = DiscreteValueGroup(-1/3); D3 - DiscreteValueGroup(1/3) - - TESTS:: - - sage: TestSuite(D1).run() - sage: TestSuite(D2).run() - sage: TestSuite(D3).run() - - """ - @staticmethod - def __classcall__(cls, generator, category=category): - r""" - Normalizes ``generator`` to a positive rational so that this is a - unique parent. - - TESTS:: - - sage: DiscreteValueGroup(1) is DiscreteValueGroup(-1) - True - - """ - from sage.misc.functional import coerce - generator = coerce(QQ, generator) - generator = generator.abs() - return super(DiscreteValueGroup, cls).__classcall__(cls, generator, category) - - def __init__(self, generator, category): - r""" - Initialization. - - TESTS:: - - sage: type(DiscreteValueGroup(0)) - - - """ - self._generator = generator - - Parent.__init__(self, facade=QQ, category=category) - - def _element_constructor_(self, x): - r""" - Create an element in this group from ``x``. - - INPUT: - - - ``x`` -- a rational number - - TESTS:: - - sage: DiscreteValueGroup(0)(0) - 0 - sage: DiscreteValueGroup(0)(1) - Traceback (most recent call last): - ... - ValueError: `1` is not in DiscreteValueGroup(0). - sage: DiscreteValueGroup(1)(1) - 1 - sage: DiscreteValueGroup(1)(1/2) - Traceback (most recent call last): - ... - ValueError: `1/2` is not in DiscreteValueGroup(1). - - """ - from sage.misc.functional import coerce - x = coerce(QQ, x) - if x == 0 or (self._generator != 0 and x/self._generator in ZZ): - return x - - raise ValueError("`{0}` is not in {1}.".format(x,self)) - - def _repr_(self): - r""" - Return a printable representation for this group. - - EXAMPLES:: - - sage: DiscreteValueGroup(0) # indirect doctest - DiscreteValueGroup(0) - - """ - return "DiscreteValueGroup({0})".format(self._generator) - - def __add__(self, other): - r""" - Return the subgroup of `\QQ` generated by this group and ``other``. - - INPUT: - - - ``other`` -- a discrete value group or a rational number - - EXAMPLES:: - - sage: D = DiscreteValueGroup(1/2) - sage: D + 1/3 - DiscreteValueGroup(1/6) - sage: D + D - DiscreteValueGroup(1/2) - sage: D + 1 - DiscreteValueGroup(1/2) - sage: DiscreteValueGroup(2/7) + DiscreteValueGroup(4/9) - DiscreteValueGroup(2/63) - - """ - if not isinstance(other, DiscreteValueGroup): - from sage.structure.element import is_Element - if is_Element(other) and QQ.has_coerce_map_from(other.parent()): - return self + DiscreteValueGroup(other, category=self.category()) - raise ValueError("`other` must be a DiscreteValueGroup or a rational number") - if self.category() is not other.category(): - raise ValueError("`other` must be in the same category") - return DiscreteValueGroup(self._generator.gcd(other._generator), category=self.category()) - - def _mul_(self, other, switch_sides=False): - r""" - Return the group generated by ``other`` times the generator of this - group. - - INPUT: - - - ``other`` -- a rational number - - EXAMPLES:: - - sage: D = DiscreteValueGroup(1/2) - sage: 1/2 * D - DiscreteValueGroup(1/4) - sage: D * (1/2) - DiscreteValueGroup(1/4) - sage: D * 0 - DiscreteValueGroup(0) - - """ - from sage.misc.functional import coerce - other = coerce(QQ, other) - return DiscreteValueGroup(self._generator*other, category=self.category()) - - def index(self, other): - r""" - Return the index of ``other`` in this group. - - INPUT: - - - ``other`` -- a subgroup of this group - - EXAMPLES:: - - sage: DiscreteValueGroup(3/8).index(DiscreteValueGroup(3)) - 8 - sage: DiscreteValueGroup(3).index(DiscreteValueGroup(3/8)) - Traceback (most recent call last): - ... - ValueError: `other` must be a subgroup of this group - sage: DiscreteValueGroup(3).index(DiscreteValueGroup(0)) - +Infinity - sage: DiscreteValueGroup(0).index(DiscreteValueGroup(0)) - 1 - sage: DiscreteValueGroup(0).index(DiscreteValueGroup(3)) - Traceback (most recent call last): - ... - ValueError: `other` must be a subgroup of this group - - """ - if not isinstance(other, DiscreteValueGroup): - raise ValueError("`other` must be a DiscreteValueGroup") - if other._generator not in self: - raise ValueError("`other` must be a subgroup of this group") - if other._generator == 0: - if self._generator == 0: - return ZZ(1) - else: - return infinity - return ZZ(other._generator / self._generator) diff --git a/src/sage/rings/padics/padic_generic.py b/src/sage/rings/padics/padic_generic.py index d57ad8448c6..bb2b52e8847 100644 --- a/src/sage/rings/padics/padic_generic.py +++ b/src/sage/rings/padics/padic_generic.py @@ -1007,6 +1007,44 @@ def _test_elements_eq_transitive(self, **options): """ pass + def valuation(self): + r""" + Return the `p`-adic valuation on this ring. + + OUTPUT: + + a valuation that is normalized such that the rational prime `p` has + valuation 1. + + EXAMPLES:: + + sage: K = Qp(3) + sage: R. = K[] + sage: L. = K.extension(a^3 - 3) + sage: v = L.valuation(); v + 3-adic valuation + sage: v(3) + 1 + sage: L(3).valuation() + 3 + + The normalization is chosen such that the valuation restricts to the + valuation on the base ring:: + + sage: v(3) == K.valuation()(3) + True + sage: v.restriction(K) == K.valuation() + True + + .. SEEALSO:: + + :meth:`NumberField_generic.valuation() `, + :meth:`Order.valuation() ` + + """ + from sage.rings.padics.padic_valuation import pAdicValuation + return pAdicValuation(self) + class ResidueReductionMap(Morphism): """ Reduction map from a p-adic ring or field to its residue field or ring. diff --git a/src/sage/rings/padics/padic_valuation.py b/src/sage/rings/padics/padic_valuation.py new file mode 100644 index 00000000000..fa89db697f8 --- /dev/null +++ b/src/sage/rings/padics/padic_valuation.py @@ -0,0 +1,1396 @@ +# -*- coding: utf-8 -*- +r""" +`p`-adic valuations on number fields and their subrings and completions + +EXAMPLES:: + + sage: ZZ.valuation(2) + 2-adic valuation + sage: QQ.valuation(3) + 3-adic valuation + sage: CyclotomicField(5).valuation(5) + 5-adic valuation + sage: GaussianIntegers().valuation(7) + 7-adic valuation + sage: Zp(11).valuation() + 11-adic valuation + +These valuations can then, e.g., be used to compute approximate factorizations +in the completion of a ring:: + + sage: v = ZZ.valuation(2) + sage: R. = ZZ[] + sage: f = x^5 + x^4 + x^3 + x^2 + x - 1 + sage: v.montes_factorization(f, required_precision=20) + (x + 676027) * (x^4 + 372550*x^3 + 464863*x^2 + 385052*x + 297869) + +AUTHORS: + +- Julian Rüth (2013-03-16): initial version + +REFERENCES: + +The theory used here was originally developed in [Mac1936I]_ and [Mac1936II]_. An +overview can also be found in Chapter 4 of [Rüt2014]_. + +""" +#***************************************************************************** +# Copyright (C) 2013-2016 Julian Rüth +# +# Distributed under the terms of the GNU General Public License (GPL) +# as published by the Free Software Foundation; either version 2 of +# the License, or (at your option) any later version. +# http://www.gnu.org/licenses/ +#***************************************************************************** +from sage.rings.valuation.valuation import DiscreteValuation +from sage.rings.valuation.value_group import DiscreteValueSemigroup +from sage.rings.valuation.mapped_valuation import FiniteExtensionFromLimitValuation +from sage.structure.factory import UniqueFactory +from sage.misc.cachefunc import cached_method +from sage.misc.fast_methods import WithEqualityById + +from sage.rings.all import infinity + +class PadicValuationFactory(UniqueFactory): + r""" + Create a ``prime``-adic valuation on ``R``. + + INPUT: + + - ``R`` -- a subring of a number field or a subring of a local field in + characteristic zero + + - ``prime`` -- a prime that does not split, a discrete (pseudo-)valuation, + a fractional ideal, or ``None`` (default: ``None``) + + EXAMPLES: + + For integers and rational numbers, ``prime`` is just a prime of the + integers:: + + sage: valuations.pAdicValuation(ZZ, 3) + 3-adic valuation + + sage: valuations.pAdicValuation(QQ, 3) + 3-adic valuation + + ``prime`` may be ``None`` for local rings:: + + sage: valuations.pAdicValuation(Qp(2)) + 2-adic valuation + + sage: valuations.pAdicValuation(Zp(2)) + 2-adic valuation + + But it must be specified in all other cases:: + + sage: valuations.pAdicValuation(ZZ) + Traceback (most recent call last): + ... + ValueError: prime must be specified for this ring + + It can sometimes be beneficial to define a number field extension as a + quotient of a polynomial ring (since number field extensions always compute + an absolute polynomial defining the extension which can be very costly):: + + sage: R. = QQ[] + sage: K. = NumberField(x^2 + 1) + sage: R. = K[] + sage: L. = R.quo(x^2 + a) + sage: valuations.pAdicValuation(L, 2) + 2-adic valuation + + .. SEEALSO:: + + :meth:`NumberField_generic.valuation() `, + :meth:`Order.valuation() `, + :meth:`pAdicGeneric.valuation() `, + :meth:`RationalField.valuation() `, + :meth:`IntegerRing_class.valuation() `. + + """ + def create_key_and_extra_args(self, R, prime=None, approximants=None): + r""" + Create a unique key identifying the valuation of ``R`` with respect to + ``prime``. + + EXAMPLES:: + + sage: QQ.valuation(2) # indirect doctest + 2-adic valuation + + """ + from sage.rings.all import ZZ, QQ + from sage.rings.padics.padic_generic import pAdicGeneric + from sage.rings.number_field.number_field import is_NumberField + from sage.rings.polynomial.polynomial_quotient_ring import is_PolynomialQuotientRing + + if R.characteristic() != 0: + # We do not support equal characteristic yet + raise ValueError("R must be a ring of characteristic zero.") + + if R is ZZ or R is QQ: + return self.create_key_for_integers(R, prime), {} + elif isinstance(R, pAdicGeneric): + return self.create_key_for_local_ring(R, prime), {} + elif is_NumberField(R.fraction_field()) or is_PolynomialQuotientRing(R): + return self.create_key_and_extra_args_for_number_field(R, prime, approximants=approximants) + else: + raise NotImplementedError("p-adic valuations not implemented for %r"%(R,)) + + def create_key_for_integers(self, R, prime): + r""" + Create a unique key identifying the valuation of ``R`` with respect to + ``prime``. + + EXAMPLES:: + + sage: QQ.valuation(2) # indirect doctest + 2-adic valuation + + """ + from sage.rings.all import ZZ + if prime is None: + raise ValueError("prime must be specified for this ring") + from sage.rings.valuation.valuation import DiscretePseudoValuation + if isinstance(prime, DiscretePseudoValuation): + prime = prime.uniformizer() + if prime not in ZZ or not ZZ(prime).is_prime(): + raise ValueError("prime must be a prime in the integers but %s is not"%(prime,)) + return R, prime + + def create_key_for_local_ring(self, R, prime): + r""" + Create a unique key identifying the valuation of ``R`` with respect to + ``prime``. + + EXAMPLES:: + + sage: Qp(2).valuation() # indirect doctest + 2-adic valuation + + """ + # We do not care much about the value of prime since there is only one + # reasonable p-adic valuation here + if prime is not None: + if prime in R: + if R(prime).valuation() <= 0: + raise ValueError("prime must be an element of positive valuation") + elif prime(R.prime()) <= 0: + raise ValueError("prime must be an element of positive valuation") + + return (R,) + + def create_key_and_extra_args_for_number_field(self, R, prime, approximants): + r""" + Create a unique key identifying the valuation of ``R`` with respect to + ``prime``. + + EXAMPLES:: + + sage: GaussianIntegers().valuation(2) # indirect doctest + 2-adic valuation + + """ + K, L, G = self._normalize_number_field_data(R) + + from sage.rings.number_field.number_field_ideal import NumberFieldFractionalIdeal + from sage.rings.valuation.valuation import DiscretePseudoValuation + if isinstance(prime, DiscretePseudoValuation): + return self.create_key_and_extra_args_for_number_field_from_valuation(R, prime, prime, approximants=approximants) + elif prime in K: + return self.create_key_and_extra_args_for_number_field_from_valuation(R, K.valuation(prime), prime, approximants=approximants) + elif prime in L or isinstance(prime, NumberFieldFractionalIdeal): + return self.create_key_and_extra_args_for_number_field_from_ideal(R, L.fractional_ideal(prime), prime) + else: + raise ValueError("prime must be a discrete pseudo-valuation, a prime in the base ring, or a fractional ideal") + + def create_key_and_extra_args_for_number_field_from_valuation(self, R, v, prime, approximants): + r""" + Create a unique key identifying the valuation of ``R`` with respect to + ``v``. + + .. NOTE:: + + ``prime``, the original parameter that was passed to + :meth:`create_key_and_extra_args`, is only used to provide more + meaningful error messages + + EXAMPLES:: + + sage: GaussianIntegers().valuation(ZZ.valuation(2)) # indirect doctest + 2-adic valuation + + TESTS: + + We can extend to the field of fractions of a quotient ring:: + + sage: R. = ZZ[] + sage: S = R.quo(x^2 + 1) + sage: v = valuations.pAdicValuation(S, 2) + sage: R. = QQ[] + sage: S = R.quo(x^2 + 1) + sage: v = valuations.pAdicValuation(S, v) + + """ + K, L, G = self._normalize_number_field_data(R) + + if v.domain().is_subring(G.parent()): + # v is defined on a subring of K[x]. + # We try to lift v to a pseudo-valuation on K[x]. + if _fraction_field(v.domain()) is not _fraction_field(G.parent()): + # First, we lift valuations defined on subrings of K to + # valuations on K[x]. + if v.domain().is_subring(K): + if v.domain() is not K: + v = K.valuation(v) + from sage.rings.valuation.gauss_valuation import GaussValuation + v = GaussValuation(G.parent(), v) + if v.domain() != G.parent(): + # Then, we lift valuations defined on polynomial rings which are + # subrings of K[x] to K[x] + v = v.extension(G.parent()) + elif _fraction_field(v.domain()) == L: + # v is defined on a ring whose field of fractions is L + v = v._base_valuation._initial_approximation.change_domain(G.parent()) + else: + raise NotImplementedError("can not rewrite %r which is defined on %r as a pseudo-valuation on %r"%(v, v.domain(), G.parent())) + + + assert(v.domain() is G.parent()) + + # To obtain uniqueness of p-adic valuations, we need a canonical + # description of v. We consider all extensions of vK to L and select + # the one approximated by v. + vK = v.restriction(v.domain().base_ring()).extension(K) + if approximants is None: + approximants = vK.mac_lane_approximants(G, require_incomparability=True) + approximants = [approximant.extension(v.domain()) for approximant in approximants] + approximant = vK.mac_lane_approximant(G, v, approximants=tuple(approximants)) + + return (R, approximant), {'approximants': approximants} + + def create_key_and_extra_args_for_number_field_from_ideal(self, R, I, prime): + r""" + Create a unique key identifying the valuation of ``R`` with respect to + ``I``. + + .. NOTE:: + + ``prime``, the original parameter that was passed to + :meth:`create_key_and_extra_args`, is only used to provide more + meaningful error messages + + EXAMPLES:: + + sage: GaussianIntegers().valuation(GaussianIntegers().ideal(2)) # indirect doctest + 2-adic valuation + + """ + K, L, G = self._normalize_number_field_data(R) + + # To obtain uniqueness of p-adic valuations, we need a canonical + # description of v. We consider all extensions of vK to L and select + # the one approximated by v. + # Of course, this only works if I comes from a single prime downstairs. + p = I.relative_norm() + F = p.factor() + if len(F) != 1: + raise ValueError("%r does not lie over a single prime of %r"%(I, K)) + vK = K.valuation(F[0][0]) + candidates = vK.mac_lane_approximants(G, require_incomparability=True) + + candidates_for_I = [c for c in candidates if all(c(g.polynomial()) > 0 for g in I.gens())] + assert(len(candidates_for_I) > 0) # This should not be possible, unless I contains a unit + if len(candidates_for_I) > 1: + raise ValueError("%s does not single out a unique extension of %s to %s"%(prime, vK, L)) + else: + return (R, candidates_for_I[0]), {'approximants': candidates} + + def _normalize_number_field_data(self, R): + r""" + Helper method which returns the defining data of the number field + ``R``. + + EXAMPLES:: + + sage: R. = QQ[] + sage: K = R.quo(x^2 + 1) + sage: valuations.pAdicValuation._normalize_number_field_data(K) + (Rational Field, + Univariate Quotient Polynomial Ring in xbar over Rational Field with modulus x^2 + 1, + x^2 + 1) + + """ + from sage.rings.polynomial.polynomial_quotient_ring import is_PolynomialQuotientRing + from sage.rings.number_field.number_field import is_NumberField + from sage.rings.fraction_field import is_FractionField + if is_NumberField(R.fraction_field()): + L = R.fraction_field() + G = L.relative_polynomial() + K = L.base_ring() + elif is_PolynomialQuotientRing(R): + from sage.categories.all import NumberFields + if R.base_ring().fraction_field() not in NumberFields(): + raise NotImplementedError("can not normalize quotients over %r"%(R.base_ring(),)) + L = R.fraction_field() + K = R.base_ring().fraction_field() + G = R.modulus().change_ring(K) + else: + raise NotImplementedError("can not normalize %r"%(R,)) + + return K, L, G + + + def create_object(self, version, key, **extra_args): + r""" + Create a `p`-adic valuation from ``key``. + + EXAMPLES:: + + sage: ZZ.valuation(5) # indirect doctest + 5-adic valuation + + """ + from sage.rings.all import ZZ, QQ + from sage.rings.padics.padic_generic import pAdicGeneric + from sage.rings.valuation.valuation_space import DiscretePseudoValuationSpace + from sage.rings.polynomial.polynomial_quotient_ring import is_PolynomialQuotientRing + from sage.rings.number_field.number_field import is_NumberField + R = key[0] + parent = DiscretePseudoValuationSpace(R) + if isinstance(R, pAdicGeneric): + assert(len(key)==1) + return parent.__make_element_class__(pAdicValuation_padic)(parent) + elif R is ZZ or R is QQ: + prime = key[1] + assert(len(key)==2) + return parent.__make_element_class__(pAdicValuation_int)(parent, prime) + else: + v = key[1] + approximants = extra_args['approximants'] + parent = DiscretePseudoValuationSpace(R) + K = R.fraction_field() + if is_NumberField(K): + G = K.relative_polynomial() + elif is_PolynomialQuotientRing(R): + G = R.modulus() + else: + raise NotImplementedError + return parent.__make_element_class__(pAdicFromLimitValuation)(parent, v, G.change_ring(R.base_ring()), approximants) + +pAdicValuation = PadicValuationFactory("sage.rings.padics.padic_valuation.pAdicValuation") + +class pAdicValuation_base(DiscreteValuation): + r""" + Abstract base class for `p`-adic valuations. + + INPUT: + + - ``ring`` -- an integral domain + + - ``p`` -- a rational prime over which this valuation lies, not + necessarily a uniformizer for the valuation + + EXAMPLES:: + + sage: ZZ.valuation(3) + 3-adic valuation + + sage: QQ.valuation(5) + 5-adic valuation + + For `p`-adic rings, ``p`` has to match the `p` of the ring. + + sage: v = valuations.pAdicValuation(Zp(3), 2); v + Traceback (most recent call last): + ... + ValueError: prime must be an element of positive valuation + + TESTS:: + + sage: TestSuite(ZZ.valuation(3)).run() # long time + sage: TestSuite(QQ.valuation(5)).run() # long time + sage: TestSuite(Zp(5).valuation()).run() # long time + + """ + def __init__(self, parent, p): + r""" + TESTS:: + + sage: from sage.rings.padics.padic_valuation import pAdicValuation_base + sage: isinstance(ZZ.valuation(3), pAdicValuation_base) + True + + """ + DiscreteValuation.__init__(self, parent) + + from sage.rings.all import ZZ + self._p = ZZ(p) + + def p(self): + r""" + Return the `p` of this `p`-adic valuation. + + EXAMPLES:: + + sage: GaussianIntegers().valuation(2).p() + 2 + + """ + return self._p + + def reduce(self, x): + r""" + Reduce ``x`` modulo the ideal of elements of positive valuation. + + INPUT: + + - ``x`` -- an element in the domain of this valuation + + OUTPUT: + + An element of the :meth:`~sage.rings.valuation.valuation_space.DiscretePseudoValuationSpace.ElementMethods.residue_field`. + + EXAMPLES:: + + sage: v = ZZ.valuation(3) + sage: v.reduce(4) + 1 + + """ + x = self.domain().coerce(x) + + if self(x) < 0: + raise ValueError("reduction is only defined for elements of non-negative valuation") + + return self.residue_field()(x) + + def lift(self, x): + r""" + Lift ``x`` from the residue field to the domain of this valuation. + + INPUT: + + - ``x`` -- an element of the :meth:`~sage.rings.valuation.valuation_space.DiscretePseudoValuationSpace.ElementMethods.residue_field` + + EXAMPLES:: + + sage: v = ZZ.valuation(3) + sage: xbar = v.reduce(4) + sage: v.lift(xbar) + 1 + + """ + x = self.residue_field().coerce(x) + + return self.domain()(x) + + def is_unramified(self, G, include_steps=False, assume_squarefree=False): + r""" + Return whether ``G`` defines a single unramified extension of the + completion of the domain of this valuation. + + INPUT: + + - ``G`` -- a monic squarefree polynomial over the domain of this valuation + + - ``include_steps`` -- a boolean (default: ``False``); whether to + include the approximate valuations that were used to determine the + result in the return value. + + - ``assume_squarefree`` -- a boolean (default: ``False``); whether to + assume that ``G`` is square-free over the completion of the domain of + this valuation. Setting this to ``True`` can significantly improve + the performance. + + EXAMPLES: + + We consider an extension as unramified if its ramification index is 1. + Hence, a trivial extension is unramified:: + + sage: R. = QQ[] + sage: v = QQ.valuation(2) + sage: v.is_unramified(x) + True + + If ``G`` remains irreducible in reduction, then it defines an + unramified extension:: + + sage: v.is_unramified(x^2 + x + 1) + True + + However, even if ``G`` factors, it might define an unramified + extension:: + + sage: v.is_unramified(x^2 + 2*x + 4) + True + + """ + R = G.parent() + + from sage.rings.polynomial.polynomial_ring import is_PolynomialRing + if not is_PolynomialRing(R) or R.base_ring() is not self.domain() or not G.is_monic(): + raise ValueError("G must be a monic univariate polynomial over the domain of this valuation") + if not assume_squarefree and not G.is_squarefree(): + raise ValueError("G must be squarefree") + + from sage.rings.valuation.gauss_valuation import GaussValuation + + steps = [ GaussValuation(R, self) ] + while True: + v = steps[-1] + if v.E() > 1: + ret = False + break + if v.F() == G.degree(): + ret = True + break + + assert v(G) is not infinity + if v.is_key(G): + ret = True + break + + next = v.mac_lane_step(G, assume_squarefree=True) + if len(next)>1: + ret = False + break + steps.append(next[0]) + + if include_steps: + return ret, steps + else: + return ret + + def is_totally_ramified(self, G, include_steps=False, assume_squarefree=False): + r""" + Return whether ``G`` defines a single totally ramified extension of the + completion of the domain of this valuation. + + INPUT: + + - ``G`` -- a monic squarefree polynomial over the domain of this valuation + + - ``include_steps`` -- a boolean (default: ``False``); where to include + the valuations produced during the process of checking whether ``G`` + is totally ramified in the return value + + - ``assume_squarefree`` -- a boolean (default: ``False``); whether to + assume that ``G`` is square-free over the completion of the domain of + this valuation. Setting this to ``True`` can significantly improve + the performance. + + ALGORITHM: + + This is a simplified version of :meth:`sage.rings.valuation.valuation.DiscreteValuation.mac_lane_approximants`. + + EXAMPLES:: + + sage: k = Qp(5,4) + sage: v = k.valuation() + sage: R. = k[] + sage: G = x^2 + 1 + sage: v.is_totally_ramified(G) + False + sage: G = x + 1 + sage: v.is_totally_ramified(G) + True + sage: G = x^2 + 2 + sage: v.is_totally_ramified(G) + False + sage: G = x^2 + 5 + sage: v.is_totally_ramified(G) + True + sage: v.is_totally_ramified(G, include_steps=True) + (True, [Gauss valuation induced by 5-adic valuation, [ Gauss valuation induced by 5-adic valuation, v((1 + O(5^4))*x) = 1/2 ]]) + + We consider an extension as totally ramified if its ramification index + matches the degree. Hence, a trivial extension is totally ramified:: + + sage: R. = QQ[] + sage: v = QQ.valuation(2) + sage: v.is_totally_ramified(x) + True + + TESTS: + + An example that Sebastian Pauli used at Sage Days 87:: + + sage: R = ZpFM(3, 20) + sage: S. = R[] + sage: f = x^9 + 9*x^2 + 3 + sage: R.valuation().is_totally_ramified(f) + True + + """ + R = G.parent() + + from sage.rings.polynomial.polynomial_ring import is_PolynomialRing + if not is_PolynomialRing(R) or R.base_ring() is not self.domain() or not G.is_monic(): + raise ValueError("G must be a monic univariate polynomial over the domain of this valuation") + if not assume_squarefree and not G.is_squarefree(): + raise ValueError("G must be squarefree") + + from sage.rings.valuation.gauss_valuation import GaussValuation + + steps = [ GaussValuation(R, self) ] + while True: + v = steps[-1] + if v.F() > 1: + ret = False + break + if v.E() == G.degree(): + ret = True + break + + assert v(G) is not infinity + if v.is_key(G): + ret = False + break + + next = v.mac_lane_step(G, assume_squarefree=True) + if len(next)>1: + ret = False + break + steps.append(next[0]) + + if include_steps: + return ret, steps + else: + return ret + + def change_domain(self, ring): + r""" + Change the domain of this valuation to ``ring`` if possible. + + EXAMPLES:: + + sage: v = ZZ.valuation(2) + sage: v.change_domain(QQ).domain() + Rational Field + + """ + return pAdicValuation(ring, self.p()) + + def _extensions_to_quotient(self, ring, approximants=None): + r""" + Return the extensions of this valuation to an integral quotient over + the domain of this valuation. + + EXAMPLES:: + + sage: R. = QQ[] + sage: QQ.valuation(2)._extensions_to_quotient(R.quo(x^2 + x + 1)) + [2-adic valuation] + + """ + from sage.rings.valuation.valuation_space import DiscretePseudoValuationSpace + parent = DiscretePseudoValuationSpace(ring) + approximants = approximants or self.mac_lane_approximants(ring.modulus().change_ring(self.domain()), assume_squarefree=True, require_incomparability=True) + return [pAdicValuation(ring, approximant, approximants) for approximant in approximants] + + def extensions(self, ring): + r""" + Return the extensions of this valuation to ``ring``. + + EXAMPLES:: + + sage: v = ZZ.valuation(2) + sage: v.extensions(GaussianIntegers()) + [2-adic valuation] + + TESTS:: + + sage: R. = QQ[] + sage: L. = QQ.extension(x^3 - 2) + sage: R. = L[] + sage: M. = L.extension(b^2 + 2*b + a) + sage: M.valuation(2) + 2-adic valuation + + Check that we can extend to a field written as a quotient:: + + sage: R. = QQ[] + sage: K. = QQ.extension(x^2 + 1) + sage: R. = K[] + sage: L. = R.quo(x^2 + a) + sage: QQ.valuation(2).extensions(L) + [2-adic valuation] + + A case where there was at some point an internal error in the + approximants code:: + + sage: R. = QQ[] + sage: L. = NumberField(x^4 + 2*x^3 + 2*x^2 + 8) + sage: QQ.valuation(2).extensions(L) + [[ 2-adic valuation, v(x + 2) = 3/2 ]-adic valuation, + [ 2-adic valuation, v(x) = 1/2 ]-adic valuation] + + A case where the extension was incorrect at some point:: + + sage: v = QQ.valuation(2) + sage: L. = NumberField(x^2 + 2) + sage: M. = L.extension(x^2 + 1) + sage: w = v.extension(L).extension(M) + sage: w(w.uniformizer()) + 1/4 + + A case where the extensions could not be separated at some point:: + + sage: v = QQ.valuation(2) + sage: R. = QQ[] + sage: F = x^48 + 120*x^45 + 56*x^42 + 108*x^36 + 32*x^33 + 40*x^30 + 48*x^27 + 80*x^24 + 112*x^21 + 96*x^18 + 96*x^15 + 24*x^12 + 96*x^9 + 16*x^6 + 96*x^3 + 68 + sage: L. = QQ.extension(F) + sage: v.extensions(L) + [[ 2-adic valuation, v(x) = 1/24, v(x^24 + 4*x^18 + 10*x^12 + 12*x^6 + 8*x^3 + 6) = 29/8 ]-adic valuation, + [ 2-adic valuation, v(x) = 1/24, v(x^24 + 4*x^18 + 2*x^12 + 12*x^6 + 8*x^3 + 6) = 29/8 ]-adic valuation] + + """ + if self.domain() is ring: + return [self] + domain_fraction_field = _fraction_field(self.domain()) + if domain_fraction_field is not self.domain(): + if domain_fraction_field.is_subring(ring): + return pAdicValuation(domain_fraction_field, self).extensions(ring) + if self.domain().is_subring(ring): + from sage.rings.polynomial.polynomial_quotient_ring import is_PolynomialQuotientRing + if is_PolynomialQuotientRing(ring): + if is_PolynomialQuotientRing(self.domain()): + if self.domain().modulus() == ring.modulus(): + base_extensions = self._base_valuation.extensions(self._base_valuation.domain().change_ring(self._base_valuation.domain().base_ring().fraction_field())) + return [pAdicValuation(ring, base._initial_approximation) for base in base_extensions] + if ring.base_ring() is self.domain(): + from sage.categories.all import IntegralDomains + if ring in IntegralDomains(): + return self._extensions_to_quotient(ring) + elif self.domain().is_subring(ring.base_ring()): + return sum([w.extensions(ring) for w in self.extensions(ring.base_ring())], []) + from sage.rings.number_field.number_field import is_NumberField + if is_NumberField(ring.fraction_field()): + if ring.base_ring().fraction_field() is self.domain().fraction_field(): + from sage.rings.valuation.valuation_space import DiscretePseudoValuationSpace + parent = DiscretePseudoValuationSpace(ring) + approximants = self.mac_lane_approximants(ring.fraction_field().relative_polynomial().change_ring(self.domain()), assume_squarefree=True, require_incomparability=True) + return [pAdicValuation(ring, approximant, approximants) for approximant in approximants] + if ring.base_ring() is not ring and self.domain().is_subring(ring.base_ring()): + return sum([w.extensions(ring) for w in self.extensions(ring.base_ring())], []) + return super(pAdicValuation_base, self).extensions(ring) + + def restriction(self, ring): + r""" + Return the restriction of this valuation to ``ring``. + + EXAMPLES:: + + sage: v = GaussianIntegers().valuation(2) + sage: v.restriction(ZZ) + 2-adic valuation + + """ + if ring is self.domain(): + return self + + if not ring.is_subring(self.domain()): + raise ValueError("ring must be a subring of the domain of this valuation but %r is not a subring of %r"%(ring, self.domain())) + + return pAdicValuation(ring, self.p()) + + @cached_method + def value_semigroup(self): + r""" + Return the value semigroup of this valuation. + + EXAMPLES:: + + sage: v = GaussianIntegers().valuation(2) + sage: v.value_semigroup() + Additive Abelian Semigroup generated by 1/2 + + """ + from sage.categories.all import Fields + v = self(self.uniformizer()) + if self.domain() in Fields(): + return DiscreteValueSemigroup([-v,v]) + else: + return DiscreteValueSemigroup([v]) + + +class pAdicValuation_padic(pAdicValuation_base): + """ + The `p`-adic valuation of a complete `p`-adic ring. + + INPUT: + + - ``R`` -- a `p`-adic ring + + EXAMPLES:: + + sage: v = Qp(2).valuation(); v #indirect doctest + 2-adic valuation + + TESTS:: + + sage: TestSuite(v).run() # long time + + """ + def __init__(self, parent): + """ + TESTS:: + + sage: from sage.rings.padics.padic_valuation import pAdicValuation_padic + sage: isinstance(Qp(2).valuation(), pAdicValuation_padic) + True + + """ + pAdicValuation_base.__init__(self, parent, parent.domain().prime()) + + def reduce(self, x): + """ + Reduce ``x`` modulo the ideal of elements of positive valuation. + + INPUT: + + - ``x`` -- an element of the domain of this valuation + + OUTPUT: + + An element of the :meth:`~sage.rings.valuation.valuation_space.DiscretePseudoValuationSpace.ElementMethods.residue_field`. + + EXAMPLES:: + + sage: R = Zp(3) + sage: Zp(3).valuation().reduce(R(4)) + 1 + + """ + x = self.domain().coerce(x) + return self.residue_field()(x.residue()) + + def lift(self, x): + """ + Lift ``x`` from the :meth:`~sage.rings.valuation.valuation_space.DiscretePseudoValuationSpace.ElementMethods.residue_field` to the domain of this + valuation. + + INPUT: + + - ``x`` -- an element of the residue field of this valuation + + EXAMPLES:: + + sage: R = Zp(3) + sage: v = R.valuation() + sage: xbar = v.reduce(R(4)) + sage: v.lift(xbar) + 1 + O(3^20) + + """ + x = self.residue_field().coerce(x) + return self.domain()(x).lift_to_precision() + + def uniformizer(self): + """ + Return a uniformizer of this valuation. + + EXAMPLES:: + + sage: v = Zp(3).valuation() + sage: v.uniformizer() + 3 + O(3^21) + + """ + return self.domain().uniformizer() + + def element_with_valuation(self, v): + """ + Return an element of valuation ``v``. + + INPUT: + + - ``v`` -- an element of the :meth:`pAdicValuation_base.value_semigroup` of this valuation + + EXAMPLES:: + + sage: R = Zp(3) + sage: v = R.valuation() + sage: v.element_with_valuation(3) + 3^3 + O(3^23) + + sage: K = Qp(3) + sage: R. = K[] + sage: L. = K.extension(y^2 + 3*y + 3) + sage: L.valuation().element_with_valuation(3/2) + y^3 + O(y^43) + + """ + from sage.rings.all import QQ, ZZ + v = QQ(v) + if v not in self.value_semigroup(): + raise ValueError("%r is not in the value semigroup of %r"%(v, self)) + v = ZZ(v * self.domain().ramification_index()) + return self.domain().one() << v + + def _repr_(self): + """ + Return a printable representation of this valuation. + + EXAMPLES:: + + sage: ZZ.valuation(3)._repr_() + '3-adic valuation' + + """ + return "%s-adic valuation"%(self.p()) + + def _call_(self, x): + r""" + Evaluate this valuation at ``x``. + + EXAMPLES:: + + sage: K = Qp(3) + sage: R. = K[] + sage: L. = K.extension(y^2 - 3) + sage: L.valuation()(3) + 1 + + """ + return x.ordp() + + def residue_ring(self): + r""" + Return the residue field of this valuation. + + EXAMPLES:: + + sage: Qq(9, names='a').valuation().residue_ring() + Finite Field in a0 of size 3^2 + + """ + return self.domain().residue_field() + + def shift(self, x, s): + r""" + Shift ``x`` in its expansion with respect to :meth:`uniformizer` by + ``s`` "digits". + + For non-negative ``s``, this just returns ``x`` multiplied by a + power of the uniformizer `\pi`. + + For negative ``s``, it does the same but when not over a field, it + drops coefficients in the `\pi`-adic expension which have negative + valuation. + + EXAMPLES:: + + sage: R = ZpCA(2) + sage: v = R.valuation() + sage: v.shift(R.one(), 1) + 2 + O(2^20) + sage: v.shift(R.one(), -1) + O(2^19) + + sage: S. = R[] + sage: S. = R.extension(y^3 - 2) + sage: v = S.valuation() + sage: v.shift(1, 5) + y^5 + O(y^60) + + """ + from sage.rings.all import ZZ + x = self.domain().coerce(x) + s = self.value_group()(s) + return x << s + + def simplify(self, x, error=None, force=False): + r""" + Return a simplified version of ``x``. + + Produce an element which differs from ``x`` by an element of + valuation strictly greater than the valuation of ``x`` (or strictly + greater than ``error`` if set.) + + INPUT: + + - ``x`` -- an element in the domain of this valuation + + - ``error`` -- a rational, infinity, or ``None`` (default: ``None``), + the error allowed to introduce through the simplification + + - ``force`` -- ignored + + EXAMPLES:: + + sage: R = Zp(2) + sage: v = R.valuation() + sage: v.simplify(6) + 2 + O(2^21) + sage: v.simplify(6, error=0) + 0 + + """ + x = self.domain().coerce(x) + + if error is None: + error = self(x) + from sage.rings.all import infinity + if error is infinity: + return x + # we need to scale by the ramification index because p-adics use a + # different normalization + normalized_error = (error / self.value_group().gen()).ceil() + return x.add_bigoh(normalized_error + 1).lift_to_precision() + + +class pAdicValuation_int(pAdicValuation_base): + r""" + A `p`-adic valuation on the integers or the rationals. + + EXAMPLES:: + + sage: v = ZZ.valuation(3); v + 3-adic valuation + + TESTS:: + + sage: TestSuite(v).run() # long time + + """ + def _repr_(self): + """ + Return a printable representation of this valuation. + + EXAMPLES:: + + sage: ZZ.valuation(3)._repr_() + '3-adic valuation' + + """ + return "%s-adic valuation"%(self.p()) + + def _call_(self, x): + """ + Evaluate this valuation at ``x``. + + INPUT: + + - ``x`` -- an element in the domain of this valuation + + EXAMPLES:: + + sage: ZZ.valuation(3)(9) + 2 + + """ + if x.is_zero(): + # x.valuation() is a factor 10 slower when computing the valuation + # of a rational zero than when computing the valuation of another + # small rational. Special casing this is a factor 100 faster. + return infinity + return x.valuation(self._p) + + def uniformizer(self): + """ + Return a uniformizer of this `p`-adic valuation, i.e., `p` as an + element of the domain. + + EXAMPLES:: + + sage: v = ZZ.valuation(3) + sage: v.uniformizer() + 3 + + """ + return self.domain()(self.p()) + + def residue_ring(self): + """ + Return the residue field of this valuation. + + EXAMPLES:: + + sage: v = ZZ.valuation(3) + sage: v.residue_ring() + Finite Field of size 3 + + """ + from sage.rings.all import GF + return GF(self.p()) + + def _ge_(self, other): + r""" + Return whether this valuation is greater than or equal than ``other`` + everywhere. + + EXAMPLES:: + + sage: v = ZZ.valuation(2) + sage: w = valuations.TrivialValuation(ZZ) + sage: v >= w + True + + """ + if other.is_trivial(): + return other.is_discrete_valuation() + if isinstance(other, pAdicValuation_int): + return self.p() == other.p() + return super(pAdicValuation_base, self)._ge_(other) + + def _relative_size(self, x): + r""" + Return an estimate on the coefficient size of ``x``. + + The number returned is an estimate on the factor between the number of + bits used by ``x`` and the minimal number of bits used by an element + congruent to ``x``. + + This is used by :meth:`simplify` to decide whether simplification of + coefficients is going to lead to a significant shrinking of the + coefficients of ``x``. + + EXAMPLES:: + + sage: v = ZZ.valuation(2) + sage: v._relative_size(2) + 1 + sage: v._relative_size(2**20) + 11 + + """ + x = self.domain().coerce(x) + return (x.numerator().nbits() + x.denominator().nbits())//self.p().nbits() + + def simplify(self, x, error=None, force=False, size_heuristic_bound=32): + r""" + Return a simplified version of ``x``. + + Produce an element which differs from ``x`` by an element of + valuation strictly greater than the valuation of ``x`` (or strictly + greater than ``error`` if set.) + + INPUT: + + - ``x`` -- an element in the domain of this valuation + + - ``error`` -- a rational, infinity, or ``None`` (default: ``None``), + the error allowed to introduce through the simplification + + - ``force`` -- ignored + + - ``size_heuristic_bound`` -- when ``force`` is not set, the expected + factor by which the ``x`` need to shrink to perform an actual + simplification (default: 32) + + EXAMPLES:: + + sage: v = ZZ.valuation(2) + sage: v.simplify(6, force=True) + 2 + sage: v.simplify(6, error=0, force=True) + 0 + + """ + if not force and self._relative_size(x) <= size_heuristic_bound: + return x + + x = self.domain().coerce(x) + + v = self(x) + if error is None: + error = v + from sage.rings.all import infinity + if error is infinity: + return x + if error < v: + return self.domain().zero() + from sage.rings.all import QQ + error = QQ(error).ceil() + + from sage.rings.all import Qp + precision_ring = Qp(self.p(), error + 1 - v) + reduced = precision_ring(x) + if error - v >= 5: + # If there is not much relative precision left, it is better to + # just go with the integer/rational lift. The rational + # reconstruction is likely not smaller. + try: + reconstruction = reduced.rational_reconstruction() + if reconstruction in self.domain(): + return self.domain()(reconstruction) + except ArithmeticError:pass + + return self.domain()(reduced.lift()) + + def inverse(self, x, precision): + r""" + Return an approximate inverse of ``x``. + + The element returned is such that the product differs from 1 by an + element of valuation at least ``precision``. + + INPUT: + + - ``x`` -- an element in the domain of this valuation + + - ``precision`` -- a rational or infinity + + EXAMPLES:: + + sage: v = ZZ.valuation(2) + sage: x = 3 + sage: y = v.inverse(3, 2); y + 3 + sage: x*y - 1 + 8 + + This might not be possible for elements of positive valuation:: + + sage: v.inverse(2, 2) + Traceback (most recent call last): + ... + ValueError: element has no approximate inverse in this ring + + Unless the precision is very small:: + + sage: v.inverse(2, 0) + 1 + + """ + if not x.is_zero(): + y = ~x + if y in self.domain(): + return self.domain()(y) + if precision <= 0: + return self.domain().one() + + from sage.rings.all import infinity + if self(x) > 0 or precision is infinity: + raise ValueError("element has no approximate inverse in this ring") + + from sage.rings.all import ZZ, QQ + return self.domain()(ZZ(x).inverse_mod(self.p() ** QQ(precision).ceil())) + + +class pAdicFromLimitValuation(FiniteExtensionFromLimitValuation, pAdicValuation_base): + r""" + A `p`-adic valuation on a number field or a subring thereof, i.e., a + valuation that extends the `p`-adic valuation on the integers. + + EXAMPLES:: + + sage: v = GaussianIntegers().valuation(3); v + 3-adic valuation + + TESTS:: + + sage: TestSuite(v).run(skip='_test_shift') # long time + + The ``_test_shift`` test fails because the parent of the shift is + incorrect, see :trac:`23971`:: + + sage: v.shift(1, -1).parent() + Number Field in I with defining polynomial x^2 + 1 + + """ + def __init__(self, parent, approximant, G, approximants): + r""" + TESTS:: + + sage: v = GaussianIntegers().valuation(3) + sage: from sage.rings.padics.padic_valuation import pAdicFromLimitValuation + sage: isinstance(v, pAdicFromLimitValuation) + True + + """ + FiniteExtensionFromLimitValuation.__init__(self, parent, approximant, G, approximants) + pAdicValuation_base.__init__(self, parent, approximant.restriction(approximant.domain().base_ring()).p()) + + def _to_base_domain(self, f): + r""" + Return ``f``, an element of the underlying limit valuation, as an + element of the domain of this valuation. + + EXAMPLES:: + + sage: v = GaussianIntegers().valuation(3) + sage: I = GaussianIntegers().fraction_field().gen() + sage: v._to_base_domain(I) + x + + TESTS: + + Check that this also works for relative extensions:: + + sage: v = QQ.valuation(2) + sage: L. = NumberField(x^2 + 2) + sage: M. = L.extension(x^2 + 1) + sage: w = v.extension(L).extension(M) + sage: w._to_base_domain(b) + x + + """ + polynomial = f.lift() + return polynomial(self._base_valuation.domain().gen()) + + def _from_base_domain(self, f): + r""" + Return ``f``, an element of the domain of this valuation, as an element + of the domain of the underlying limit valuation. + + EXAMPLES:: + + sage: v = GaussianIntegers().valuation(3) + sage: v._from_base_domain(v._base_valuation.domain().gen()) + I + + """ + return self.domain()(f) + + def extensions(self, ring): + r""" + Return the extensions of this valuation to ``ring``. + + EXAMPLES:: + + sage: v = GaussianIntegers().valuation(3) + sage: v.extensions(v.domain().fraction_field()) + [3-adic valuation] + + """ + if ring is self.domain().fraction_field(): + if self.domain() is not self.domain().fraction_field(): + base_ring = self.domain().base_ring() + base_valuation = self.restriction(base_ring).extension(base_ring.fraction_field()) + G = ring.relative_polynomial() + approximant = self._base_valuation.change_domain(G.parent())._initial_approximation + return [pAdicValuation(ring, approximant)] + return super(pAdicFromLimitValuation, self).extensions(ring) + +def _fraction_field(ring): + r""" + Return a fraction field of ``ring``. + + EXAMPLES: + + This works around some annoyances with ``ring.fraction_field()``:: + + sage: R. = ZZ[] + sage: S = R.quo(x^2 + 1) + sage: S.fraction_field() + Fraction Field of Univariate Quotient Polynomial Ring in xbar over Integer Ring with modulus x^2 + 1 + + sage: from sage.rings.padics.padic_valuation import _fraction_field + sage: _fraction_field(S) + Univariate Quotient Polynomial Ring in xbar over Rational Field with modulus x^2 + 1 + + """ + from sage.categories.all import Fields + if ring in Fields(): + return ring + + from sage.rings.polynomial.polynomial_quotient_ring import is_PolynomialQuotientRing + if is_PolynomialQuotientRing(ring): + from sage.categories.all import IntegralDomains + if ring in IntegralDomains(): + return ring.base().change_ring(ring.base_ring().fraction_field()).quo(ring.modulus()) + return ring.fraction_field() diff --git a/src/sage/rings/rational_field.py b/src/sage/rings/rational_field.py index 3e66bf4df67..80beed4425c 100644 --- a/src/sage/rings/rational_field.py +++ b/src/sage/rings/rational_field.py @@ -1319,6 +1319,26 @@ def _factor_univariate_polynomial(self, f): from sage.structure.factorization import Factorization return Factorization(F, f.leading_coefficient()) + def valuation(self, p): + r""" + Return the discrete valuation with uniformizer ``p``. + + EXAMPLES:: + + sage: v = QQ.valuation(3); v + 3-adic valuation + sage: v(1/3) + -1 + + .. SEEALSO:: + + :meth:`NumberField_generic.valuation() `, + :meth:`IntegerRing_class.valuation() ` + + """ + from sage.rings.padics.padic_valuation import pAdicValuation + return pAdicValuation(self, p) + QQ = RationalField() Q = QQ diff --git a/src/sage/rings/valuation/__init__.py b/src/sage/rings/valuation/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/sage/rings/valuation/all.py b/src/sage/rings/valuation/all.py new file mode 100644 index 00000000000..3f90ef9daa0 --- /dev/null +++ b/src/sage/rings/valuation/all.py @@ -0,0 +1,5 @@ +from sage.misc.lazy_import import lazy_import + +lazy_import('sage.rings.valuation.gauss_valuation', 'GaussValuation') +lazy_import('sage.rings.valuation', 'valuations_catalog', 'valuations') +lazy_import('sage.rings.valuation.value_group', 'DiscreteValueGroup') diff --git a/src/sage/rings/valuation/augmented_valuation.py b/src/sage/rings/valuation/augmented_valuation.py new file mode 100644 index 00000000000..eca54abf6af --- /dev/null +++ b/src/sage/rings/valuation/augmented_valuation.py @@ -0,0 +1,2105 @@ +# -*- coding: utf-8 -*- +r""" +Augmented valuations on polynomial rings + +Implements augmentations of (inductive) valuations. + +AUTHORS: + +- Julian Rüth (2013-04-15): initial version + +EXAMPLES: + +Starting from a :mod:`Gauss valuation `, we can create augmented valuations on +polynomial rings:: + + sage: R. = QQ[] + sage: v = GaussValuation(R, QQ.valuation(2)) + sage: w = v.augmentation(x, 1); w + [ Gauss valuation induced by 2-adic valuation, v(x) = 1 ] + sage: w(x) + 1 + +This also works for polynomial rings over base rings which are not fields. +However, much of the functionality is only available over fields:: + + sage: R. = ZZ[] + sage: v = GaussValuation(R, ZZ.valuation(2)) + sage: w = v.augmentation(x, 1); w + [ Gauss valuation induced by 2-adic valuation, v(x) = 1 ] + sage: w(x) + 1 + +TESTS:: + + sage: R. = QQ[] + sage: v = GaussValuation(R, QQ.valuation(2)) + sage: w = v.augmentation(x, 1) + sage: TestSuite(w).run() # long time + + sage: w = v.augmentation(x, 2) + sage: TestSuite(w).run() # long time + +Run the test suite for a valuation with a residual extension:: + + sage: R. = QQ[] + sage: v = GaussValuation(R, QQ.valuation(2)) + sage: w = v.augmentation(x^2 + x + 1, 1) + sage: TestSuite(w).run() # long time + +Run the test suite for an iterated residual extension starting from a +non-prime residue field:: + + sage: R. = Qq(4, 40) + sage: S. = R[] + sage: v = GaussValuation(S) + sage: w = v.augmentation(x^2 + x + u, 1/2) + sage: TestSuite(w).run() # long time + + sage: ww = w.augmentation(x^8 + 4*x^7 + 2*x^6 + 2*x^5 + x^4 + 2*x^3 + 4*(u + 1)*x^2 + 6*(u + 1)*x + 4 + 3*u, 10) + sage: TestSuite(ww).run() # long time + +Run the test suite for an augmentation of a ramified augmentation:: + + sage: R. = Qq(4, 5) + sage: S. = R[] + sage: v = GaussValuation(S) + sage: w = v.augmentation(x, 3/4) + sage: TestSuite(w).run() # long time + + sage: ww = w.augmentation(x^4 + 8, 5) + sage: TestSuite(ww).run() # long time + +Run the test suite for a ramified augmentation of an unramified augmentation:: + + sage: R. = QQ[] + sage: v = GaussValuation(R, QQ.valuation(2)) + sage: w = v.augmentation(x^2 + x + 1, 1) + sage: TestSuite(w).run() # long time + + sage: ww = w.augmentation(x^4 + 2*x^3 + 5*x^2 + 8*x + 3, 16/3) + sage: TestSuite(ww).run() # long time + +Run the test suite for a ramified augmentation of a ramified augmentation:: + + sage: R. = Qq(4, 20) + sage: S. = R[] + sage: v = GaussValuation(S) + sage: w = v.augmentation(x^2 + x + u, 1/2) + sage: TestSuite(w).run() # long time + + sage: ww = w.augmentation((x^2 + x + u)^2 + 2, 5/3) + sage: TestSuite(ww).run() # long time + +Run the test suite for another augmentation with iterated residue field extensions:: + + sage: R. = Qq(4, 10) + sage: S. = R[] + sage: v = GaussValuation(S) + sage: w = v.augmentation(x^2 + x + u, 1) + sage: TestSuite(w).run() # long time + + sage: ww = w.augmentation((x^2 + x + u)^2 + 2*x*(x^2 + x + u) + 4*x, 3) + sage: TestSuite(ww).run() # long time + +Run the test suite for a rather trivial pseudo-valuation:: + + sage: R. = Qq(4, 5) + sage: S. = R[] + sage: v = GaussValuation(S) + sage: w = v.augmentation(x, infinity) + sage: TestSuite(w).run() # long time + +Run the test suite for an infinite valuation which extends the residue field:: + + sage: R. = Qq(4, 5) + sage: S. = R[] + sage: v = GaussValuation(S) + sage: w = v.augmentation(x^2 + x + u, infinity) + sage: TestSuite(w).run() # long time + +Run the test suite for an infinite valuation which extends a valuation which +extends the residue field:: + + sage: R. = Qq(4, 5) + sage: S. = R[] + sage: v = GaussValuation(S) + sage: w = v.augmentation(x^2 + x + u, 1/2) + sage: TestSuite(w).run() # long time + + sage: ww = w.augmentation((x^2 + x + u)^2 + 2, infinity) + sage: TestSuite(ww).run() # long time + +Run the test suite if the polynomial ring is not over a field:: + + sage: R. = ZZ[] + sage: v = GaussValuation(R, ZZ.valuation(2)) + sage: w = v.augmentation(x, 1) + sage: TestSuite(w).run() # long time + +REFERENCES: + +Augmentations are described originally in [Mac1936I]_ and [Mac1936II]_. An +overview can also be found in Chapter 4 of [Rüt2014]_. + +""" +#***************************************************************************** +# Copyright (C) 2013-2017 Julian Rüth +# +# Distributed under the terms of the GNU General Public License (GPL) +# as published by the Free Software Foundation; either version 2 of +# the License, or (at your option) any later version. +# http://www.gnu.org/licenses/ +#***************************************************************************** +from inductive_valuation import _lift_to_maximal_precision +from inductive_valuation import FinalInductiveValuation, NonFinalInductiveValuation, FiniteInductiveValuation, InfiniteInductiveValuation, InductiveValuation +from valuation import InfiniteDiscretePseudoValuation, DiscreteValuation + +from sage.misc.cachefunc import cached_method +from sage.rings.all import infinity, QQ, ZZ +from sage.structure.factory import UniqueFactory + +class AugmentedValuationFactory(UniqueFactory): + r""" + Factory for augmented valuations. + + EXAMPLES: + + This factory is not meant to be called directly. Instead, + :meth:`~sage.rings.valuation.inductive_valuation.NonFinalInductiveValuation.augmentation` + of a valuation should be called:: + + sage: R. = QQ[] + sage: v = GaussValuation(R, QQ.valuation(2)) + sage: w = v.augmentation(x, 1) # indirect doctest + + Note that trivial parts of the augmented valuation might be dropped, so you + should not rely on ``_base_valuation`` to be the valuation you started + with:: + + sage: ww = w.augmentation(x, 2) + sage: ww._base_valuation is v + True + + """ + def create_key(self, base_valuation, phi, mu, check=True): + r""" + Create a key which uniquely identifies the valuation over + ``base_valuation`` which sends ``phi`` to ``mu``. + + .. NOTE:: + + The uniqueness that this factory provides is not why we chose to + use a factory. However, it makes pickling and equality checks much + easier. At the same time, going through a factory makes it easier + to enforce that all instances correctly inherit methods from the + parent Hom space. + + TESTS:: + + sage: R. = QQ[] + sage: v = GaussValuation(R, QQ.valuation(2)) + sage: w = v.augmentation(x, 1) # indirect doctest + sage: ww = v.augmentation(x, 1) + sage: w is ww + True + + """ + if check: + is_key, reason = base_valuation.is_key(phi, explain=True) + if not is_key: + raise ValueError(reason) + if mu <= base_valuation(phi): + raise ValueError("the value of the key polynomial must strictly increase but `%s` does not exceed `%s`."%(mu, base_valuation(phi))) + if not isinstance(base_valuation, InductiveValuation): + raise TypeError("base_valuation must be inductive") + + phi = base_valuation.domain().coerce(phi) + if mu is not infinity: + mu = QQ(mu) + + if isinstance(base_valuation, AugmentedValuation_base): + if phi.degree() == base_valuation.phi().degree(): + # drop base_valuation and extend base_valuation._base_valuation instead + return self.create_key(base_valuation._base_valuation, phi, mu, check=check) + + return base_valuation, phi, mu + + def create_object(self, version, key): + r""" + Create the augmented valuation represented by ``key``. + + TESTS:: + + sage: R. = QQ[] + sage: v = GaussValuation(R, QQ.valuation(2)) + sage: w = v.augmentation(x^2 + x + 1, 1) # indirect doctest + + """ + base_valuation, phi, mu = key + + from valuation_space import DiscretePseudoValuationSpace + parent = DiscretePseudoValuationSpace(base_valuation.domain()) + if mu is not infinity: + if base_valuation.is_trivial(): + return parent.__make_element_class__(FinalFiniteAugmentedValuation)(parent, base_valuation, phi, mu) + else: + return parent.__make_element_class__(NonFinalFiniteAugmentedValuation)(parent, base_valuation, phi, mu) + else: + return parent.__make_element_class__(InfiniteAugmentedValuation)(parent, base_valuation, phi, mu) + +AugmentedValuation = AugmentedValuationFactory("sage.rings.valuation.augmented_valuation.AugmentedValuation") + +class AugmentedValuation_base(InductiveValuation): + r""" + An augmented valuation is a discrete valuation on a polynomial ring. It + extends another discrete valuation `v` by setting the valuation of a + polynomial `f` to the minumum of `v(f_i)i\mu` when writing `f=\sum_i + f_i\phi^i`. + + INPUT: + + - ``v`` -- a :class:`~sage.rings.valuation.inductive_valuation.InductiveValuation` on a polynomial ring + + - ``phi`` -- a :meth:`key polynomial ` over ``v`` + + - ``mu`` -- a rational number such that ``mu > v(phi)`` or ``infinity`` + + EXAMPLES:: + + sage: K. = CyclotomicField(5) + sage: R. = K[] + sage: v = GaussValuation(R, K.valuation(2)) + sage: w = v.augmentation(x, 1/2); w # indirect doctest + [ Gauss valuation induced by 2-adic valuation, v(x) = 1/2 ] + sage: ww = w.augmentation(x^4 + 2*x^2 + 4*u, 3); ww + [ Gauss valuation induced by 2-adic valuation, v(x) = 1/2, v(x^4 + 2*x^2 + 4*u) = 3 ] + + TESTS:: + + sage: TestSuite(w).run() # long time + sage: TestSuite(ww).run() # long time + + """ + def __init__(self, parent, v, phi, mu): + r""" + TESTS:: + + sage: K. = Qq(4, 5) + sage: R. = K[] + sage: v = GaussValuation(R) + sage: from sage.rings.valuation.augmented_valuation import AugmentedValuation + sage: w = AugmentedValuation(v, x, 1/2) + sage: from sage.rings.valuation.augmented_valuation import AugmentedValuation_base + sage: isinstance(w, AugmentedValuation_base) + True + + sage: TestSuite(w).run() # long time + + """ + InductiveValuation.__init__(self, parent, phi) + + self._base_valuation = v + self._mu = mu + + @cached_method + def equivalence_unit(self, s, reciprocal=False): + r""" + Return an equivalence unit of minimal degree and valuation ``s``. + + INPUT: + + - ``s`` -- a rational number + + - ``reciprocal`` -- a boolean (default: ``False``); whether or not to + return the equivalence unit as the :meth:`~sage.rings.valuation.inductive_valuation.InductiveValuation.equivalence_reciprocal` + of the equivalence unit of valuation ``-s``. + + OUTPUT: + + A polynomial in the domain of this valuation which + :meth:`~sage.rings.valuation.inductive_valuation.InductiveValuation.is_equivalence_unit` for this valuation. + + EXAMPLES:: + + sage: R. = Qq(4, 5) + sage: S. = R[] + sage: v = GaussValuation(S) + sage: w = v.augmentation(x^2 + x + u, 1) + + sage: w.equivalence_unit(0) + 1 + O(2^5) + sage: w.equivalence_unit(-4) + 2^-4 + O(2) + + Since an equivalence unit is of effective degree zero, `\phi` must not + divide it. Therefore, its valuation is in the value group of the base + valuation:: + + sage: w = v.augmentation(x, 1/2) + + sage: w.equivalence_unit(3/2) + Traceback (most recent call last): + ... + ValueError: 3/2 is not in the value semigroup of 2-adic valuation + sage: w.equivalence_unit(1) + 2 + O(2^6) + + An equivalence unit might not be integral, even if ``s >= 0``:: + + sage: w = v.augmentation(x, 3/4) + sage: ww = w.augmentation(x^4 + 8, 5) + + sage: ww.equivalence_unit(1/2) + (2^-1 + O(2^4))*x^2 + + """ + if reciprocal: + ret = self._base_valuation.element_with_valuation(s) + residue = self.reduce(ret*self._base_valuation.element_with_valuation(-s), check=False) + assert residue.is_constant() + ret *= self.lift(~(residue[0])) + else: + ret = self._base_valuation.element_with_valuation(s) + + assert self.is_equivalence_unit(ret) + assert self(ret) == s + return ret + + @cached_method + def element_with_valuation(self, s): + r""" + Create an element of minimal degree and of valuation ``s``. + + INPUT: + + - ``s`` -- a rational number in the value group of this valuation + + OUTPUT: + + An element in the domain of this valuation + + EXAMPLES:: + + sage: R. = Qq(4, 5) + sage: S. = R[] + sage: v = GaussValuation(S) + sage: w = v.augmentation(x^2 + x + u, 1/2) + sage: w.element_with_valuation(0) + 1 + O(2^5) + sage: w.element_with_valuation(1/2) + (1 + O(2^5))*x^2 + (1 + O(2^5))*x + u + O(2^5) + sage: w.element_with_valuation(1) + 2 + O(2^6) + sage: c = w.element_with_valuation(-1/2); c + (2^-1 + O(2^4))*x^2 + (2^-1 + O(2^4))*x + u*2^-1 + O(2^4) + sage: w(c) + -1/2 + sage: w.element_with_valuation(1/3) + Traceback (most recent call last): + ... + ValueError: s must be in the value group of the valuation but 1/3 is not in Additive Abelian Group generated by 1/2. + + """ + if s not in self.value_group(): + raise ValueError("s must be in the value group of the valuation but %r is not in %r."%(s, self.value_group())) + error = s + + ret = self.domain().one() + while s not in self._base_valuation.value_group(): + ret *= self._phi + s -= self._mu + ret = ret * self._base_valuation.element_with_valuation(s) + return self.simplify(ret, error=error) + + def _repr_(self): + r""" + Return a printable representation of this valuation. + + EXAMPLES:: + + sage: R. = Qq(4, 5) + sage: S. = R[] + sage: v = GaussValuation(S) + sage: w = v.augmentation(x^2 + x + u, 1/2) + sage: w # indirect doctest + [ Gauss valuation induced by 2-adic valuation, v((1 + O(2^5))*x^2 + (1 + O(2^5))*x + u + O(2^5)) = 1/2 ] + + """ + vals = self.augmentation_chain() + vals.reverse() + vals = [ "v(%s) = %s"%(v._phi, v._mu) if isinstance(v, AugmentedValuation_base) else str(v) for v in vals ] + return "[ %s ]"%", ".join(vals) + + def augmentation_chain(self): + r""" + Return a list with the chain of augmentations down to the underlying :mod:`Gauss valuation `. + + EXAMPLES:: + + sage: R. = QQ[] + sage: v = GaussValuation(R, QQ.valuation(2)) + sage: w = v.augmentation(x, 1) + sage: w.augmentation_chain() + [[ Gauss valuation induced by 2-adic valuation, v(x) = 1 ], + Gauss valuation induced by 2-adic valuation] + + For performance reasons, (and to simplify the underlying + implementation,) trivial augmentations might get dropped. You should + not rely on :meth:`augmentation_chain` to contain all the steps that + you specified to create the current valuation:: + + sage: ww = w.augmentation(x, 2) + sage: ww.augmentation_chain() + [[ Gauss valuation induced by 2-adic valuation, v(x) = 2 ], + Gauss valuation induced by 2-adic valuation] + + """ + return [self] + self._base_valuation.augmentation_chain() + + @cached_method + def psi(self): + r""" + Return the minimal polynomial of the residue field extension of this valuation. + + OUTPUT: + + A polynomial in the residue ring of the base valuation + + EXAMPLES:: + + sage: R. = Qq(4, 5) + sage: S. = R[] + sage: v = GaussValuation(S) + + sage: w = v.augmentation(x^2 + x + u, 1/2) + sage: w.psi() + x^2 + x + u0 + + sage: ww = w.augmentation((x^2 + x + u)^2 + 2, 5/3) + sage: ww.psi() + x + 1 + + """ + R = self._base_valuation.equivalence_unit(-self._base_valuation(self._phi)) + F = self._base_valuation.reduce(self._phi*R, check=False).monic() + assert F.is_irreducible() + return F + + @cached_method + def E(self): + r""" + Return the ramification index of this valuation over its underlying + Gauss valuation. + + EXAMPLES:: + + sage: R. = Qq(4, 5) + sage: S. = R[] + sage: v = GaussValuation(S) + + sage: w = v.augmentation(x^2 + x + u, 1) + sage: w.E() + 1 + + sage: w = v.augmentation(x, 1/2) + sage: w.E() + 2 + + """ + if self.augmentation_chain()[-1]._base_valuation.is_trivial(): + raise NotImplementedError("ramification index is not defined over a trivial Gauss valuation") + return self.value_group().index(self._base_valuation.value_group()) * self._base_valuation.E() + + @cached_method + def F(self): + r""" + Return the degree of the residue field extension of this valuation + over the underlying Gauss valuation. + + EXAMPLES:: + + sage: R. = Qq(4, 5) + sage: S. = R[] + sage: v = GaussValuation(S) + + sage: w = v.augmentation(x^2 + x + u, 1) + sage: w.F() + 2 + + sage: w = v.augmentation(x, 1/2) + sage: w.F() + 1 + + """ + return self.phi().degree() // self._base_valuation.E() + + def extensions(self, ring): + r""" + Return the extensions of this valuation to ``ring``. + + EXAMPLES:: + + sage: R. = QQ[] + sage: v = GaussValuation(R, QQ.valuation(2)) + sage: w = v.augmentation(x^2 + x + 1, 1) + + sage: w.extensions(GaussianIntegers().fraction_field()['x']) + [[ Gauss valuation induced by 2-adic valuation, v(x^2 + x + 1) = 1 ]] + + """ + if ring is self.domain(): + return [self] + + from sage.rings.polynomial.polynomial_ring import is_PolynomialRing + if is_PolynomialRing(ring): # univariate + base_valuations = self._base_valuation.extensions(ring) + phi = self.phi().change_ring(ring.base_ring()) + + ret = [] + for v in base_valuations: + if v.is_key(phi): + ret.append(AugmentedValuation(v, phi, self._mu)) + else: + F = v.equivalence_decomposition(phi) + mu0 = v(phi) + for f,e in F: + # We construct a valuation with [v, w(phi) = mu] which should be such that + # self(phi) = self._mu, i.e., w(phi) = w(unit) + sum e_i * w(f_i) where + # the sum runs over all the factors in the equivalence decomposition of phi + # Solving for mu gives + mu = (self._mu - v(F.unit()) - sum([ee*v(ff) for ff,ee in F if ff != f])) / e + ret.append(AugmentedValuation(v, f, mu)) + return ret + + return super(AugmentedValuation_base, self).extensions(ring) + + def restriction(self, ring): + r""" + Return the restriction of this valuation to ``ring``. + + EXAMPLES:: + + sage: K = GaussianIntegers().fraction_field() + sage: R. = K[] + sage: v = GaussValuation(R, K.valuation(2)) + sage: w = v.augmentation(x^2 + x + 1, 1) + + sage: w.restriction(QQ['x']) + [ Gauss valuation induced by 2-adic valuation, v(x^2 + x + 1) = 1 ] + + """ + if ring.is_subring(self.domain()): + base = self._base_valuation.restriction(ring) + if ring.is_subring(self.domain().base_ring()): + return base + from sage.rings.polynomial.polynomial_ring import is_PolynomialRing + if is_PolynomialRing(ring): # univariate + return base.augmentation(self.phi().change_ring(ring.base_ring()), self._mu) + return super(AugmentedValuation_base, self).restriction(ring) + + def uniformizer(self): + r""" + Return a uniformizing element for this valuation. + + EXAMPLES:: + + sage: R. = QQ[] + sage: v = GaussValuation(R, QQ.valuation(2)) + sage: w = v.augmentation(x^2 + x + 1, 1) + + sage: w.uniformizer() + 2 + + """ + return self.element_with_valuation(self.value_group()._generator) + + def is_gauss_valuation(self): + r""" + Return whether this valuation is a Gauss valuation. + + EXAMPLES:: + + sage: R. = QQ[] + sage: v = GaussValuation(R, QQ.valuation(2)) + sage: w = v.augmentation(x^2 + x + 1, 1) + + sage: w.is_gauss_valuation() + False + + """ + assert(self._mu > 0) + return False + + def monic_integral_model(self, G): + r""" + Return a monic integral irreducible polynomial which defines the same + extension of the base ring of the domain as the irreducible polynomial + ``G`` together with maps between the old and the new polynomial. + + EXAMPLES:: + + sage: R. = QQ[] + sage: v = GaussValuation(R, QQ.valuation(2)) + sage: w = v.augmentation(x^2 + x + 1, 1) + + sage: w.monic_integral_model(5*x^2 + 1/2*x + 1/4) + (Ring endomorphism of Univariate Polynomial Ring in x over Rational Field + Defn: x |--> 1/2*x, + Ring endomorphism of Univariate Polynomial Ring in x over Rational Field + Defn: x |--> 2*x, + x^2 + 1/5*x + 1/5) + + """ + return self._base_valuation.monic_integral_model(G) + + def _ge_(self, other): + r""" + Return whether this valuation is greater or equal than ``other`` + everywhere. + + EXAMPLES:: + + sage: R. = QQ[] + sage: v = GaussValuation(R, QQ.valuation(2)) + sage: w = v.augmentation(x^2 + x + 1, 1) + sage: w >= v + True + sage: ww = v.augmentation(x^2 + x + 1, 2) + sage: ww >= w + True + sage: www = w.augmentation(x^4 + 2*x^3 + 5*x^2 + 8*x + 3, 16/3) + sage: www >= w + True + sage: www >= ww + False + + """ + from gauss_valuation import GaussValuation_generic + if other.is_trivial(): + return other.is_discrete_valuation() + if isinstance(other, GaussValuation_generic): + return self._base_valuation >= other + if isinstance(other, AugmentedValuation_base): + if self(other._phi) >= other._mu: + return self >= other._base_valuation + else: + return False + + return super(AugmentedValuation_base, self)._ge_(other) + + def is_trivial(self): + r""" + Return whether this valuation is trivial, i.e., zero outside of zero. + + EXAMPLES:: + + sage: R. = QQ[] + sage: v = GaussValuation(R, QQ.valuation(2)) + sage: w = v.augmentation(x^2 + x + 1, 1) + sage: w.is_trivial() + False + + """ + # We need to override the default implementation from valuation_space + # because that one uses uniformizer() which might not be implemented if + # the base ring is not a field. + return False + + def scale(self, scalar): + r""" + Return this valuation scaled by ``scalar``. + + EXAMPLES:: + + sage: R. = QQ[] + sage: v = GaussValuation(R, QQ.valuation(2)) + sage: w = v.augmentation(x^2 + x + 1, 1) + sage: 3*w # indirect doctest + [ Gauss valuation induced by 3 * 2-adic valuation, v(x^2 + x + 1) = 3 ] + + """ + if scalar in QQ and scalar > 0 and scalar != 1: + return self._base_valuation.scale(scalar).augmentation(self.phi(), scalar*self._mu) + return super(AugmentedValuation_base, self).scale(scalar) + + def _residue_ring_generator_name(self): + r""" + Return a name for a generator of the residue ring. + + This method is used by :meth:`residue_ring` to work around name clashes + with names in subrings of the residue ring. + + EXAMPLES:: + + sage: R. = QQ[] + sage: v = GaussValuation(R, QQ.valuation(2)) + sage: w = v.augmentation(x^2 + x + 1, 1) + sage: w._residue_ring_generator_name() + 'u1' + + """ + base = self._base_valuation.residue_ring().base() + # we need a name for a generator that is not present already in base + generator = 'u' + str(len(self.augmentation_chain()) - 1) + while True: + try: + base(generator) + generator = 'u' + generator + except NameError: + # use this name, it has no meaning in base + return generator + except TypeError: + # use this name, base can not handle strings, so hopefully, + # there are no variable names (such as in QQ or GF(p)) + return generator + + def _relative_size(self, f): + r""" + Return an estimate on the coefficient size of ``f``. + + The number returned is an estimate on the factor between the number of + bits used by ``f`` and the minimal number of bits used by an element + congruent to ``f``. + + This is used by :meth:`simplify` to decide whether simplification of + coefficients is going to lead to a significant shrinking of the + coefficients of ``f``. + + EXAMPLES:: + + sage: R. = QQ[] + sage: K. = QQ.extension(u^2 + u+ 1) + sage: S. = K[] + sage: v = GaussValuation(S, K.valuation(2)) + sage: w = v.augmentation(x^2 + x + u, 1/2) + sage: w._relative_size(x^2 + x + 1) + 1 + sage: w._relative_size(1048576*x^2 + 1048576*x + 1048576) + 11 + + """ + return self._base_valuation._relative_size(f) + + def is_negative_pseudo_valuation(self): + r""" + Return whether this valuation attains `-\infty`. + + EXAMPLES: + + No element in the domain of an augmented valuation can have valuation + `-\infty`, so this method always returns ``False``:: + + sage: R. = QQ[] + sage: v = GaussValuation(R, valuations.TrivialValuation(QQ)) + sage: w = v.augmentation(x, infinity) + sage: w.is_negative_pseudo_valuation() + False + + """ + return False + + def change_domain(self, ring): + r""" + Return this valuation over ``ring``. + + EXAMPLES: + + We can change the domain of an augmented valuation even if there is no coercion between rings:: + + sage: R. = GaussianIntegers()[] + sage: v = GaussValuation(R, GaussianIntegers().valuation(2)) + sage: v = v.augmentation(x, 1) + sage: v.change_domain(QQ['x']) + [ Gauss valuation induced by 2-adic valuation, v(x) = 1 ] + + """ + from sage.rings.polynomial.polynomial_ring import is_PolynomialRing + if is_PolynomialRing(ring) and ring.variable_name() == self.domain().variable_name(): + return self._base_valuation.change_domain(ring).augmentation(self.phi().change_ring(ring.base_ring()), self._mu, check=False) + return super(AugmentedValuation_base, self).change_domain(ring) + + +class FinalAugmentedValuation(AugmentedValuation_base, FinalInductiveValuation): + r""" + An augmented valuation which can not be augmented anymore, either because + it augments a trivial valuation or because it is infinite. + + EXAMPLES:: + + sage: R. = QQ[] + sage: v = GaussValuation(R, valuations.TrivialValuation(QQ)) + sage: w = v.augmentation(x, 1) + + """ + def __init__(self, parent, v, phi, mu): + r""" + TESTS:: + + sage: R. = QQ[] + sage: v = GaussValuation(R, valuations.TrivialValuation(QQ)) + sage: w = v.augmentation(x, 1) + sage: from sage.rings.valuation.augmented_valuation import FinalAugmentedValuation + sage: isinstance(w, FinalAugmentedValuation) + True + + """ + AugmentedValuation_base.__init__(self, parent, v, phi, mu) + FinalInductiveValuation.__init__(self, parent, phi) + + @cached_method + def residue_ring(self): + r""" + Return the residue ring of this valuation, i.e., the elements of + non-negative valuation modulo the elements of positive valuation. + + EXAMPLES:: + + sage: R. = QQ[] + sage: v = GaussValuation(R, valuations.TrivialValuation(QQ)) + + sage: w = v.augmentation(x, 1) + sage: w.residue_ring() + Rational Field + + sage: w = v.augmentation(x^2 + x + 1, infinity) + sage: w.residue_ring() + Number Field in u1 with defining polynomial x^2 + x + 1 + + An example with a non-trivial base valuation:: + + sage: v = GaussValuation(R, QQ.valuation(2)) + sage: w = v.augmentation(x^2 + x + 1, infinity) + sage: w.residue_ring() + Finite Field in u1 of size 2^2 + + Since trivial extensions of finite fields are not implemented, the + resulting ring might be identical to the residue ring of the underlying + valuation:: + + sage: w = v.augmentation(x, infinity) + sage: w.residue_ring() + Finite Field of size 2 + + TESTS: + + We avoid clashes in generator names:: + + sage: K. = FunctionField(QQ) + sage: v = K.valuation(x^2 + 2) + sage: R. = K[] + sage: L. = K.extension(y^2 + x^2) + sage: w = v.extension(L) + sage: w.residue_field() + Number Field in uu1 with defining polynomial y^2 - 2 over its base field + sage: w.residue_field().base_field() + Number Field in u1 with defining polynomial x^2 + 2 + + """ + # the following is correct, even if the polynomial ring is not over a field + + base = self._base_valuation.residue_ring().base() + if self.psi().degree() > 1: + generator = self._residue_ring_generator_name() + return base.extension(self.psi(), names=generator) + else: + # Do not call extension() if self.psi().degree() == 1: + # In that case the resulting field appears to be the same as the original field, + # however, it is not == to the original field (for finite fields at + # least) but a distinct copy (this is a bug in finite field's + # extension() implementation.) + return base + + def reduce(self, f, check=True, degree_bound=None, coefficients=None, valuations=None): + r""" + Reduce ``f`` module this valuation. + + INPUT: + + - ``f`` -- an element in the domain of this valuation + + - ``check`` -- whether or not to check whether ``f`` has non-negative + valuation (default: ``True``) + + - ``degree_bound`` -- an a-priori known bound on the degree of the + result which can speed up the computation (default: not set) + + - ``coefficients`` -- the coefficients of ``f`` as produced by + :meth:`~sage.rings.valuation.developing_valuation.DevelopingValuation.coefficients` + or ``None`` (default: ``None``); this can be used to speed up the + computation when the expansion of ``f`` is already known from a + previous computation. + + - ``valuations`` -- the valuations of ``coefficients`` or ``None`` + (default: ``None``); ignored + + OUTPUT: + + an element of the :meth:`residue_ring` of this valuation, the reduction + modulo the ideal of elements of positive valuation + + EXAMPLES:: + + sage: R. = QQ[] + sage: v = GaussValuation(R, valuations.TrivialValuation(QQ)) + + sage: w = v.augmentation(x, 1) + sage: w.reduce(x^2 + x + 1) + 1 + + sage: w = v.augmentation(x^2 + x + 1, infinity) + sage: w.reduce(x) + u1 + + TESTS: + + Cases with non-trivial base valuation:: + + sage: R. = Qq(4, 10) + sage: S. = R[] + sage: v = GaussValuation(S) + sage: v.reduce(x) + x + sage: v.reduce(S(u)) + u0 + + sage: w = v.augmentation(x^2 + x + u, 1/2) + sage: w.reduce(S.one()) + 1 + sage: w.reduce(S(2)) + 0 + sage: w.reduce(S(u)) + u0 + sage: w.reduce(x) # this gives the generator of the residue field extension of w over v + u1 + sage: f = (x^2 + x + u)^2 / 2 + sage: w.reduce(f) + x + sage: w.reduce(f + x + 1) + x + u1 + 1 + + sage: ww = w.augmentation((x^2 + x + u)^2 + 2, 5/3) + sage: g = ((x^2 + x + u)^2 + 2)^3 / 2^5 + sage: ww.reduce(g) + x + sage: ww.reduce(f) + 1 + sage: ww.is_equivalent(f, 1) + True + sage: ww.reduce(f * g) + x + sage: ww.reduce(f + g) + x + 1 + + """ + f = self.domain().coerce(f) + + if check: + v = self(f) + if v < 0: + raise ValueError("f must have non-negative valuation") + elif v > 0: + return self.residue_ring().zero() + + if coefficients is None: + constant_term = next(self.coefficients(f)) + else: + constant_term = coefficients[0] + constant_term_reduced = self._base_valuation.reduce(constant_term) + return constant_term_reduced(self._residue_field_generator()) + + @cached_method + def _residue_field_generator(self): + r""" + Return a root of :meth:`psi` in :meth:`residue_ring`. + + EXAMPLES:: + + sage: R. = QQ[] + sage: v = GaussValuation(R, valuations.TrivialValuation(QQ)) + + sage: w = v.augmentation(x, 1) + sage: w._residue_field_generator() + 0 + + sage: w = v.augmentation(x^2 + x + 1, infinity) + sage: w._residue_field_generator() + u1 + + A case with non-trivial base valuation:: + + sage: R. = Qq(4, 10) + sage: S. = R[] + sage: v = GaussValuation(S) + sage: w = v.augmentation(x^2 + x + u, infinity) + sage: w._residue_field_generator() + u1 + + """ + if self.psi().degree() == 1: + ret = self.residue_ring()(-self.psi()[0]) + else: + ret = self.residue_ring().gen() + + assert self.psi()(ret).is_zero() + return ret + + def lift(self, F): + r""" + Return a polynomial which reduces to ``F``. + + INPUT: + + - ``F`` -- an element of the :meth:`residue_ring` + + ALGORITHM: + + We simply undo the steps performed in :meth:`reduce`. + + OUTPUT: + + A polynomial in the domain of the valuation with reduction ``F`` + + EXAMPLES:: + + sage: R. = QQ[] + sage: v = GaussValuation(R, valuations.TrivialValuation(QQ)) + + sage: w = v.augmentation(x, 1) + sage: w.lift(1/2) + 1/2 + + sage: w = v.augmentation(x^2 + x + 1, infinity) + sage: w.lift(w.residue_ring().gen()) + x + + A case with non-trivial base valuation:: + + sage: R. = Qq(4, 10) + sage: S. = R[] + sage: v = GaussValuation(S) + sage: w = v.augmentation(x^2 + x + u, infinity) + sage: w.lift(w.residue_ring().gen()) + (1 + O(2^10))*x + + """ + F = self.residue_ring().coerce(F) + + if F.is_zero(): + return self.domain().zero() + if F.is_one(): + return self.domain().one() + + # Write F as a polynomial in self._residue_field_generator() + # We only have to do that if psi is non-trivial + if self.psi().degree() > 1: + from sage.rings.polynomial.polynomial_quotient_ring_element import PolynomialQuotientRingElement + from sage.rings.function_field.function_field_element import FunctionFieldElement_polymod + if isinstance(F, PolynomialQuotientRingElement): + G = F.lift() + elif isinstance(F, FunctionFieldElement_polymod): + G = F.element() + else: + G = F.polynomial() + assert(G(self._residue_field_generator()) == F) + F = G.change_variable_name(self._base_valuation.residue_ring().variable_name()) + + H = self._base_valuation.lift(F) + return self.domain()(H) + + +class NonFinalAugmentedValuation(AugmentedValuation_base, NonFinalInductiveValuation): + r""" + An augmented valuation which can be augmented further. + + EXAMPLES:: + + sage: R. = QQ[] + sage: v = GaussValuation(R, QQ.valuation(2)) + sage: w = v.augmentation(x^2 + x + 1, 1) + + """ + def __init__(self, parent, v, phi, mu): + r""" + TESTS:: + + sage: R. = QQ[] + sage: v = GaussValuation(R, QQ.valuation(2)) + sage: w = v.augmentation(x^2 + x + 1, 1) + sage: from sage.rings.valuation.augmented_valuation import NonFinalAugmentedValuation + sage: isinstance(w, NonFinalAugmentedValuation) + True + + """ + AugmentedValuation_base.__init__(self, parent, v, phi, mu) + NonFinalInductiveValuation.__init__(self, parent, phi) + + @cached_method + def residue_ring(self): + r""" + Return the residue ring of this valuation, i.e., the elements of + non-negative valuation modulo the elements of positive valuation. + + EXAMPLES:: + + sage: R. = QQ[] + sage: v = GaussValuation(R, QQ.valuation(2)) + + sage: w = v.augmentation(x^2 + x + 1, 1) + sage: w.residue_ring() + Univariate Polynomial Ring in x over Finite Field in u1 of size 2^2 + + Since trivial valuations of finite fields are not implemented, the + resulting ring might be identical to the residue ring of the underlying + valuation:: + + sage: w = v.augmentation(x, 1) + sage: w.residue_ring() + Univariate Polynomial Ring in x over Finite Field of size 2 (using ...) + + """ + from sage.categories.fields import Fields + if self.domain().base() not in Fields(): + raise NotImplementedError("only implemented for polynomial rings over fields") + + base = self._base_valuation.residue_ring().base() + if self.psi().degree() > 1: + generator = self._residue_ring_generator_name() + base = base.extension(self.psi(), names=generator) + else: + # Do not call extension() if self.psi().degree() == 1: + # In that case the resulting field appears to be the same as the original field, + # however, it is not == to the original field (for finite fields at + # least) but a distinct copy (this is a bug in finite field's + # extension() implementation.) + pass + return base[self.domain().variable_name()] + + def reduce(self, f, check=True, degree_bound=None, coefficients=None, valuations=None): + r""" + Reduce ``f`` module this valuation. + + INPUT: + + - ``f`` -- an element in the domain of this valuation + + - ``check`` -- whether or not to check whether ``f`` has non-negative + valuation (default: ``True``) + + - ``degree_bound`` -- an a-priori known bound on the degree of the + result which can speed up the computation (default: not set) + + - ``coefficients`` -- the coefficients of ``f`` as produced by + :meth:`~sage.rings.valuation.developing_valuation.DevelopingValuation.coefficients` + or ``None`` (default: ``None``); this can be used to speed up the + computation when the expansion of ``f`` is already known from a + previous computation. + + - ``valuations`` -- the valuations of ``coefficients`` or ``None`` + (default: ``None``) + + OUTPUT: + + an element of the :meth:`residue_ring` of this valuation, the reduction + modulo the ideal of elements of positive valuation + + ALGORITHM: + + We follow the algorithm given in the proof of Theorem 12.1 of [Mac1936I]_: + If ``f`` has positive valuation, the reduction is simply zero. + Otherwise, let `f=\sum f_i\phi^i` be the expansion of `f`, as computed + by + :meth:`~sage.rings.valuation.developing_valuation.DevelopingValuation.coefficients`. + Since the valuation is zero, the exponents `i` must all be multiples of + `\tau`, the index the value group of the base valuation in the value + group of this valuation. Hence, there is an + :meth:`~sage.rings.valuation.inductive_valuation.InductiveValuation.equivalence_unit` + `Q` with the same valuation as `\phi^\tau`. Let `Q'` be its + :meth:`~sage.rings.valuation.inductive_valuation.InductiveValuation.equivalence_reciprocal`. + Now, rewrite each term `f_i\phi^{i\tau}=(f_iQ^i)(\phi^\tau Q^{-1})^i`; + it turns out that the second factor in this expression is a lift of the + generator of the :meth:`~sage.rings.valuation.valuation_space.DiscretePseudoValuationSpace.ElementMethods.residue_field`. + The reduction of the first factor can be computed recursively. + + EXAMPLES:: + + sage: R. = Qq(4, 10) + sage: S. = R[] + sage: v = GaussValuation(S) + sage: v.reduce(x) + x + sage: v.reduce(S(u)) + u0 + + sage: w = v.augmentation(x^2 + x + u, 1/2) + sage: w.reduce(S.one()) + 1 + sage: w.reduce(S(2)) + 0 + sage: w.reduce(S(u)) + u0 + sage: w.reduce(x) # this gives the generator of the residue field extension of w over v + u1 + sage: f = (x^2 + x + u)^2 / 2 + sage: w.reduce(f) + x + sage: w.reduce(f + x + 1) + x + u1 + 1 + + sage: ww = w.augmentation((x^2 + x + u)^2 + 2, 5/3) + sage: g = ((x^2 + x + u)^2 + 2)^3 / 2^5 + sage: ww.reduce(g) + x + sage: ww.reduce(f) + 1 + sage: ww.is_equivalent(f, 1) + True + sage: ww.reduce(f * g) + x + sage: ww.reduce(f + g) + x + 1 + + """ + f = self.domain().coerce(f) + + if self.lower_bound(f) > 0: + return self.residue_ring().zero() + + tau = self.value_group().index(self._base_valuation.value_group()) + + if coefficients is None: + coefficients = self.coefficients(f) + if degree_bound is not None: + from itertools import islice + coefficients = islice(coefficients, 0, tau*degree_bound + 1, 1) + coefficients = list(coefficients) + + if valuations is None: + valuations = [] + valuations = valuations[::tau] + + # rewrite as sum of f_i phi^{i tau}, i.e., drop the coefficients that + # can have no influence on the reduction + for i,c in enumerate(coefficients): + if i % tau != 0: + if check: + v = self._base_valuation(c) + i*self._mu + assert v != 0 # this can not happen for an augmented valuation + if v < 0: + raise ValueError("f must not have negative valuation") + else: + # the validity of the coefficients with i % tau == 0 is checked by + # the recursive call to reduce below + # replace f_i by f_i Q^{i tau} + if i//tau >= len(valuations): + # we do not know the correct valuation of the coefficient, but + # the computation is faster if we know that the coefficient + # has positive valuation + valuations.append(self._base_valuation.lower_bound(c) + i*self._mu) + v = valuations[i//tau] + if v is infinity or v > 0: + coefficients[i] = self.domain().zero() + valuations[i//tau] = infinity + else: + coefficients[i] = c * self._Q(i//tau) + valuations[i//tau] -= i*self._mu + + coefficients = coefficients[::tau] + + # recursively reduce the f_i Q^{i tau} + C = [self._base_valuation.reduce(c, check=False)(self._residue_field_generator()) + if valuations[i] is not infinity + else self._base_valuation.residue_ring().zero() + for i,c in enumerate(coefficients)] + + # reduce the Q'^i phi^i + return self.residue_ring()(C) + + @cached_method + def _residue_field_generator(self): + r""" + Return a root of :meth:`psi` in :meth:`residue_ring`. + + EXAMPLES:: + + sage: R. = Qq(4, 10) + sage: S. = R[] + sage: v = GaussValuation(S) + sage: w = v.augmentation(x^2 + x + u, 1/2) + sage: w._residue_field_generator() + u1 + + """ + if self.residue_ring() == self._base_valuation.residue_ring(): + assert self.psi().degree() == 1 + ret = self.residue_ring().base()(-self.psi()[0]) + else: + ret = self.residue_ring().base().gen() + + assert ret.parent() is self.residue_ring().base() + assert self.psi()(ret).is_zero() + return ret + + def lift(self, F, report_coefficients=False): + r""" + Return a polynomial which reduces to ``F``. + + INPUT: + + - ``F`` -- an element of the :meth:`residue_ring` + + - ``report_coefficients`` -- whether to return the coefficients of the + :meth:`~sage.rings.valuation.developing_valuation.DevelopingValuation.phi`-adic + expansion or the actual polynomial (default: ``False``, i.e., return + the polynomial) + + OUTPUT: + + A polynomial in the domain of the valuation with reduction ``F``, monic + if ``F`` is monic. + + ALGORITHM: + + Since this is the inverse of :meth:`reduce`, we only have to go backwards + through the algorithm described there. + + EXAMPLES:: + + sage: R. = Qq(4, 10) + sage: S. = R[] + sage: v = GaussValuation(S) + + sage: w = v.augmentation(x^2 + x + u, 1/2) + sage: y = w.residue_ring().gen() + sage: u1 = w.residue_ring().base().gen() + + sage: w.lift(1) + 1 + O(2^10) + sage: w.lift(0) + 0 + sage: w.lift(u1) + (1 + O(2^10))*x + sage: w.reduce(w.lift(y)) == y + True + sage: w.reduce(w.lift(y + u1 + 1)) == y + u1 + 1 + True + + sage: ww = w.augmentation((x^2 + x + u)^2 + 2, 5/3) + sage: y = ww.residue_ring().gen() + sage: u2 = ww.residue_ring().base().gen() + + sage: ww.reduce(ww.lift(y)) == y + True + sage: ww.reduce(ww.lift(1)) == 1 + True + sage: ww.reduce(ww.lift(y + 1)) == y + 1 + True + + A more complicated example:: + + sage: v = GaussValuation(S) + sage: w = v.augmentation(x^2 + x + u, 1) + sage: ww = w.augmentation((x^2 + x + u)^2 + 2*x*(x^2 + x + u) + 4*x, 3) + sage: u = ww.residue_ring().base().gen() + + sage: F = ww.residue_ring()(u); F + u2 + sage: f = ww.lift(F); f + (2^-1 + O(2^9))*x^2 + (2^-1 + O(2^9))*x + u*2^-1 + O(2^9) + sage: F == ww.reduce(f) + True + + """ + F = self.residue_ring().coerce(F) + + from sage.categories.fields import Fields + if not self.domain().base_ring() in Fields(): + raise NotImplementedError("only implemented for polynomial rings over fields") + + if F.is_constant(): + if F.is_zero(): + return self.domain().zero() + if F.is_one(): + return self.domain().one() + + R0 = self._base_valuation.residue_ring() + + # in the last step of reduce, the f_iQ^i are reduced, and evaluated at + # the generator of the residue field + # here, we undo this: + coeffs = [ R0(c if self.psi().degree()==1 else list(c._vector_() if hasattr(c, '_vector_') else c.list())) + for c in F.coefficients(sparse=False) ] + coeffs = [ self._base_valuation.lift(c) for c in coeffs ] + # now the coefficients correspond to the expansion with (f_iQ^i)(Q^{-1} phi)^i + + # now we undo the factors of Q^i (the if else is necessary to handle the case when mu is infinity, i.e., when _Q_reciprocal() is undefined) + coeffs = [ (c if i == 0 else c*self._Q_reciprocal(i)).map_coefficients(_lift_to_maximal_precision) + for i,c in enumerate(coeffs) ] + # reduce the coefficients mod phi; the part that exceeds phi has no effect on the reduction of the coefficient + coeffs = [ next(self.coefficients(c)) for c in coeffs ] + + if report_coefficients: + return coeffs + + RR = self.domain().change_ring(self.domain()) + + tau = self.value_group().index(self._base_valuation.value_group()) + ret = RR(coeffs)(self.phi()**tau) + ret = ret.map_coefficients(_lift_to_maximal_precision) + return ret + + def lift_to_key(self, F, check=True): + r""" + Lift the irreducible polynomial ``F`` to a key polynomial. + + INPUT: + + - ``F`` -- an irreducible non-constant polynomial in the + :meth:`residue_ring` of this valuation + + - ``check`` -- whether or not to check correctness of ``F`` (default: + ``True``) + + OUTPUT: + + A polynomial `f` in the domain of this valuation which is a key + polynomial for this valuation and which, for a suitable equivalence + unit `R`, satifies that the reduction of `Rf` is ``F`` + + ALGORITHM: + + We follow the algorithm described in Theorem 13.1 [Mac1936I]_ which, after + a :meth:`lift` of ``F``, essentially shifts the valuations of all terms + in the `\phi`-adic expansion up and then kills the leading coefficient. + + EXAMPLES:: + + sage: R. = Qq(4, 10) + sage: S. = R[] + sage: v = GaussValuation(S) + + sage: w = v.augmentation(x^2 + x + u, 1/2) + sage: y = w.residue_ring().gen() + sage: f = w.lift_to_key(y + 1); f + (1 + O(2^10))*x^4 + (2 + O(2^11))*x^3 + (1 + u*2 + O(2^10))*x^2 + (u*2 + O(2^11))*x + (u + 1) + u*2 + O(2^10) + sage: w.is_key(f) + True + + A more complicated example:: + + sage: v = GaussValuation(S) + sage: w = v.augmentation(x^2 + x + u, 1) + sage: ww = w.augmentation((x^2 + x + u)^2 + 2*x*(x^2 + x + u) + 4*x, 3) + + sage: u = ww.residue_ring().base().gen() + sage: y = ww.residue_ring().gen() + sage: f = ww.lift_to_key(y^3+y+u) + sage: f.degree() + 12 + sage: ww.is_key(f) + True + + """ + F = self.residue_ring().coerce(F) + + from sage.categories.fields import Fields + if not self.domain().base_ring() in Fields(): + raise NotImplementedError("only implemented for polynomial rings over fields") + + if check: + if self._base_valuation.is_gauss_valuation() and self._mu is infinity: + raise TypeError("there are no keys over this valuation") + if F.is_constant(): + raise ValueError("F must not be constant") + if not F.is_monic(): + raise ValueError("F must be monic") + if not F.is_irreducible(): + raise ValueError("F must be irreducible") + + if F == F.parent().gen(): + return self.phi() + + coefficients = self.lift(F, report_coefficients=True)[:-1] + coefficients = [c*self._Q(F.degree()) for i,c in enumerate(coefficients)] + [self.domain().one()] + if len(coefficients) >= 2: + # In the phi-adic development, the second-highest coefficient could + # spill over into the highest coefficient (which is a constant one) + # so we need to mod it away. + # This can not happen for other coefficients because self._Q() has + # degree at most the degree of phi. + coefficients[-2] %= self.phi() + tau = self.value_group().index(self._base_valuation.value_group()) + vf = self._mu * tau * F.degree() + ret = self.domain().change_ring(self.domain())([c for c in coefficients])(self.phi()**tau) + ret = self.simplify(ret, error=vf, force=True) + ret = ret.map_coefficients(_lift_to_maximal_precision) + assert (ret == self.phi()) == (F == F.parent().gen()) + assert self.is_key(ret) + return ret + + @cached_method + def _Q(self, e): + r""" + Return the polynomial `Q^e` used in the construction to :meth:`reduce` an + element to the :meth:`residue_ring`. + + EXAMPLES:: + + sage: R. = QQ[] + sage: v = GaussValuation(R, QQ.valuation(2)) + sage: w = v.augmentation(x^2 + x + 1, 1) + + sage: w._Q(1) + 2 + + """ + tau = self.value_group().index(self._base_valuation.value_group()) + v = self._mu * tau + return self._pow(self.equivalence_unit(v), e, error=v*e, effective_degree=0) + + @cached_method + def _Q_reciprocal(self, e=1): + r""" + Return the :meth:`equivalence_reciprocal` of the ``e``-th power of + :meth:`_Q`. + + EXAMPLES:: + + sage: R. = QQ[] + sage: v = GaussValuation(R, QQ.valuation(2)) + sage: w = v.augmentation(x^2 + x + 1, 1) + + sage: w._Q_reciprocal() + 1/2 + + """ + if e == 1: + return self.equivalence_reciprocal(self._Q(1), check=False) + + tau = self.value_group().index(self._base_valuation.value_group()) + v = -self._mu * tau + ret = self._pow(self._Q_reciprocal(1), e, error=v*e, effective_degree=0) + + assert self.is_equivalence_unit(ret) + # esentially this checks that the reduction of Q'*phi^tau is the + # generator of the residue field + assert self._base_valuation.reduce(self._Q(e)*ret)(self._residue_field_generator()).is_one() + + return ret + + +class FiniteAugmentedValuation(AugmentedValuation_base, FiniteInductiveValuation): + r""" + A finite augmented valuation, i.e., an augmented valuation which is + discrete, or equivalently an augmented valuation which assigns to its last + key polynomial a finite valuation. + + EXAMPLES:: + + sage: R. = Qq(4, 5) + sage: S. = R[] + sage: v = GaussValuation(S) + sage: w = v.augmentation(x^2 + x + u, 1/2) + + """ + def __init__(self, parent, v, phi, mu): + r""" + EXAMPLES:: + + sage: R. = Qq(4, 5) + sage: S. = R[] + sage: v = GaussValuation(S) + sage: w = v.augmentation(x^2 + x + u, 1/2) + sage: from sage.rings.valuation.augmented_valuation import FiniteAugmentedValuation + sage: isinstance(w, FiniteAugmentedValuation) + True + + """ + AugmentedValuation_base.__init__(self, parent, v, phi, mu) + FiniteInductiveValuation.__init__(self, parent, phi) + + @cached_method + def value_group(self): + r""" + Return the value group of this valuation. + + EXAMPLES:: + + sage: R. = Qq(4, 5) + sage: S. = R[] + sage: v = GaussValuation(S) + + sage: w = v.augmentation(x^2 + x + u, 1/2) + sage: w.value_group() + Additive Abelian Group generated by 1/2 + + sage: ww = w.augmentation((x^2 + x + u)^2 + 2, 5/3) + sage: ww.value_group() + Additive Abelian Group generated by 1/6 + + """ + return self._base_valuation.value_group() + self._mu + + def value_semigroup(self): + r""" + Return the value semigroup of this valuation. + + EXAMPLES:: + + sage: R. = Zq(4, 5) + sage: S. = R[] + sage: v = GaussValuation(S) + + sage: w = v.augmentation(x^2 + x + u, 1/2) + sage: w.value_semigroup() + Additive Abelian Semigroup generated by 1/2 + + sage: ww = w.augmentation((x^2 + x + u)^2 + 2, 5/3) + sage: ww.value_semigroup() + Additive Abelian Semigroup generated by 1/2, 5/3 + + """ + return self._base_valuation.value_semigroup() + self._mu + + def valuations(self, f, coefficients=None, call_error=False): + r""" + Return the valuations of the `f_i\phi^i` in the expansion `f=\sum_i + f_i\phi^i`. + + INPUT: + + - ``f`` -- a polynomial in the domain of this valuation + + - ``coefficients`` -- the coefficients of ``f`` as produced by + :meth:`~sage.rings.valuation.developing_valuation.DevelopingValuation.coefficients` + or ``None`` (default: ``None``); this can be used to speed up the + computation when the expansion of ``f`` is already known from a + previous computation. + + - ``call_error`` -- whether or not to speed up the computation by + assuming that the result is only used to compute the valuation of + ``f`` (default: ``False``) + + OUTPUT: + + An iterator over rational numbers (or infinity) `[v(f_0), v(f_1\phi), \dots]` + + EXAMPLES:: + + sage: R. = Qq(4, 5) + sage: S. = R[] + sage: v = GaussValuation(S) + + sage: w = v.augmentation(x^2 + x + u, 1/2) + sage: list(w.valuations( x^2 + 1 )) + [0, 1/2] + + sage: ww = w.augmentation((x^2 + x + u)^2 + 2, 5/3) + sage: list(ww.valuations( ((x^2 + x + u)^2 + 2)^3 )) + [+Infinity, +Infinity, +Infinity, 5] + + """ + f = self.domain().coerce(f) + + if call_error: + lowest_valuation = infinity + for i,c in enumerate(coefficients or self.coefficients(f)): + if call_error: + if lowest_valuation is not infinity: + v = self._base_valuation.lower_bound(c) + if v is infinity or v >= lowest_valuation: + yield infinity + continue + v = self._base_valuation(c) + if v is infinity: + yield v + else: + ret = v + i*self._mu + if call_error: + if lowest_valuation is infinity or ret < lowest_valuation: + lowest_valuation = ret + yield ret + + def simplify(self, f, error=None, force=False, effective_degree=None, size_heuristic_bound=32, phiadic=False): + r""" + Return a simplified version of ``f``. + + Produce an element which differs from ``f`` by an element of valuation + strictly greater than the valuation of ``f`` (or strictly greater than + ``error`` if set.) + + INPUT: + + - ``f`` -- an element in the domain of this valuation + + - ``error`` -- a rational, infinity, or ``None`` (default: ``None``), + the error allowed to introduce through the simplification + + - ``force`` -- whether or not to simplify ``f`` even if there is + heuristically no change in the coefficient size of ``f`` expected + (default: ``False``) + + - ``effective_degree`` -- when set, assume that coefficients beyond + ``effective_degree`` in the :meth:`~sage.rings.valuation.developing_valuation.DevelopingValuation.phi`-adic development can be + safely dropped (default: ``None``) + + - ``size_heuristic_bound`` -- when ``force`` is not set, the expected + factor by which the coefficients need to shrink to perform an actual + simplification (default: 32) + + - ``phiadic`` -- whether to simplify the coefficients in the + `\phi`-adic expansion recursively. This tends to be slower and + sometimes leads to very huge coefficients in the `x`-adic + expansion (default: ``False``.) + + EXAMPLES:: + + sage: R. = Qq(4, 5) + sage: S. = R[] + sage: v = GaussValuation(S) + sage: w = v.augmentation(x^2 + x + u, 1/2) + sage: w.simplify(x^10/2 + 1, force=True) # not tested - error is incorrect + (u + 1)*2^-1 + O(2^4) + + """ + f = self.domain().coerce(f) + + if effective_degree is not None: + if (QQ(f.degree())/self.phi().degree()).ceil() > effective_degree: + from itertools import islice + f = self.domain().change_ring(self.domain())(list(islice(self.coefficients(f), 0, effective_degree + 1, 1)))(self.phi()) + + if not force and self._relative_size(f) < size_heuristic_bound: + return f + + if error is None: + # if the caller was sure that we should simplify, then we should try to do the best simplification possible + error = self(f) if force else self.upper_bound(f) + + if phiadic: + coefficients = list(self.coefficients(f)) + valuations = list(self.valuations(f, coefficients=coefficients)) + return self.domain().change_ring(self.domain())([ + 0 if valuations[i] > error + else self._base_valuation.simplify(c, error=error-i*self._mu, force=force, phiadic=True) + for (i,c) in enumerate(coefficients)])(self.phi()) + else: + return self._base_valuation.simplify(f, error=error, force=force) + + def lower_bound(self, f): + r""" + Return a lower bound of this valuation at ``f``. + + Use this method to get an approximation of the valuation of ``f`` + when speed is more important than accuracy. + + ALGORITHM: + + The main cost of evaluation is the computation of the + :meth:`~sage.rings.valuation.developing_valuation.DevelopingValuation.coefficients` + of the :meth:`~sage.rings.valuation.developing_valuation.DevelopingValuation.phi`-adic + expansion of ``f`` (which often leads to coefficient bloat.) So unless + :meth:`~sage.rings.valuation.developing_valuation.DevelopingValuation.phi` + is trivial, we fall back to valuation which this valuation augments + since it is guaranteed to be smaller everywhere. + + EXAMPLES:: + + sage: R. = Qq(4, 5) + sage: S. = R[] + sage: v = GaussValuation(S) + sage: w = v.augmentation(x^2 + x + u, 1/2) + sage: w.lower_bound(x^2 + x + u) + 0 + + """ + f = self.domain().coerce(f) + + if self.phi() == self.domain().gen(): + constant_valuation = self.restriction(f.base_ring()) + ret = infinity + for i,c in enumerate(f.coefficients(sparse=False)): + v = constant_valuation.lower_bound(c) + if v is infinity: + continue + v += i*self._mu + if ret is infinity or v < ret: + ret = v + return ret + else: + return self._base_valuation.lower_bound(f) + + def upper_bound(self, f): + r""" + Return an upper bound of this valuation at ``f``. + + Use this method to get an approximation of the valuation of ``f`` + when speed is more important than accuracy. + + ALGORITHM: + + Any entry of :meth:`valuations` serves as an upper bound. However, + computation of the :meth:`~sage.rings.valuation.developing_valuation.DevelopingValuation.phi`-adic + expansion of ``f`` is quite costly. + Therefore, we produce an upper bound on the last entry of + :meth:`valuations`, namely the valuation of the leading coefficient of + ``f`` plus the valuation of the appropriate power of :meth:`~sage.rings.valuation.developing_valuation.DevelopingValuation.phi`. + + EXAMPLES:: + + sage: R. = Qq(4, 5) + sage: S. = R[] + sage: v = GaussValuation(S) + sage: w = v.augmentation(x^2 + x + u, 1/2) + sage: w.upper_bound(x^2 + x + u) + 1/2 + + """ + f = self.domain().coerce(f) + + len_coefficients_bound = (QQ(f.degree()) / self.phi().degree()).ceil() + return self.restriction(f.base_ring())(f.leading_coefficient()) + len_coefficients_bound * self._mu + + +class FinalFiniteAugmentedValuation(FiniteAugmentedValuation, FinalAugmentedValuation): + r""" + An augmented valuation which is discrete, i.e., which assigns a finite + valuation to its last key polynomial, but which can not be further + augmented. + + EXAMPLES:: + + sage: R. = QQ[] + sage: v = GaussValuation(R, valuations.TrivialValuation(QQ)) + sage: w = v.augmentation(x, 1) + + """ + def __init__(self, parent, v, phi, mu): + r""" + TESTS:: + + sage: R. = QQ[] + sage: v = GaussValuation(R, valuations.TrivialValuation(QQ)) + sage: w = v.augmentation(x, 1) + sage: from sage.rings.valuation.augmented_valuation import FinalFiniteAugmentedValuation + sage: isinstance(w, FinalFiniteAugmentedValuation) + True + + """ + FiniteAugmentedValuation.__init__(self, parent, v, phi, mu) + FinalAugmentedValuation.__init__(self, parent, v, phi, mu) + + +class NonFinalFiniteAugmentedValuation(FiniteAugmentedValuation, NonFinalAugmentedValuation): + r""" + An augmented valuation which is discrete, i.e., which assigns a finite + valuation to its last key polynomial, and which can be augmented furter. + + EXAMPLES:: + + sage: R. = QQ[] + sage: v = GaussValuation(R, QQ.valuation(2)) + sage: w = v.augmentation(x, 1) + + """ + def __init__(self, parent, v, phi, mu): + r""" + TESTS:: + + sage: R. = QQ[] + sage: v = GaussValuation(R, QQ.valuation(2)) + sage: w = v.augmentation(x, 1) + sage: from sage.rings.valuation.augmented_valuation import NonFinalFiniteAugmentedValuation + sage: isinstance(w, NonFinalFiniteAugmentedValuation) + True + + """ + FiniteAugmentedValuation.__init__(self, parent, v, phi, mu) + NonFinalAugmentedValuation.__init__(self, parent, v, phi, mu) + + +class InfiniteAugmentedValuation(FinalAugmentedValuation, InfiniteInductiveValuation): + r""" + An augmented valuation which is infinite, i.e., which assigns valuation + infinity to its last key polynomial (and which can therefore not be + augmented further.) + + EXAMPLES:: + + sage: R. = QQ[] + sage: v = GaussValuation(R, QQ.valuation(2)) + sage: w = v.augmentation(x, infinity) + + """ + def __init__(self, parent, v, phi, mu): + r""" + TESTS:: + + sage: R. = QQ[] + sage: v = GaussValuation(R, QQ.valuation(2)) + sage: w = v.augmentation(x, infinity) + sage: from sage.rings.valuation.augmented_valuation import InfiniteAugmentedValuation + sage: isinstance(w, InfiniteAugmentedValuation) + True + + """ + FinalAugmentedValuation.__init__(self, parent, v, phi, mu) + InfiniteInductiveValuation.__init__(self, parent, phi) + + @cached_method + def value_group(self): + r""" + Return the value group of this valuation. + + EXAMPLES:: + + sage: R. = Qq(4, 5) + sage: S. = R[] + sage: v = GaussValuation(S) + sage: w = v.augmentation(x, infinity) + sage: w.value_group() + Additive Abelian Group generated by 1 + + """ + return self._base_valuation.value_group() + + @cached_method + def value_semigroup(self): + r""" + Return the value semigroup of this valuation. + + EXAMPLES:: + + sage: R. = Zq(4, 5) + sage: S. = R[] + sage: v = GaussValuation(S) + sage: w = v.augmentation(x, infinity) + sage: w.value_semigroup() + Additive Abelian Semigroup generated by 1 + + """ + return self._base_valuation.value_semigroup() + + def valuations(self, f, coefficients=None, call_error=False): + r""" + Return the valuations of the `f_i\phi^i` in the expansion `f=\sum_i + f_i\phi^i`. + + INPUT: + + - ``f`` -- a polynomial in the domain of this valuation + + - ``coefficients`` -- the coefficients of ``f`` as produced by + :meth:`~sage.rings.valuation.developing_valuation.DevelopingValuation.coefficients` + or ``None`` (default: ``None``); this can be used to speed up the + computation when the expansion of ``f`` is already known from a + previous computation. + + - ``call_error`` -- whether or not to speed up the computation by + assuming that the result is only used to compute the valuation of + ``f`` (default: ``False``) + + OUTPUT: + + An iterator over rational numbers (or infinity) `[v(f_0), v(f_1\phi), \dots]` + + EXAMPLES:: + + sage: R. = Qq(4, 5) + sage: S. = R[] + sage: v = GaussValuation(S) + sage: w = v.augmentation(x, infinity) + sage: list(w.valuations(x^2 + 1)) + [0, +Infinity, +Infinity] + + """ + f = self.domain().coerce(f) + + num_infty_coefficients = f.degree() // self.phi().degree() + if coefficients is not None: + constant_coefficient = coefficients[0] + else: + constant_coefficient = next(self.coefficients(f)) + yield self._base_valuation(constant_coefficient) + for i in range(num_infty_coefficients): + yield infinity + + def simplify(self, f, error=None, force=False, effective_degree=None): + r""" + Return a simplified version of ``f``. + + Produce an element which differs from ``f`` by an element of valuation + strictly greater than the valuation of ``f`` (or strictly greater than + ``error`` if set.) + + INPUT: + + - ``f`` -- an element in the domain of this valuation + + - ``error`` -- a rational, infinity, or ``None`` (default: ``None``), + the error allowed to introduce through the simplification + + - ``force`` -- whether or not to simplify ``f`` even if there is + heuristically no change in the coefficient size of ``f`` expected + (default: ``False``) + + - ``effective_degree`` -- ignored; for compatibility with other + ``simplify`` methods + + EXAMPLES:: + + sage: R. = Qq(4, 5) + sage: S. = R[] + sage: v = GaussValuation(S) + sage: w = v.augmentation(x^2 + x + u, infinity) + sage: w.simplify(x^10/2 + 1, force=True) # not tested - error incorrect + (u + 1)*2^-1 + O(2^4) + + """ + f = self.domain().coerce(f) + + if error is None: + error = self.upper_bound(f) + + if error is infinity: + return f + + return self.domain()(self._base_valuation.simplify(next(self.coefficients(f)), error, force=force)) + + def lower_bound(self, f): + r""" + Return a lower bound of this valuation at ``f``. + + Use this method to get an approximation of the valuation of ``f`` + when speed is more important than accuracy. + + EXAMPLES:: + + sage: R. = Qq(4, 5) + sage: S. = R[] + sage: v = GaussValuation(S) + sage: w = v.augmentation(x^2 + x + u, infinity) + sage: w.lower_bound(x^2 + x + u) + +Infinity + + """ + return self._base_valuation.lower_bound(next(self.coefficients(f))) + + def upper_bound(self, f): + r""" + Return an upper bound of this valuation at ``f``. + + Use this method to get an approximation of the valuation of ``f`` + when speed is more important than accuracy. + + EXAMPLES:: + + sage: R. = Qq(4, 5) + sage: S. = R[] + sage: v = GaussValuation(S) + sage: w = v.augmentation(x^2 + x + u, infinity) + sage: w.upper_bound(x^2 + x + u) + +Infinity + + """ + return self._base_valuation.upper_bound(next(self.coefficients(f))) diff --git a/src/sage/rings/valuation/developing_valuation.py b/src/sage/rings/valuation/developing_valuation.py new file mode 100644 index 00000000000..982f696a8ab --- /dev/null +++ b/src/sage/rings/valuation/developing_valuation.py @@ -0,0 +1,343 @@ +# -*- coding: utf-8 -*- +r""" +Valuations on polynomial rings based on `\phi`-adic expansions + +This file implements a base class for discrete valuations on polynomial rings, +defined by a `\phi`-adic expansion. + +AUTHORS: + +- Julian Rüth (2013-04-15): initial version + +EXAMPLES: + +The :mod:`Gauss valuation ` is a simple example of a valuation that relies on +`\phi`-adic expansions:: + + sage: R. = QQ[] + sage: v = GaussValuation(R, QQ.valuation(2)) + +In this case, `\phi = x`, so the expansion simply lists the coefficients of the +polynomial:: + + sage: f = x^2 + 2*x + 2 + sage: list(v.coefficients(f)) + [2, 2, 1] + +Often only the first few coefficients are necessary in computations, so for +performance reasons, coefficients are computed lazily:: + + sage: v.coefficients(f) + + +Another example of a :class:`DevelopingValuation` is an :mod:`augmented +valuation `:: + + sage: w = v.augmentation(x^2 + x + 1, 3) + +Here, the expansion lists the remainders of repeated division by `x^2 + x + 1`:: + + sage: list(w.coefficients(f)) + [x + 1, 1] + +""" +#***************************************************************************** +# Copyright (C) 2013-2017 Julian Rüth +# +# Distributed under the terms of the GNU General Public License (GPL) +# as published by the Free Software Foundation; either version 2 of +# the License, or (at your option) any later version. +# http://www.gnu.org/licenses/ +#***************************************************************************** +from valuation import DiscretePseudoValuation +from sage.misc.abstract_method import abstract_method + +from sage.misc.cachefunc import cached_method + +class DevelopingValuation(DiscretePseudoValuation): + r""" + Abstract base class for a discrete valuation of polynomials defined over + the polynomial ring ``domain`` by the `\phi`-adic development. + + EXAMPLES:: + + sage: R. = QQ[] + sage: v = GaussValuation(R, QQ.valuation(7)) + + TESTS:: + + sage: TestSuite(v).run() # long time + + """ + def __init__(self, parent, phi): + r""" + TESTS:: + + sage: R. = QQ[] + sage: v = GaussValuation(R, QQ.valuation(7)) + sage: from sage.rings.valuation.developing_valuation import DevelopingValuation + sage: isinstance(v, DevelopingValuation) + True + + """ + DiscretePseudoValuation.__init__(self, parent) + + domain = parent.domain() + from sage.rings.polynomial.polynomial_ring import is_PolynomialRing + if not is_PolynomialRing(domain) or not domain.ngens() == 1: + raise TypeError("domain must be a univariate polynomial ring but %r is not"%(domain,)) + + phi = domain.coerce(phi) + if phi.is_constant() or not phi.is_monic(): + raise ValueError("phi must be a monic non-constant polynomial but %r is not"%(phi,)) + + self._phi = phi + + def phi(self): + r""" + Return the polynomial `\phi`, the key polynomial of this valuation. + + EXAMPLES:: + + sage: R = Zp(2,5) + sage: S. = R[] + sage: v = GaussValuation(S) + sage: v.phi() + (1 + O(2^5))*x + + """ + return self._phi + + def effective_degree(self, f, valuations=None): + r""" + Return the effective degree of ``f`` with respect to this valuation. + + The effective degree of `f` is the largest `i` such that the valuation + of `f` and the valuation of `f_i\phi^i` in the development `f=\sum_j + f_j\phi^j` coincide (see [Mac1936II]_ p.497.) + + INPUT: + + - ``f`` -- a non-zero polynomial in the domain of this valuation + + EXAMPLES:: + + sage: R = Zp(2,5) + sage: S. = R[] + sage: v = GaussValuation(S) + sage: v.effective_degree(x) + 1 + sage: v.effective_degree(2*x + 1) + 0 + + """ + f = self.domain().coerce(f) + + if f.is_zero(): + raise ValueError("the effective degree is only defined for non-zero polynomials") + + if valuations is None: + valuations = list(self.valuations(f)) + v = min(valuations) + return [i for i,w in enumerate(valuations) if w == v][-1] + + @cached_method + def _pow(self, f, e, error, effective_degree): + r""" + Return `f^e`. + + This method does not compute the exact value of `f^e` but only an + element that differs from the correct result by an error with valuation + at least ``error``. The output is assumed to have at most + ``effective_degree``. If the effective degree is higher than + ``effective_degree``, then the result may not be correct. + + EXAMPLES:: + + sage: R = Zp(2,5) + sage: S. = R[] + sage: v = GaussValuation(S) + sage: v._pow(2*x + 1, 10, effective_degree=0, error=5) + (1 + O(2^5)) + + """ + if e == 0: + return self.domain().one() + if e == 1: + return self.simplify(f, error=error) + if e % 2 == 0: + return self._pow(self.simplify(f*f, error=error*2/e, effective_degree=effective_degree*2/e), + e//2, error=error, effective_degree=effective_degree) + else: + return self.simplify(f*self._pow(f, e-1, error=error*(e-1)/e, effective_degree=effective_degree*(e-1)/e), + error=error, effective_degree=effective_degree) + + def coefficients(self, f): + r""" + Return the `\phi`-adic expansion of ``f``. + + INPUT: + + - ``f`` -- a monic polynomial in the domain of this valuation + + OUTPUT: + + An iterator `f_0, f_1, \dots, f_n` of polynomials in the domain of this + valuation such that `f=\sum_i f_i\phi^i` + + EXAMPLES:: + + sage: R = Qp(2,5) + sage: S. = R[] + sage: v = GaussValuation(S) + sage: f = x^2 + 2*x + 3 + sage: list(v.coefficients(f)) # note that these constants are in the polynomial ring + [(1 + 2 + O(2^5)), (2 + O(2^6)), (1 + O(2^5))] + sage: v = v.augmentation( x^2 + x + 1, 1) + sage: list(v.coefficients(f)) + [(1 + O(2^5))*x + (2 + O(2^5)), (1 + O(2^5))] + + """ + domain = self.domain() + f = domain.coerce(f) + + if f.degree() < self.phi().degree(): + yield f + elif self.phi().degree() == 1: + if self.phi() != domain.gen() or not domain.is_exact(): + f = f(domain.gen() - self.phi()[0]) + for c in f.coefficients(sparse=False): + yield domain(c) + else: + while f.degree() >= 0: + f,r = self._quo_rem(f) + yield r + + def _quo_rem(self, f): + r""" + Return the quotient and remainder of ``f`` divided by the key + polynomial :meth:`phi`. + + EXAMPLES:: + + sage: S. = QQ[] + sage: v = GaussValuation(S, QQ.valuation(2)) + sage: v._quo_rem(x^2 + 1) + (x, 1) + + """ + return f.quo_rem(self.phi()) + + def newton_polygon(self, f, valuations=None): + r""" + Return the newton polygon of the `\phi`-adic development of ``f``. + + INPUT: + + - ``f`` -- a polynomial in the domain of this valuation + + EXAMPLES:: + + sage: R = Qp(2,5) + sage: S. = R[] + sage: v = GaussValuation(S) + sage: f = x^2 + 2*x + 3 + sage: v.newton_polygon(f) + Finite Newton polygon with 2 vertices: (0, 0), (2, 0) + + sage: v = v.augmentation( x^2 + x + 1, 1) + sage: v.newton_polygon(f) + Finite Newton polygon with 2 vertices: (0, 0), (1, 1) + sage: v.newton_polygon( f * v.phi()^3 ) + Finite Newton polygon with 2 vertices: (3, 3), (4, 4) + + """ + f = self.domain().coerce(f) + + from sage.geometry.newton_polygon import NewtonPolygon + if valuations is None: + valuations = self.valuations(f) + return NewtonPolygon(list(enumerate(valuations))) + + def _call_(self, f): + r""" + Evaluate this valuation at ``f``. + + INPUT: + + - ``f`` -- a polynomial in the domain of this valuation + + EXAMPLES:: + + sage: R = Qp(2,5) + sage: S. = R[] + sage: v = GaussValuation(S) + sage: f = x^2 + 2*x + 3 + sage: v(f) + 0 + + sage: v = v.augmentation( x^2 + x + 1, 1) + sage: v(f) + 0 + sage: v(f * v.phi()^3 ) + 3 + sage: v(S.zero()) + +Infinity + + """ + f = self.domain().coerce(f) + + from sage.rings.all import infinity + if f.is_zero(): + return infinity + + ret = infinity + for v in self.valuations(f, call_error=True): + if ret is infinity or (v is not infinity and v < ret): + # "ret is infinity" is redundant but much faster than < when ret is infinite + ret = v + return ret + + @abstract_method + def valuations(self, f): + r""" + Return the valuations of the `f_i\phi^i` in the expansion `f=\sum f_i\phi^i`. + + INPUT: + + - ``f`` -- a polynomial in the domain of this valuation + + OUTPUT: + + A list, each entry a rational numbers or infinity, the valuations of + `f_0, f_1\phi, \dots` + + EXAMPLES:: + + sage: R = Qp(2,5) + sage: S. = R[] + sage: v = GaussValuation(S, R.valuation()) + sage: f = x^2 + 2*x + 16 + sage: list(v.valuations(f)) + [4, 1, 0] + + """ + + def _test_effective_degree(self, **options): + r""" + Test the correctness of :meth:`effective_degree`. + + EXAMPLES:: + + sage: R = Zp(2,5) + sage: S. = R[] + sage: v = GaussValuation(S) + sage: v._test_effective_degree() + + """ + tester = self._tester(**options) + S = tester.some_elements(self.domain().base_ring().some_elements()) + for x in S: + if x == 0: + continue + tester.assertEqual(self.effective_degree(x), 0) diff --git a/src/sage/rings/valuation/gauss_valuation.py b/src/sage/rings/valuation/gauss_valuation.py new file mode 100644 index 00000000000..935361460e5 --- /dev/null +++ b/src/sage/rings/valuation/gauss_valuation.py @@ -0,0 +1,833 @@ +# -*- coding: utf-8 -*- +""" +Gauss valuations on polynomial rings + +This file implements Gauss valuations for polynomial rings, i.e. discrete +valuations which assign to a polynomial the minimal valuation of its +coefficients. + +AUTHORS: + +- Julian Rüth (2013-04-15): initial version + +EXAMPLES: + +A Gauss valuation maps a polynomial to the minimal valuation of any of its +coefficients:: + + sage: R. = QQ[] + sage: v0 = QQ.valuation(2) + sage: v = GaussValuation(R, v0); v + Gauss valuation induced by 2-adic valuation + sage: v(2*x + 2) + 1 + +Gauss valuations can also be defined iteratively based on valuations over +polynomial rings:: + + sage: v = v.augmentation(x, 1/4); v + [ Gauss valuation induced by 2-adic valuation, v(x) = 1/4 ] + sage: v = v.augmentation(x^4+2*x^3+2*x^2+2*x+2, 4/3); v + [ Gauss valuation induced by 2-adic valuation, v(x) = 1/4, v(x^4 + 2*x^3 + 2*x^2 + 2*x + 2) = 4/3 ] + sage: S. = R[] + sage: w = GaussValuation(S, v); w + Gauss valuation induced by [ Gauss valuation induced by 2-adic valuation, v(x) = 1/4, v(x^4 + 2*x^3 + 2*x^2 + 2*x + 2) = 4/3 ] + sage: w(2*T + 1) + 0 + +""" +#***************************************************************************** +# Copyright (C) 2013-2017 Julian Rüth +# +# Distributed under the terms of the GNU General Public License (GPL) +# as published by the Free Software Foundation; either version 2 of +# the License, or (at your option) any later version. +# http://www.gnu.org/licenses/ +#***************************************************************************** +from inductive_valuation import NonFinalInductiveValuation + +from sage.misc.cachefunc import cached_method +from sage.structure.unique_representation import UniqueRepresentation +from sage.structure.factory import UniqueFactory + +class GaussValuationFactory(UniqueFactory): + r""" + Create a Gauss valuation on ``domain``. + + INPUT: + + - ``domain`` -- a univariate polynomial ring + + - ``v`` -- a valuation on the base ring of ``domain``, the underlying + valuation on the constants of the polynomial ring (if unspecified take + the natural valuation on the valued ring ``domain``.) + + EXAMPLES: + + The Gauss valuation is the minimum of the valuation of the coefficients:: + + sage: v = QQ.valuation(2) + sage: R. = QQ[] + sage: w = GaussValuation(R, v) + sage: w(2) + 1 + sage: w(x) + 0 + sage: w(x + 2) + 0 + + """ + def create_key(self, domain, v = None): + r""" + Normalize and check the parameters to create a Gauss valuation. + + TESTS:: + + sage: v = QQ.valuation(2) + sage: R. = ZZ[] + sage: GaussValuation.create_key(R, v) + Traceback (most recent call last): + ... + ValueError: the domain of v must be the base ring of domain but 2-adic valuation is not defined over Integer Ring but over Rational Field + + """ + from sage.rings.polynomial.polynomial_ring import is_PolynomialRing + if not is_PolynomialRing(domain): + raise TypeError("GaussValuations can only be created over polynomial rings but %r is not a polynomial ring"%(domain,)) + if not domain.ngens() == 1: + raise NotImplementedError("domain must be univariate but %r is not univariate"%(domain,)) + + if v is None: + v = domain.base_ring().valuation() + + if not v.domain() is domain.base_ring(): + raise ValueError("the domain of v must be the base ring of domain but %r is not defined over %r but over %r"%(v, domain.base_ring(), v.domain())) + if not v.is_discrete_valuation(): + raise ValueError("v must be a discrete valuation but %r is not"%(v,)) + + return (domain, v) + + def create_object(self, version, key, **extra_args): + r""" + Create a Gauss valuation from normalized parameters. + + TESTS:: + + sage: v = QQ.valuation(2) + sage: R. = QQ[] + sage: GaussValuation.create_object(0, (R, v)) + Gauss valuation induced by 2-adic valuation + + """ + domain, v = key + from sage.rings.valuation.valuation_space import DiscretePseudoValuationSpace + parent = DiscretePseudoValuationSpace(domain) + return parent.__make_element_class__(GaussValuation_generic)(parent, v) + +GaussValuation = GaussValuationFactory("sage.rings.valuation.gauss_valuation.GaussValuation") + +class GaussValuation_generic(NonFinalInductiveValuation): + """ + A Gauss valuation on a polynomial ring ``domain``. + + INPUT: + + - ``domain`` -- a univariate polynomial ring over a valued ring `R` + + - ``v`` -- a discrete valuation on `R` + + EXAMPLES:: + + sage: R = Zp(3,5) + sage: S. = R[] + sage: v0 = R.valuation() + sage: v = GaussValuation(S, v0); v + Gauss valuation induced by 3-adic valuation + + sage: S. = QQ[] + sage: v = GaussValuation(S, QQ.valuation(5)); v + Gauss valuation induced by 5-adic valuation + + TESTS:: + + sage: TestSuite(v).run() # long time + + """ + def __init__(self, parent, v): + """ + TESTS:: + + sage: from sage.rings.valuation.gauss_valuation import GaussValuation_generic + sage: S. = QQ[] + sage: v = GaussValuation(S, QQ.valuation(5)) + sage: isinstance(v, GaussValuation_generic) + True + + """ + NonFinalInductiveValuation.__init__(self, parent, parent.domain().gen()) + + self._base_valuation = v + + def value_group(self): + """ + Return the value group of this valuation. + + EXAMPLES:: + + sage: S. = QQ[] + sage: v = GaussValuation(S, QQ.valuation(5)) + sage: v.value_group() + Additive Abelian Group generated by 1 + + """ + return self._base_valuation.value_group() + + def value_semigroup(self): + r""" + Return the value semigroup of this valuation. + + EXAMPLES:: + + sage: S. = QQ[] + sage: v = GaussValuation(S, QQ.valuation(5)) + sage: v.value_semigroup() + Additive Abelian Semigroup generated by -1, 1 + + """ + return self._base_valuation.value_semigroup() + + def _repr_(self): + """ + Return a printable representation of this valuation. + + EXAMPLES:: + + sage: S. = QQ[] + sage: v = GaussValuation(S, QQ.valuation(5)) + sage: v # indirect doctest + Gauss valuation induced by 5-adic valuation + + """ + return "Gauss valuation induced by %r"%self._base_valuation + + @cached_method + def uniformizer(self): + """ + Return a uniformizer of this valuation, i.e., a uniformizer of the + valuation of the base ring. + + EXAMPLES:: + + sage: S. = QQ[] + sage: v = GaussValuation(S, QQ.valuation(5)) + sage: v.uniformizer() + 5 + sage: v.uniformizer().parent() is S + True + + """ + return self.domain()(self._base_valuation.uniformizer()) + + def valuations(self, f, coefficients=None, call_error=False): + """ + Return the valuations of the `f_i\phi^i` in the expansion `f=\sum f_i\phi^i`. + + INPUT: + + - ``f`` -- a polynomial in the domain of this valuation + + - ``coefficients`` -- the coefficients of ``f`` as produced by + :meth:`~sage.rings.valuation.developing_valuation.DevelopingValuation.coefficients` or ``None`` (default: ``None``); this can be + used to speed up the computation when the expansion of ``f`` is + already known from a previous computation. + + - ``call_error`` -- whether or not to speed up the computation by + assuming that the result is only used to compute the valuation of + ``f`` (default: ``False``) + + OUTPUT: + + A list, each entry a rational numbers or infinity, the valuations of `f_0, f_1\phi, \dots` + + EXAMPLES:: + + sage: R = ZZ + sage: S. = R[] + sage: v = GaussValuation(S, R.valuation(2)) + sage: f = x^2 + 2*x + 16 + sage: list(v.valuations(f)) + [4, 1, 0] + + """ + f = self.domain().coerce(f) + + if f.is_constant(): + yield self._base_valuation(f[0]) + return + + from sage.rings.all import infinity, QQ + if f == self.domain().gen(): + yield infinity + yield QQ(0) + return + + if call_error: + lowest_valuation = infinity + for c in coefficients or f.coefficients(sparse=False): + if call_error: + if lowest_valuation is not infinity: + v = self._base_valuation.lower_bound(c) + if v is infinity or v >= lowest_valuation: + yield infinity + continue + ret = self._base_valuation(c) + if call_error: + if ret is not infinity and (lowest_valuation is infinity or ret < lowest_valuation): + lowest_valuation = ret + yield ret + + @cached_method + def residue_ring(self): + """ + Return the residue ring of this valuation, i.e., the elements of + valuation zero module the elements of positive valuation. + + EXAMPLES:: + + sage: S. = Qp(2,5)[] + sage: v = GaussValuation(S) + sage: v.residue_ring() + Univariate Polynomial Ring in x over Finite Field of size 2 (using ...) + + """ + return self.domain().change_ring(self._base_valuation.residue_ring()) + + def reduce(self, f, check=True, degree_bound=None, coefficients=None, valuations=None): + """ + Return the reduction of ``f`` modulo this valuation. + + INPUT: + + - ``f`` -- an integral element of the domain of this valuation + + - ``check`` -- whether or not to check whether ``f`` has non-negative + valuation (default: ``True``) + + - ``degree_bound`` -- an a-priori known bound on the degree of the + result which can speed up the computation (default: not set) + + - ``coefficients`` -- the coefficients of ``f`` as produced by + :meth:`~sage.rings.valuation.developing_valuation.DevelopingValuation.coefficients` or ``None`` (default: ``None``); ignored + + - ``valuations`` -- the valuations of ``coefficients`` or ``None`` + (default: ``None``); ignored + + OUTPUT: + + A polynomial in the :meth:`residue_ring` of this valuation. + + EXAMPLES:: + + sage: S. = Qp(2,5)[] + sage: v = GaussValuation(S) + sage: f = x^2 + 2*x + 16 + sage: v.reduce(f) + x^2 + sage: v.reduce(f).parent() is v.residue_ring() + True + + The reduction is only defined for integral elements:: + + sage: f = x^2/2 + sage: v.reduce(f) + Traceback (most recent call last): + ... + ValueError: reduction not defined for non-integral elements and (2^-1 + O(2^4))*x^2 is not integral over Gauss valuation induced by 2-adic valuation + + .. SEEALSO:: + + :meth:`lift` + + """ + f = self.domain().coerce(f) + + if degree_bound is not None: + f = f.truncate(degree_bound + 1) + + try: + return f.map_coefficients(self._base_valuation.reduce, self._base_valuation.residue_field()) + except Exception: + if check and not all([v>=0 for v in self.valuations(f)]): + raise ValueError("reduction not defined for non-integral elements and %r is not integral over %r"%(f, self)) + raise + + def lift(self, F): + """ + Return a lift of ``F``. + + INPUT: + + - ``F`` -- a polynomial over the :meth:`residue_ring` of this valuation + + OUTPUT: + + a (possibly non-monic) polynomial in the domain of this valuation which + reduces to ``F`` + + EXAMPLES:: + + sage: S. = Qp(3,5)[] + sage: v = GaussValuation(S) + sage: f = x^2 + 2*x + 16 + sage: F = v.reduce(f); F + x^2 + 2*x + 1 + sage: g = v.lift(F); g + (1 + O(3^5))*x^2 + (2 + O(3^5))*x + (1 + O(3^5)) + sage: v.is_equivalent(f,g) + True + sage: g.parent() is v.domain() + True + + .. SEEALSO:: + + :meth:`reduce` + + """ + F = self.residue_ring().coerce(F) + + return F.map_coefficients(lambda c:self._base_valuation.lift(c), self._base_valuation.domain()) + + def lift_to_key(self, F): + """ + Lift the irreducible polynomial ``F`` from the :meth:`residue_ring` to + a key polynomial over this valuation. + + INPUT: + + - ``F`` -- an irreducible non-constant monic polynomial in + :meth:`residue_ring` of this valuation + + OUTPUT: + + A polynomial `f` in the domain of this valuation which is a key + polynomial for this valuation and which, for a suitable equivalence + unit `R`, satifies that the reduction of `Rf` is ``F`` + + EXAMPLES:: + + sage: R. = QQ + sage: S. = R[] + sage: v = GaussValuation(S, QQ.valuation(2)) + sage: y = v.residue_ring().gen() + sage: f = v.lift_to_key(y^2 + y + 1); f + x^2 + x + 1 + + """ + F = self.residue_ring().coerce(F) + + if F.is_constant(): + raise ValueError("F must not be constant but %r is constant"%(F,)) + if not F.is_monic(): + raise ValueError("F must be monic but %r is not monic"%(F,)) + if not F.is_irreducible(): + raise ValueError("F must be irreducible but %r factors"%(F,)) + + return self.lift(F) + + @cached_method + def equivalence_unit(self, s, reciprocal=False): + """ + Return an equivalence unit of valuation ``s``. + + INPUT: + + - ``s`` -- an element of the :meth:`value_group` + + - ``reciprocal`` -- a boolean (default: ``False``); whether or not to + return the equivalence unit as the :meth:`~sage.rings.valuation.inductive_valuation.InductiveValuation.equivalence_reciprocal` of + the equivalence unit of valuation ``-s`` + + EXAMPLES:: + + sage: S. = Qp(3,5)[] + sage: v = GaussValuation(S) + sage: v.equivalence_unit(2) + (3^2 + O(3^7)) + sage: v.equivalence_unit(-2) + (3^-2 + O(3^3)) + + """ + if reciprocal: + return self.equivalence_reciprocal(self.equivalence_unit(-s)) + + ret = self._base_valuation.element_with_valuation(s) + return self.domain()(ret) + + def element_with_valuation(self, s): + r""" + Return a polynomial of minimal degree with valuation ``s``. + + EXAMPLES:: + + sage: R. = QQ[] + sage: v = GaussValuation(R, QQ.valuation(2)) + sage: v.element_with_valuation(-2) + 1/4 + + """ + return self.equivalence_unit(s) + + def E(self): + """ + Return the ramification index of this valuation over its underlying + Gauss valuation, i.e., 1. + + EXAMPLES:: + + sage: R. = Qq(4,5) + sage: S. = R[] + sage: v = GaussValuation(S) + sage: v.E() + 1 + + """ + from sage.rings.all import ZZ + return ZZ.one() + + def F(self): + """ + Return the degree of the residue field extension of this valuation + over the Gauss valuation, i.e., 1. + + EXAMPLES:: + + sage: R. = Qq(4,5) + sage: S. = R[] + sage: v = GaussValuation(S) + sage: v.F() + 1 + + """ + from sage.rings.all import ZZ + return ZZ.one() + + def change_domain(self, ring): + r""" + Return this valuation as a valuation over ``ring``. + + EXAMPLES:: + + sage: v = ZZ.valuation(2) + sage: R. = ZZ[] + sage: w = GaussValuation(R, v) + sage: w.change_domain(QQ['x']) + Gauss valuation induced by 2-adic valuation + + """ + from sage.rings.polynomial.polynomial_ring import is_PolynomialRing + if is_PolynomialRing(ring) and ring.ngens() == 1: + base_valuation = self._base_valuation.change_domain(ring.base_ring()) + return GaussValuation(self.domain().change_ring(ring.base_ring()), base_valuation) + return super(GaussValuation_generic, self).change_domain(ring) + + def extensions(self, ring): + r""" + Return the extensions of this valuation to ``ring``. + + EXAMPLES:: + + sage: v = ZZ.valuation(2) + sage: R. = ZZ[] + sage: w = GaussValuation(R, v) + sage: w.extensions(GaussianIntegers()['x']) + [Gauss valuation induced by 2-adic valuation] + + """ + from sage.rings.polynomial.polynomial_ring import is_PolynomialRing + if is_PolynomialRing(ring) and ring.ngens() == 1: + if self.domain().is_subring(ring): + return [GaussValuation(ring, w) for w in self._base_valuation.extensions(ring.base_ring())] + return super(GaussValuation_generic, self).extensions(ring) + + def restriction(self, ring): + r""" + Return the restriction of this valuation to ``ring``. + + EXAMPLES:: + + sage: v = ZZ.valuation(2) + sage: R. = ZZ[] + sage: w = GaussValuation(R, v) + sage: w.restriction(ZZ) + 2-adic valuation + + """ + if ring.is_subring(self.domain().base_ring()): + return self._base_valuation.restriction(ring) + from sage.rings.polynomial.polynomial_ring import is_PolynomialRing + if is_PolynomialRing(ring) and ring.ngens() == 1: + if ring.base().is_subring(self.domain().base()): + return GaussValuation(ring, self._base_valuation.restriction(ring.base())) + return super(GaussValuation_generic, self).restriction(ring) + + def is_gauss_valuation(self): + r""" + Return whether this valuation is a Gauss valuation. + + EXAMPLES:: + + sage: R. = Qq(4,5) + sage: S. = R[] + sage: v = GaussValuation(S) + sage: v.is_gauss_valuation() + True + + """ + return True + + def augmentation_chain(self): + r""" + Return a list with the chain of augmentations down to the underlying + Gauss valuation. + + EXAMPLES:: + + sage: R. = Qq(4,5) + sage: S. = R[] + sage: v = GaussValuation(S) + sage: v.augmentation_chain() + [Gauss valuation induced by 2-adic valuation] + + """ + return [self] + + def is_trivial(self): + r""" + Return whether this is a trivial valuation (sending everything but zero + to zero.) + + EXAMPLES:: + + sage: R. = QQ[] + sage: v = GaussValuation(R, valuations.TrivialValuation(QQ)) + sage: v.is_trivial() + True + + """ + return self._base_valuation.is_trivial() + + def monic_integral_model(self, G): + r""" + Return a monic integral irreducible polynomial which defines the same + extension of the base ring of the domain as the irreducible polynomial + ``G`` together with maps between the old and the new polynomial. + + EXAMPLES:: + + sage: R. = Qp(2, 5)[] + sage: v = GaussValuation(R) + sage: v.monic_integral_model(5*x^2 + 1/2*x + 1/4) + (Ring endomorphism of Univariate Polynomial Ring in x over 2-adic Field with capped relative precision 5 + Defn: (1 + O(2^5))*x |--> (2^-1 + O(2^4))*x, + Ring endomorphism of Univariate Polynomial Ring in x over 2-adic Field with capped relative precision 5 + Defn: (1 + O(2^5))*x |--> (2 + O(2^6))*x, + (1 + O(2^5))*x^2 + (1 + 2^2 + 2^3 + O(2^5))*x + (1 + 2^2 + 2^3 + O(2^5))) + + """ + if not G.is_monic(): + # this might fail if the base ring is not a field + G = G / G.leading_coefficient() + + x = G.parent().gen() + u = self._base_valuation.uniformizer() + + factor = 1 + substitution = x + H = G + while self(H) < 0: + # this might fail if the base ring is not a field + factor *= u + substitution = x/factor + H = G(substitution) * (factor ** G.degree()) + + assert H.is_monic() + return H.parent().hom(substitution, G.parent()), G.parent().hom(x / substitution[1], H.parent()), H + + def _ge_(self, other): + r""" + Return whether this valuation is greater than or equal to ``other`` + everywhere. + + EXAMPLES:: + + sage: R. = QQ[] + sage: v = GaussValuation(R, QQ.valuation(2)) + sage: w = GaussValuation(R, QQ.valuation(3)) + sage: v >= w + False + sage: w >= v + False + + """ + if isinstance(other, GaussValuation_generic): + return self._base_valuation >= other._base_valuation + from augmented_valuation import AugmentedValuation_base + if isinstance(other, AugmentedValuation_base): + return False + if other.is_trivial(): + return other.is_discrete_valuation() + return super(GaussValuation_generic, self)._ge_(other) + + def scale(self, scalar): + r""" + Return this valuation scaled by ``scalar``. + + EXAMPLES:: + + sage: R. = QQ[] + sage: v = GaussValuation(R, QQ.valuation(2)) + sage: 3*v # indirect doctest + Gauss valuation induced by 3 * 2-adic valuation + + """ + from sage.rings.all import QQ + if scalar in QQ and scalar > 0 and scalar != 1: + return GaussValuation(self.domain(), self._base_valuation.scale(scalar)) + return super(GaussValuation_generic, self).scale(scalar) + + def _relative_size(self, f): + r""" + Return an estimate on the coefficient size of ``f``. + + The number returned is an estimate on the factor between the number of + Bits used by ``f`` and the minimal number of bits used by an element + Congruent to ``f``. + + This is used by :meth:`simplify` to decide whether simplification of + Coefficients is going to lead to a significant shrinking of the + Coefficients of ``f``. + + EXAMPLES:: + + sage: R. = QQ[] + sage: v = GaussValuation(R, QQ.valuation(2)) + sage: v._relative_size(x + 1024) + 6 + + For performance reasons, only the constant coefficient is considered. + (In common appplications, the constant coefficient shows the most + critical coefficient growth):: + + sage: v._relative_size(1024*x + 1) + 1 + + """ + return self._base_valuation._relative_size(f[0]) + + def simplify(self, f, error=None, force=False, size_heuristic_bound=32, effective_degree=None, phiadic=True): + r""" + Return a simplified version of ``f``. + + Produce an element which differs from ``f`` by an element of valuation + strictly greater than the valuation of ``f`` (or strictly greater than + ``error`` if set.) + + INPUT: + + - ``f`` -- an element in the domain of this valuation + + - ``error`` -- a rational, infinity, or ``None`` (default: ``None``), + the error allowed to introduce through the simplification + + - ``force`` -- whether or not to simplify ``f`` even if there is + heuristically no change in the coefficient size of ``f`` expected + (default: ``False``) + + - ``effective_degree`` -- when set, assume that coefficients beyond + ``effective_degree`` can be safely dropped (default: ``None``) + + - ``size_heuristic_bound`` -- when ``force`` is not set, the expected + factor by which the coefficients need to shrink to perform an actual + simplification (default: 32) + + - ``phiadic`` -- whether to simplify in the `x`-adic expansion; the + parameter is ignored as no other simplification is implemented + + EXAMPLES:: + + sage: R. = Qq(4, 5) + sage: S. = R[] + sage: v = GaussValuation(S) + sage: f = x^10/2 + 1 + sage: v.simplify(f) + (2^-1 + O(2^4))*x^10 + 1 + O(2^5) + + """ + f = self.domain().coerce(f) + + if effective_degree is not None: + if effective_degree < f.degree(): + f = f.truncate(effective_degree + 1) + + if not force and self._relative_size(f) < size_heuristic_bound: + return f + + if error is None: + # if the caller was sure that we should simplify, then we should try to do the best simplification possible + error = self(f) if force else self.uppper_bound(f) + + return f.map_coefficients(lambda c: self._base_valuation.simplify(c, error=error, force=force)) + + def lower_bound(self, f): + r""" + Return an lower bound of this valuation at ``f``. + + Use this method to get an approximation of the valuation of ``f`` + when speed is more important than accuracy. + + EXAMPLES:: + + sage: R. = Qq(4, 5) + sage: S. = R[] + sage: v = GaussValuation(S) + sage: v.lower_bound(1024*x + 2) + 1 + sage: v(1024*x + 2) + 1 + + """ + from sage.rings.all import infinity, QQ + coefficients = f.coefficients(sparse=True) + coefficients.reverse() + ret = infinity + for c in coefficients: + v = self._base_valuation.lower_bound(c) + if c is not infinity and (ret is infinity or v < ret): + ret = v + return ret + + def upper_bound(self, f): + r""" + Return an upper bound of this valuation at ``f``. + + Use this method to get an approximation of the valuation of ``f`` + when speed is more important than accuracy. + + EXAMPLES:: + + sage: R. = Qq(4, 5) + sage: S. = R[] + sage: v = GaussValuation(S) + sage: v.upper_bound(1024*x + 1) + 10 + sage: v(1024*x + 1) + 0 + + """ + f = self.domain().coerce(f) + coefficients = f.coefficients(sparse=True) + if not coefficients: + from sage.rings.all import infinity + return infinity + else: + return self._base_valuation.upper_bound(coefficients[-1]) diff --git a/src/sage/rings/valuation/inductive_valuation.py b/src/sage/rings/valuation/inductive_valuation.py new file mode 100644 index 00000000000..a8b1ca79773 --- /dev/null +++ b/src/sage/rings/valuation/inductive_valuation.py @@ -0,0 +1,1618 @@ +# -*- coding: utf-8 -*- +r""" +Inductive valuations on polynomial rings + +This module provides functionality for inductive valuations, i.e., finite +chains of :mod:`augmented valuations ` on top of a :mod:`Gauss valuation `. + +AUTHORS: + +- Julian Rüth (2016-11-01): initial version + +EXAMPLES: + +A :mod:`Gauss valuation ` is an example of an inductive valuation:: + + sage: R. = QQ[] + sage: v = GaussValuation(R, QQ.valuation(2)) + +Generally, an inductive valuation is an augmentation of an inductive valuation, +i.e., a valuation that was created from a Gauss valuation in a finite number of +augmentation steps:: + + sage: w = v.augmentation(x, 1) + sage: w = w.augmentation(x^2 + 2*x + 4, 3) + +REFERENCES: + +Inductive valuations are originally discussed in [Mac1936I]_ and [Mac1936II]_. +An introduction is also given in Chapter 4 of [Rüt2014]_. + +""" +#***************************************************************************** +# Copyright (C) 2016-2017 Julian Rüth +# +# Distributed under the terms of the GNU General Public License (GPL) +# as published by the Free Software Foundation; either version 2 of +# the License, or (at your option) any later version. +# http://www.gnu.org/licenses/ +#***************************************************************************** +from valuation import DiscreteValuation, InfiniteDiscretePseudoValuation +from developing_valuation import DevelopingValuation + +from sage.misc.cachefunc import cached_method +from sage.misc.abstract_method import abstract_method + +class InductiveValuation(DevelopingValuation): + r""" + Abstract base class for iterated :mod:`augmented valuations ` on top of a :mod:`Gauss valuation `. + + EXAMPLES:: + + sage: R. = QQ[] + sage: v = GaussValuation(R, QQ.valuation(5)) + + TESTS:: + + sage: TestSuite(v).run() # long time + + """ + def is_equivalence_unit(self, f, valuations=None): + r""" + Return whether the polynomial ``f`` is an equivalence unit, i.e., an + element of :meth:`~sage.rings.valuation.developing_valuation.DevelopingValuation.effective_degree` + zero (see [Mac1936II]_ p.497.) + + INPUT: + + - ``f`` -- a polynomial in the domain of this valuation + + EXAMPLES:: + + sage: R = Zp(2,5) + sage: S. = R[] + sage: v = GaussValuation(S) + sage: v.is_equivalence_unit(x) + False + sage: v.is_equivalence_unit(S.zero()) + False + sage: v.is_equivalence_unit(2*x + 1) + True + + """ + f = self.domain().coerce(f) + + if f.is_zero(): + return False + return self.effective_degree(f, valuations=valuations) == 0 + + def equivalence_reciprocal(self, f, coefficients=None, valuations=None, check=True): + r""" + Return an equivalence reciprocal of ``f``. + + An equivalence reciprocal of `f` is a polynomial `h` such that `f\cdot + h` is equivalent to 1 modulo this valuation (see [Mac1936II]_ p.497.) + + INPUT: + + - ``f`` -- a polynomial in the domain of this valuation which is an + :meth:`equivalence_unit` + + - ``coefficients`` -- the coefficients of ``f`` in the :meth:`~sage.rings.valuation.developing_valuation.DevelopingValuation.phi`-adic + expansion if known (default: ``None``) + + - ``valuations`` -- the valuations of ``coefficients`` if known + (default: ``None``) + + - ``check`` -- whether or not to check the validity of ``f`` (default: + ``True``) + + .. WARNING:: + + This method may not work over `p`-adic rings due to problems with + the xgcd implementation there. + + EXAMPLES:: + + sage: R = Zp(3,5) + sage: S. = R[] + sage: v = GaussValuation(S) + sage: f = 3*x + 2 + sage: h = v.equivalence_reciprocal(f); h + (2 + O(3^5)) + sage: v.is_equivalent(f*h, 1) + True + + In an extended valuation over an extension field:: + + sage: R. = Qq(4,5) + sage: S. = R[] + sage: v = GaussValuation(S) + sage: v = v.augmentation(x^2 + x + u, 1) + sage: f = 2*x + u + sage: h = v.equivalence_reciprocal(f); h + (u + 1) + O(2^5) + sage: v.is_equivalent(f*h, 1) + True + + Extending the valuation once more:: + + sage: v = v.augmentation((x^2 + x + u)^2 + 2*x*(x^2 + x + u) + 4*x, 3) + sage: h = v.equivalence_reciprocal(f); h + (u + 1) + O(2^5) + sage: v.is_equivalent(f*h, 1) + True + + TESTS: + + A case that caused problems at some point:: + + sage: K = Qp(2, 4) + sage: R. = K[] + sage: L. = K.extension(x^4 + 4*x^3 + 6*x^2 + 4*x + 2) + sage: R. = L[] + sage: v = GaussValuation(R) + sage: w = v.augmentation(t + 1, 5/16) + sage: w = w.augmentation(t^4 + (a^8 + a^12 + a^14 + a^16 + a^17 + a^19 + a^20 + a^23)*t^3 + (a^6 + a^9 + a^13 + a^15 + a^18 + a^19 + a^21)*t^2 + a^10*t + 1 + a^4 + a^5 + a^8 + a^13 + a^14 + a^15, 17/8) + sage: f = a^-15*t^2 + (a^-11 + a^-9 + a^-6 + a^-5 + a^-3 + a^-2)*t + a^-15 + sage: f_ = w.equivalence_reciprocal(f) + sage: w.reduce(f*f_) + 1 + sage: f = f.parent()([f[0], f[1].add_bigoh(1), f[2]]) + sage: f_ = w.equivalence_reciprocal(f) + sage: w.reduce(f*f_) + 1 + + """ + f = self.domain().coerce(f) + + if check: + if coefficients is None: + coefficients = list(self.coefficients(f)) + if valuations is None: + valuations = list(self.valuations(f, coefficients=coefficients)) + if not self.is_equivalence_unit(f, valuations=valuations): + raise ValueError("f must be an equivalence unit but %r is not"%(f,)) + + if coefficients is None: + e0 = next(self.coefficients(f)) + else: + e0 = coefficients[0] + + # f is an equivalence unit, its valuation is given by the constant coefficient + if valuations is None: + vf = self(e0) + else: + vf = valuations[0] + + e0 = self.simplify(e0, error=vf) + s_ = self.equivalence_unit(-vf) + residue = self.reduce(e0 * s_) + if not isinstance(self, FinalInductiveValuation): + assert residue.is_constant() + residue = residue[0] + h = self.lift(~residue) * s_ + + h = self.simplify(h, -vf) + + # it might be the case that f*h has non-zero valuation because h has + # insufficient precision, so we must not assert that here but only + # until we lifted to higher precision + + # We do not actually need g*phi + h*e0 = 1, it is only important that + # the RHS is 1 in reduction. + # This allows us to do two things: + # - we may lift h to arbitrary precision + # - we can add anything which times e0 has positive valuation, e.g., we + # may drop coefficients of positive valuation + h = h.map_coefficients(lambda c:_lift_to_maximal_precision(c)) + + return h + + @cached_method + def mu(self): + r""" + Return the valuation of :meth:`~sage.rings.valuation.developing_valuation.DevelopingValuation.phi`. + + EXAMPLES:: + + sage: R. = QQ[] + sage: v = GaussValuation(R, QQ.valuation(2)) + sage: v.mu() + 0 + + """ + return self(self.phi()) + + @abstract_method + def equivalence_unit(self, s, reciprocal=False): + """ + Return an equivalence unit of valuation ``s``. + + INPUT: + + - ``s`` -- an element of the :meth:`~sage.rings.valuation.valuation_space.DiscretePseudoValuationSpace.ElementMethods.value_group` + + - ``reciprocal`` -- a boolean (default: ``False``); whether or not to + return the equivalence unit as the :meth:`equivalence_reciprocal` of + the equivalence unit of valuation ``-s``. + + EXAMPLES:: + + sage: S. = Qp(3,5)[] + sage: v = GaussValuation(S) + sage: v.equivalence_unit(2) + (3^2 + O(3^7)) + sage: v.equivalence_unit(-2) + (3^-2 + O(3^3)) + + Note that this might fail for negative ``s`` if the domain is not + defined over a field:: + + sage: v = ZZ.valuation(2) + sage: R. = ZZ[] + sage: w = GaussValuation(R, v) + sage: w.equivalence_unit(1) + 2 + sage: w.equivalence_unit(-1) + Traceback (most recent call last): + ... + ValueError: s must be in the value semigroup of this valuation but -1 is not in Additive Abelian Semigroup generated by 1 + + """ + + @abstract_method + def augmentation_chain(self): + r""" + Return a list with the chain of augmentations down to the underlying + :mod:`Gauss valuation `. + + EXAMPLES:: + + sage: R. = Qq(4,5) + sage: S. = R[] + sage: v = GaussValuation(S) + sage: v.augmentation_chain() + [Gauss valuation induced by 2-adic valuation] + + """ + + @abstract_method + def is_gauss_valuation(self): + r""" + Return whether this valuation is a Gauss valuation over the domain. + + EXAMPLES:: + + sage: R. = Qq(4,5) + sage: S. = R[] + sage: v = GaussValuation(S) + sage: v.is_gauss_valuation() + True + + """ + + @abstract_method + def E(self): + """ + Return the ramification index of this valuation over its underlying + Gauss valuation. + + EXAMPLES:: + + sage: R. = Qq(4,5) + sage: S. = R[] + sage: v = GaussValuation(S) + sage: v.E() + 1 + + """ + + @abstract_method + def F(self): + """ + Return the residual degree of this valuation over its Gauss extension. + + EXAMPLES:: + + sage: R. = Qq(4,5) + sage: S. = R[] + sage: v = GaussValuation(S) + sage: v.F() + 1 + + """ + + @abstract_method + def monic_integral_model(self, G): + r""" + Return a monic integral irreducible polynomial which defines the same + extension of the base ring of the domain as the irreducible polynomial + ``G`` together with maps between the old and the new polynomial. + + EXAMPLES:: + + sage: R. = QQ[] + sage: v = GaussValuation(R, QQ.valuation(2)) + sage: v.monic_integral_model(5*x^2 + 1/2*x + 1/4) + (Ring endomorphism of Univariate Polynomial Ring in x over Rational Field + Defn: x |--> 1/2*x, + Ring endomorphism of Univariate Polynomial Ring in x over Rational Field + Defn: x |--> 2*x, + x^2 + 1/5*x + 1/5) + + """ + + @abstract_method + def element_with_valuation(self, s): + r""" + Return a polynomial of minimal degree with valuation ``s``. + + EXAMPLES:: + + sage: R. = QQ[] + sage: v = GaussValuation(R, QQ.valuation(2)) + sage: v.element_with_valuation(-2) + 1/4 + + Depending on the base ring, an element of valuation ``s`` might not + exist:: + + sage: R. = ZZ[] + sage: v = GaussValuation(R, ZZ.valuation(2)) + sage: v.element_with_valuation(-2) + Traceback (most recent call last): + ... + ValueError: s must be in the value semigroup of this valuation but -2 is not in Additive Abelian Semigroup generated by 1 + + """ + + def _test_element_with_valuation_inductive_valuation(self, **options): + r""" + Test the correctness of :meth:`element_with_valuation`. + + EXAMPLES:: + + sage: R. = QQ[] + sage: v = GaussValuation(R, QQ.valuation(2)) + sage: v._test_element_with_valuation_inductive_valuation() + + """ + tester = self._tester(**options) + chain = self.augmentation_chain() + for s in tester.some_elements(self.value_group().some_elements()): + try: + R = self.element_with_valuation(s) + except (ValueError, NotImplementedError): + # this is often not possible unless the underlying ring of + # constants is a field + from sage.categories.fields import Fields + if self.domain().base() not in Fields(): + continue + raise + tester.assertEqual(self(R), s) + if chain != [self]: + base = chain[1] + if s in base.value_group(): + S = base.element_with_valuation(s) + tester.assertEqual(self(S), s) + tester.assertGreaterEqual(S.degree(), R.degree()) + + def _test_EF(self, **options): + r""" + Test the correctness of :meth:`E` and :meth:`F`. + + EXAMPLES:: + + sage: R. = Qq(4,5) + sage: S. = R[] + sage: v = GaussValuation(S) + sage: v._test_EF() + + """ + tester = self._tester(**options) + chain = self.augmentation_chain() + for w,v in zip(chain, chain[1:]): + from sage.rings.all import infinity, ZZ + if w(w.phi()) is infinity: + tester.assertEqual(w.E(), v.E()) + tester.assertIn(w.E(), ZZ) + tester.assertIn(w.F(), ZZ) + tester.assertGreaterEqual(w.E(), v.E()) + tester.assertGreaterEqual(w.F(), v.F()) + + def _test_augmentation_chain(self, **options): + r""" + Test the correctness of :meth:`augmentation_chain`. + + EXAMPLES:: + + sage: R. = QQ[] + sage: v = GaussValuation(R, valuations.TrivialValuation(QQ)) + sage: v._test_augmentation_chain() + + """ + tester = self._tester(**options) + chain = self.augmentation_chain() + tester.assertIs(chain[0], self) + tester.assertTrue(chain[-1].is_gauss_valuation()) + for w,v in zip(chain, chain[1:]): + tester.assertGreaterEqual(w, v) + + def _test_equivalence_unit(self, **options): + r""" + Test the correctness of :meth:`lift_to_key`. + + EXAMPLES:: + + sage: R. = QQ[] + sage: v = GaussValuation(R, valuations.TrivialValuation(QQ)) + sage: v._test_equivalence_unit() + + """ + tester = self._tester(**options) + + if self.is_gauss_valuation(): + value_group = self.value_group() + else: + value_group = self.augmentation_chain()[1].value_group() + + for s in tester.some_elements(value_group.some_elements()): + try: + R = self.equivalence_unit(s) + except (ValueError, NotImplementedError): + # this is often not possible unless the underlying ring of + # constants is a field + from sage.categories.fields import Fields + if self.domain().base() not in Fields(): + continue + raise + tester.assertIs(R.parent(), self.domain()) + tester.assertEqual(self(R), s) + tester.assertTrue(self.is_equivalence_unit(R)) + + def _test_is_equivalence_unit(self, **options): + r""" + Test the correctness of :meth:`is_equivalence_unit`. + + EXAMPLES:: + + sage: R. = QQ[] + sage: v = GaussValuation(R, valuations.TrivialValuation(QQ)) + sage: v._test_is_equivalence_unit() + + """ + tester = self._tester(**options) + tester.assertFalse(self.is_equivalence_unit(self.phi())) + + def _test_equivalence_reciprocal(self, **options): + r""" + Test the correctness of :meth:`equivalence_reciprocal`. + + EXAMPLES:: + + sage: R. = QQ[] + sage: v = GaussValuation(R, valuations.TrivialValuation(QQ)) + sage: v._test_equivalence_reciprocal() + + """ + tester = self._tester(**options) + S = tester.some_elements(self.domain().some_elements()) + for f in S: + if self.is_equivalence_unit(f): + try: + g = self.equivalence_reciprocal(f) + except (ValueError, NotImplementedError): + # this is often not possible unless the underlying ring of + # constants is a field + from sage.categories.fields import Fields + if self.domain().base() not in Fields(): + continue + raise + tester.assertEqual(self.reduce(f*g), 1) + + def _test_inductive_valuation_inheritance(self, **options): + r""" + Test that every instance that is a :class:`InductiveValuation` is + either a :class:`FiniteInductiveValuation` or a + :class:`InfiniteInductiveValuation`. Same for + :class:`FinalInductiveValuation` and + :class:`NonFinalInductiveValuation`. + + EXAMPLES:: + + sage: R. = QQ[] + sage: v = GaussValuation(R, valuations.TrivialValuation(QQ)) + sage: v._test_inductive_valuation_inheritance() + + """ + tester = self._tester(**options) + tester.assertTrue(isinstance(self, InfiniteInductiveValuation) != isinstance(self, FiniteInductiveValuation)) + tester.assertTrue(isinstance(self, FinalInductiveValuation) != isinstance(self, NonFinalInductiveValuation)) + + +class FiniteInductiveValuation(InductiveValuation, DiscreteValuation): + r""" + Abstract base class for iterated :mod:`augmented valuations ` + on top of a :mod:`Gauss valuation ` which is a discrete valuation, + i.e., the last key polynomial has finite valuation. + + EXAMPLES:: + + sage: R. = QQ[] + sage: v = GaussValuation(R, valuations.TrivialValuation(QQ)) + + """ + def __init__(self, parent, phi): + r""" + TESTS:: + + sage: R. = QQ[] + sage: v = GaussValuation(R, valuations.TrivialValuation(QQ)) + sage: from sage.rings.valuation.inductive_valuation import FiniteInductiveValuation + sage: isinstance(v, FiniteInductiveValuation) + True + + """ + InductiveValuation.__init__(self, parent, phi) + DiscreteValuation.__init__(self, parent) + + def extensions(self, other): + r""" + Return the extensions of this valuation to ``other``. + + EXAMPLES:: + + sage: R. = ZZ[] + sage: v = GaussValuation(R, valuations.TrivialValuation(ZZ)) + sage: K. = FunctionField(QQ) + sage: v.extensions(K) + [Trivial valuation on Rational Field] + + """ + from sage.categories.function_fields import FunctionFields + if other in FunctionFields() and other.ngens() == 1: + # extend to K[x] and from there to K(x) + v = self.extension(self.domain().change_ring(self.domain().base().fraction_field())) + return [other.valuation(v)] + return super(FiniteInductiveValuation, self).extensions(other) + + +class NonFinalInductiveValuation(FiniteInductiveValuation, DiscreteValuation): + r""" + Abstract base class for iterated :mod:`augmented valuations ` + on top of a :mod:`Gauss valuation ` which can be extended further + through :meth:`augmentation`. + + EXAMPLES:: + + sage: R. = Qq(4,5) + sage: S. = R[] + sage: v = GaussValuation(S) + sage: v = v.augmentation(x^2 + x + u, 1) + + """ + def __init__(self, parent, phi): + r""" + TESTS:: + + sage: R. = Qq(4,5) + sage: S. = R[] + sage: v = GaussValuation(S) + sage: v = v.augmentation(x^2 + x + u, 1) + sage: from sage.rings.valuation.inductive_valuation import NonFinalInductiveValuation + sage: isinstance(v, NonFinalInductiveValuation) + True + + """ + FiniteInductiveValuation.__init__(self, parent, phi) + DiscreteValuation.__init__(self, parent) + + def augmentation(self, phi, mu, check=True): + r""" + Return the inductive valuation which extends this valuation by mapping + ``phi`` to ``mu``. + + INPUT: + + - ``phi`` -- a polynomial in the domain of this valuation; this must be + a key polynomial, see :meth:`is_key` for properties of key + polynomials. + + - ``mu`` -- a rational number or infinity, the valuation of ``phi`` in + the extended valuation + + - ``check`` -- a boolean (default: ``True``), whether or not to check + the correctness of the parameters + + EXAMPLES:: + + sage: R. = Qq(4,5) + sage: S. = R[] + sage: v = GaussValuation(S) + sage: v = v.augmentation(x^2 + x + u, 1) + sage: v = v.augmentation((x^2 + x + u)^2 + 2*x*(x^2 + x + u) + 4*x, 3) + sage: v + [ Gauss valuation induced by 2-adic valuation, + v((1 + O(2^5))*x^2 + (1 + O(2^5))*x + u + O(2^5)) = 1, + v((1 + O(2^5))*x^4 + (2^2 + O(2^6))*x^3 + (1 + (u + 1)*2 + O(2^5))*x^2 + ((u + 1)*2^2 + O(2^6))*x + (u + 1) + (u + 1)*2 + (u + 1)*2^2 + (u + 1)*2^3 + (u + 1)*2^4 + O(2^5)) = 3 ] + + TESTS: + + Make sure that we do not make the assumption that the degrees of the + key polynomials are strictly increasing:: + + sage: v_K = QQ.valuation(3) + sage: A. = QQ[] + sage: v0 = GaussValuation(A,v_K) + + sage: v1 = v0.augmentation(t, 1/12) + sage: v2 = v1.augmentation(t^12 + 3, 7/6) + sage: v3 = v2.augmentation(t^12 + 3*t^2 + 3, 9/4) + sage: v4 = v1.augmentation(t^12 + 3*t^2 + 3, 9/4) + sage: v3 <= v4 and v3 >= v4 + True + + .. SEEALSO:: + + :mod:`~sage.rings.valuation.augmented_valuation` + + """ + from augmented_valuation import AugmentedValuation + return AugmentedValuation(self, phi, mu, check) + + def mac_lane_step(self, G, principal_part_bound=None, assume_squarefree=False, assume_equivalence_irreducible=False, report_degree_bounds_and_caches=False, coefficients=None, valuations=None, check=True): + r""" + Perform an approximation step towards the squarefree monic non-constant + integral polynomial ``G`` which is not an :meth:`equivalence unit `. + + This performs the individual steps that are used in + :meth:`~sage.rings.valuation.valuation.DiscreteValuation.mac_lane_approximants`. + + INPUT: + + - ``G`` -- a sqaurefree monic non-constant integral polynomial ``G`` + which is not an :meth:`equivalence unit ` + + - ``principal_part_bound`` -- an integer or ``None`` (default: + ``None``), a bound on the length of the principal part, i.e., the + section of negative slope, of the Newton polygon of ``G`` + + - ``assume_squarefree`` -- whether or not to assume that ``G`` is + squarefree (default: ``False``) + + - ``assume_equivalence_irreducible`` -- whether or not to assume that + ``G`` is equivalence irreducible (default: ``False``) + + - ``report_degree_bounds_and_caches`` -- whether or not to include internal state with the returned value (used by :meth:`~sage.rings.valuation.valuation.DiscreteValuation.mac_lane_approximants` to speed up sequential calls) + + - ``coefficients`` -- the coefficients of ``G`` in the :meth:`~sage.rings.valuation.developing_valuation.DevelopingValuation.phi`-adic expansion if known (default: ``None``) + + - ``valauations`` -- the valuations of ``coefficients`` if known + (default: ``None``) + + - ``check`` -- whether to check that ``G`` is a squarefree monic + non-constant integral polynomial and not an :meth:`equivalence unit ` + (default: ``True``) + + TESTS:: + + sage: K. = FunctionField(QQ) + sage: S. = K[] + sage: F = y^2 - x^2 - x^3 - 3 + sage: v0 = GaussValuation(K._ring, QQ.valuation(3)) + sage: v1 = v0.augmentation(K._ring.gen(), 1/3) + sage: mu0 = K.valuation(v1) + sage: eta0 = GaussValuation(S, mu0) + sage: eta1 = eta0.mac_lane_step(F)[0] + sage: eta2 = eta1.mac_lane_step(F)[0] + sage: eta2 + [ Gauss valuation induced by Valuation on rational function field induced by [ Gauss valuation induced by 3-adic valuation, v(x) = 1/3 ], v(y + x) = 2/3 ] + + """ + G = self.domain().coerce(G) + + if G.is_constant(): + raise ValueError("G must not be constant") + + from itertools import islice + from sage.misc.misc import verbose + verbose("Augmenting %s towards %s"%(self, G), level=10) + + if not G.is_monic(): + raise ValueError("G must be monic") + + if coefficients is None: + coefficients = self.coefficients(G) + if principal_part_bound: + coefficients = islice(coefficients, 0, principal_part_bound + 1, 1) + coefficients = list(coefficients) + if valuations is None: + valuations = self.valuations(G, coefficients=coefficients) + if principal_part_bound: + valuations = islice(valuations, 0, principal_part_bound + 1, 1) + valuations = list(valuations) + + if check and min(valuations) < 0: + raise ValueError("G must be integral") + + if check and self.is_equivalence_unit(G, valuations=valuations): + raise ValueError("G must not be an equivalence-unit") + + if check and not assume_squarefree and not G.is_squarefree(): + raise ValueError("G must be squarefree") + + from sage.rings.all import infinity + assert self(G) is not infinity # this is a valuation and G is non-zero + + ret = [] + + F = self.equivalence_decomposition(G, assume_not_equivalence_unit=True, coefficients=coefficients, valuations=valuations, compute_unit=False, degree_bound=principal_part_bound) + assert len(F), "%s equivalence-decomposes as an equivalence-unit %s"%(G, F) + if len(F) == 1 and F[0][1] == 1 and F[0][0].degree() == G.degree(): + assert self.is_key(G, assume_equivalence_irreducible=assume_equivalence_irreducible) + ret.append((self.augmentation(G, infinity, check=False), G.degree(), principal_part_bound, None, None)) + else: + for phi,e in F: + if G == phi: + # Something strange happened here: + # G is not a key (we checked that before) but phi==G is; so phi must have less precision than G + # this can happen if not all coefficients of G have the same precision + # if we drop some precision of G then it will be a key (but is + # that really what we should do?) + assert not G.base_ring().is_exact() + prec = min([c.precision_absolute() for c in phi.list()]) + g = G.map_coefficients(lambda c:c.add_bigoh(prec)) + assert self.is_key(g) + ret.append((self.augmentation(g, infinity, check=False), g.degree(), principal_part_bound, None, None)) + assert len(F) == 1 + break + + if phi == self.phi(): + # a factor phi in the equivalence decomposition means that we + # found an actual factor of G, i.e., we can set + # v(phi)=infinity + # However, this should already have happened in the last step + # (when this polynomial had -infinite slope in the Newton + # polygon.) + if self.is_gauss_valuation(): # unless in the first step + pass + else: + continue + + verbose("Determining the augmentation of %s for %s"%(self, phi), level=11) + old_mu = self(phi) + w = self.augmentation(phi, old_mu, check=False) + + # we made some experiments here: instead of computing the + # coefficients again from scratch, update the coefficients when + # phi - self.phi() is a constant. + # It turned out to be slightly slower than just recomputing the + # coefficients. The main issue with the approach was that we + # needed to keep track of all the coefficients and not just of + # the coefficients up to principal_part_bound. + + w_coefficients = w.coefficients(G) + if principal_part_bound: + w_coefficients = islice(w_coefficients, 0, principal_part_bound + 1, 1) + w_coefficients = list(w_coefficients) + + w_valuations = w.valuations(G, coefficients=w_coefficients) + if principal_part_bound: + w_valuations = islice(w_valuations, 0, principal_part_bound + 1, 1) + w_valuations = list(w_valuations) + + from sage.geometry.newton_polygon import NewtonPolygon + NP = NewtonPolygon(w.newton_polygon(G, valuations=w_valuations).vertices(), last_slope=0) + + verbose("Newton-Polygon for v(phi)=%s : %s"%(self(phi), NP), level=11) + slopes = NP.slopes(repetition=True) + multiplicities = {slope : len([s for s in slopes if s == slope]) for slope in slopes} + slopes = multiplicities.keys() + if NP.vertices()[0][0] != 0: + slopes = [-infinity] + slopes + multiplicities[-infinity] = 1 + + for i, slope in enumerate(slopes): + verbose("Slope = %s"%slope, level=12) + new_mu = old_mu - slope + new_valuations = [val - (j*slope if slope is not -infinity else (0 if j == 0 else -infinity)) + for j,val in enumerate(w_valuations)] + base = self + if phi.degree() == base.phi().degree(): + # very frequently, the degree of the key polynomials + # stagnate for a bit while the valuation of the key + # polynomial is slowly increased. + # In this case, we can drop previous key polynomials + # of the same degree. (They have no influence on the + # phi-adic expansion.) + assert new_mu > self(phi) + if not base.is_gauss_valuation(): + base = base._base_valuation + w = base.augmentation(phi, new_mu, check=False) + assert slope is -infinity or 0 in w.newton_polygon(G).slopes(repetition=False) + + from sage.rings.all import ZZ + assert (phi.degree() / self.phi().degree()) in ZZ + degree_bound = multiplicities[slope] * phi.degree() + assert degree_bound <= G.degree() + assert degree_bound >= phi.degree() + ret.append((w, degree_bound, multiplicities[slope], w_coefficients, new_valuations)) + + assert ret + if not report_degree_bounds_and_caches: + ret = [v for v,_,_,_,_ in ret] + return ret + + def is_key(self, phi, explain=False, assume_equivalence_irreducible=False): + r""" + Return whether ``phi`` is a key polynomial for this valuation, i.e., + whether it is monic, whether it :meth:`is_equivalence_irreducible`, and + whether it is :meth:`is_minimal`. + + INPUT: + + - ``phi`` -- a polynomial in the domain of this valuation + + - ``explain`` -- a boolean (default: ``False``), if ``True``, return a + string explaining why ``phi`` is not a key polynomial + + EXAMPLES:: + + sage: R. = Qq(4, 5) + sage: S. = R[] + sage: v = GaussValuation(S) + sage: v.is_key(x) + True + sage: v.is_key(2*x, explain = True) + (False, 'phi must be monic') + sage: v.is_key(x^2, explain = True) + (False, 'phi must be equivalence irreducible') + + sage: w = v.augmentation(x, 1) + sage: w.is_key(x + 1, explain = True) + (False, 'phi must be minimal') + + """ + phi = self.domain().coerce(phi) + + reason = None + + if not phi.is_monic(): + reason = "phi must be monic" + elif not assume_equivalence_irreducible and not self.is_equivalence_irreducible(phi): + reason = "phi must be equivalence irreducible" + elif not self.is_minimal(phi, assume_equivalence_irreducible=True): + reason = "phi must be minimal" + + if explain: + return reason is None, reason + else: + return reason is None + + def is_minimal(self, f, assume_equivalence_irreducible=False): + r""" + Return whether the polynomial ``f`` is minimal with respect to this + valuation. + + A polynomial `f` is minimal with respect to `v` if it is not a constant + and any non-zero polynomial `h` which is `v`-divisible by `f` has at + least the degree of `f`. + + A polynomial `h` is `v`-divisible by `f` if there is a polynomial `c` + such that `fc` :meth:`~sage.rings.valuation.valuation.DiscretePseudoValuation.is_equivalent` to `h`. + + ALGORITHM: + + Based on Theorem 9.4 of [Mac1936II]_. + + EXAMPLES:: + + sage: R. = Qq(4, 5) + sage: S. = R[] + sage: v = GaussValuation(S) + sage: v.is_minimal(x + 1) + True + sage: w = v.augmentation(x, 1) + sage: w.is_minimal(x + 1) + False + + TESTS:: + + sage: K = Qp(2, 10) + sage: R. = K[] + sage: vp = K.valuation() + sage: v0 = GaussValuation(R, vp) + sage: v1 = v0.augmentation(x, 1/4) + sage: v2 = v1.augmentation(x^4 + 2, 5/4) + sage: v2.is_minimal(x^5 + x^4 + 2) + False + + Polynomials which are equivalent to the key polynomial are minimal if + and only if they have the same degree as the key polynomial:: + + sage: v2.is_minimal(x^4 + 2) + True + sage: v2.is_minimal(x^4 + 4) + False + + """ + f = self.domain().coerce(f) + + if f.is_constant(): + return False + + if not assume_equivalence_irreducible and not self.is_equivalence_irreducible(f): + # any factor divides f with respect to this valuation + return False + + if not f.is_monic(): + # divide out the leading factor, it does not change minimality + v = self + if not self.domain().base_ring().is_field(): + domain = self.domain().change_ring(self.domain().base_ring().fraction_field()) + v = self.extension(domain) + f = domain(f) + return v.is_minimal(f / f.leading_coefficient()) + + if self.is_gauss_valuation(): + if self(f) == 0: + F = self.reduce(f, check=False) + assert not F.is_constant() + return F.is_irreducible() + else: + assert(self(f) <= 0) # f is monic + # f is not minimal: + # Let g be f stripped of its leading term, i.e., g = f - x^n. + # Then g and f are equivalent with respect to this valuation + # and in particular g divides f with respect to this valuation + return False + + if self.is_equivalent(self.phi(), f): + assert f.degree() >= self.phi().degree() + # If an h divides f with respect to this valuation, then it also divides phi: + # v(f - c*h) > v(f) = v(c*h) => v(phi - c*h) = v((phi - f) + (f - c*h)) > v(phi) = v(c*h) + # So if f were not minimal then phi would not be minimal but it is. + return f.degree() == self.phi().degree() + + else: + tau = self.value_group().index(self._base_valuation.value_group()) + # see Theorem 9.4 of [Mac1936II] + return list(self.valuations(f))[-1] == self(f) and \ + list(self.coefficients(f))[-1].is_constant() and \ + list(self.valuations(f))[0] == self(f) and \ + tau.divides(len(list(self.coefficients(f))) - 1) + + def _equivalence_reduction(self, f, coefficients=None, valuations=None, degree_bound=None): + r""" + Helper method for :meth:`is_equivalence_irreducible` and + :meth:`equivalence_decomposition` which essentially returns the + reduction of ``f`` after multiplication with an ``R`` which + :meth:`is_equivalence_unit`. + + This only works when ``f`` is not divisible by :meth:`phi` with respect + to this valuation. Therefore, we also return the number of times that + we took out :meth:`phi` of ``f`` before we computed the reduction. + + EXAMPLES:: + + sage: R. = QQ[] + sage: v = GaussValuation(R, QQ.valuation(2)) + sage: v._equivalence_reduction(2*x^6 + 4*x^5 + 2*x^4 + 8) + (1, 4, x^2 + 1) + + """ + f = self.domain().coerce(f) + + # base change from R[x] to K[x], so divisions work and sufficient + # elements of negative valuation exist + if not self.domain().base_ring().is_field(): + domain = self.domain().change_ring(self.domain().base_ring().fraction_field()) + v = self.extension(domain) + assert self.residue_ring() is v.residue_ring() + return v._equivalence_reduction(f) + + if coefficients is None: + coefficients = list(self.coefficients(f)) + if valuations is None: + valuations = list(self.valuations(f, coefficients=coefficients)) + valuation = min(valuations) + for phi_divides in range(len(valuations)): + # count how many times phi divides f + if valuations[phi_divides] <= valuation: + break + + if phi_divides: + from sage.rings.all import PolynomialRing + R = PolynomialRing(f.parent(), 'phi') + f = R(coefficients[phi_divides:])(self.phi()) + valuations = [v-self.mu()*phi_divides for v in valuations[phi_divides:]] + coefficients = coefficients[phi_divides:] + valuation = min(valuations) + + R = self.equivalence_unit(-valuation) + R = next(self.coefficients(R)) + fR_valuations = [v-valuation for v in valuations] + from sage.rings.all import infinity + fR_coefficients = [next(self.coefficients(c*R)) if v is not infinity and v == 0 else 0 + for c,v in zip(coefficients,fR_valuations)] + + return valuation, phi_divides, self.reduce(f*R, check=False, degree_bound=degree_bound, coefficients=fR_coefficients, valuations=fR_valuations) + + def is_equivalence_irreducible(self, f, coefficients=None, valuations=None): + r""" + Return whether the polynomial ``f`` is equivalence-irreducible, i.e., + whether its :meth:`equivalence_decomposition` is trivial. + + ALGORITHM: + + We use the same algorithm as in :meth:`equivalence_decomposition` we + just do not lift the result to key polynomials. + + INPUT: + + - ``f`` -- a non-constant polynomial in the domain of this valuation + + EXAMPLES:: + + sage: R. = Qq(4,5) + sage: S. = R[] + sage: v = GaussValuation(S) + sage: v.is_equivalence_irreducible(x) + True + sage: v.is_equivalence_irreducible(x^2) + False + sage: v.is_equivalence_irreducible(x^2 + 2) + False + + """ + f = self.domain().coerce(f) + + if not self.domain().base_ring().is_field(): + domain = self.domain().change_ring(self.domain().base_ring().fraction_field()) + v = self.extension(domain) + return v.is_equivalence_irreducible(v.domain()(f)) + + if f.is_constant(): + raise ValueError("f must not be constant") + + _, phi_divides, F = self._equivalence_reduction(f, coefficients=coefficients, valuations=valuations) + if phi_divides == 0: + return F.is_constant() or F.is_irreducible() + if phi_divides == 1: + return F.is_constant() + if phi_divides > 1: + return False + + def equivalence_decomposition(self, f, assume_not_equivalence_unit=False, coefficients=None, valuations=None, compute_unit=True, degree_bound=None): + r""" + Return an equivalence decomposition of ``f``, i.e., a polynomial + `g(x)=e(x)\prod_i \phi_i(x)` with `e(x)` an :meth:`equivalence unit + ` and the `\phi_i` :meth:`key + polynomials ` such that ``f`` :meth:`~sage.rings.valuation.valuation.DiscretePseudoValuation.is_equivalent` to `g`. + + INPUT: + + - ``f`` -- a non-zero polynomial in the domain of this valuation + + - ``assume_not_equivalence_unit`` -- whether or not to assume that + ``f`` is not an :meth:`equivalence unit ` + (default: ``False``) + + - ``coefficients`` -- the coefficients of ``f`` in the + :meth:`~sage.rings.valuation.developing_valuation.DevelopingValuation.phi`-adic + expansion if known (default: ``None``) + + - ``valuations`` -- the valuations of ``coefficients`` if known + (default: ``None``) + + - ``compute_unit`` -- whether or not to compute the unit part of the + decomposition (default: ``True``) + + - ``degree_bound`` -- a bound on the degree of the + :meth:`_equivalence_reduction` of ``f`` (default: ``None``) + + ALGORITHM: + + We use the algorithm described in Theorem 4.4 of [Mac1936II]_. After + removing all factors `\phi` from a polynomial `f`, there is an + equivalence unit `R` such that `Rf` has valuation zero. Now `Rf` can be + factored as `\prod_i \alpha_i` over the :meth:`~sage.rings.valuation.valuation_space.DiscretePseudoValuationSpace.ElementMethods.residue_field`. Lifting + all `\alpha_i` to key polynomials `\phi_i` gives `Rf=\prod_i R_i f_i` + for suitable equivalence units `R_i` (see :meth:`lift_to_key`). Taking + `R'` an :meth:`~InductiveValuation.equivalence_reciprocal` of `R`, we have `f` equivalent + to `(R'\prod_i R_i)\prod_i\phi_i`. + + EXAMPLES:: + + sage: R. = Qq(4,10) + sage: S. = R[] + sage: v = GaussValuation(S) + sage: v.equivalence_decomposition(S.zero()) + Traceback (most recent call last): + ... + ValueError: equivalence decomposition of zero is not defined + sage: v.equivalence_decomposition(S.one()) + 1 + O(2^10) + sage: v.equivalence_decomposition(x^2+2) + ((1 + O(2^10))*x)^2 + sage: v.equivalence_decomposition(x^2+1) + ((1 + O(2^10))*x + 1 + O(2^10))^2 + + A polynomial that is an equivalence unit, is returned as the unit part + of a :class:`~sage.structure.factorization.Factorization`, leading to a unit + non-minimal degree:: + + sage: w = v.augmentation(x, 1) + sage: F = w.equivalence_decomposition(x^2+1); F + (1 + O(2^10))*x^2 + 1 + O(2^10) + sage: F.unit() + (1 + O(2^10))*x^2 + 1 + O(2^10) + + However, if the polynomial has a non-unit factor, then the unit might + be replaced by a factor of lower degree:: + + sage: f = x * (x^2 + 1) + sage: F = w.equivalence_decomposition(f); F + (1 + O(2^10))*x + sage: F.unit() + 1 + O(2^10) + + Examples over an iterated unramified extension:: + + sage: v = v.augmentation(x^2 + x + u, 1) + sage: v = v.augmentation((x^2 + x + u)^2 + 2*x*(x^2 + x + u) + 4*x, 3) + + sage: v.equivalence_decomposition(x) + (1 + O(2^10))*x + sage: F = v.equivalence_decomposition( v.phi() ) + sage: len(F) + 1 + sage: F = v.equivalence_decomposition( v.phi() * (x^4 + 4*x^3 + (7 + 2*u)*x^2 + (8 + 4*u)*x + 1023 + 3*u) ) + sage: len(F) + 2 + + TESTS:: + + sage: R. = QQ[] + sage: K1.=NumberField(x^3 - 2) + sage: K.=K1.galois_closure() + sage: R.=K[] + sage: vp = QQ.valuation(2) + sage: vp = vp.extension(K) + sage: v0 = GaussValuation(R, vp) + sage: G=x^36 + 36*x^35 + 630*x^34 + 7144*x^33 + 59055*x^32 + 379688*x^31 +1978792*x^30 + 8604440*x^29 + 31895428*x^28 + 102487784*x^27 + 289310720*x^26 + 725361352*x^25 + 1629938380*x^24 + 3307417800*x^23 + 6098786184*x^22+10273444280*x^21 + 15878121214*x^20 + 22596599536*x^19 + 29695703772*x^18 +36117601976*x^17 + 40722105266*x^16 + 42608585080*x^15 + 41395961848*x^14 +37344435656*x^13 + 31267160756*x^12 + 24271543640*x^11 + 17439809008*x^10 + 11571651608*x^9 + 7066815164*x^8 + 3953912472*x^7 + 2013737432*x^6 + 925014888*x^5 + 378067657*x^4 + 134716588*x^3 + 40441790*x^2 + 9532544*x + 1584151 + sage: v1 = v0.mac_lane_step(G)[0] + sage: V = v1.mac_lane_step(G) + sage: v2 = V[0] + sage: F = v2.equivalence_decomposition(G); F + (x^4 + 2*x^2 + alpha^4 + alpha^3 + 1)^3 * (x^4 + 2*x^2 + 1/2*alpha^4 + alpha^3 + 5*alpha + 1)^3 * (x^4 + 2*x^2 + 3/2*alpha^4 + alpha^3 + 5*alpha + 1)^3 + sage: v2.is_equivalent(F.prod(), G) + True + + """ + f = self.domain().coerce(f) + + if f.is_zero(): + raise ValueError("equivalence decomposition of zero is not defined") + + from sage.structure.factorization import Factorization + if not assume_not_equivalence_unit and self.is_equivalence_unit(f): + return Factorization([], unit=f, sort=False) + + if not self.domain().base_ring().is_field(): + nonfractions = self.domain().base_ring() + domain = self.domain().change_ring(nonfractions.fraction_field()) + v = self.extension(domain) + ret = v.equivalence_decomposition(v.domain()(f)) + return Factorization([(self._eliminate_denominators(g), e) + for (g,e) in ret], unit=self._eliminate_denominators(ret.unit())) + + valuation, phi_divides, F = self._equivalence_reduction(f, coefficients=coefficients, valuations=valuations, degree_bound=degree_bound) + F = F.factor() + from sage.misc.misc import verbose + verbose("%s factors as %s = %s in reduction"%(f, F.prod(), F), level=20) + + unit = self.domain().one() + if compute_unit: + R_ = self.equivalence_unit(valuation, reciprocal=True) + unit = self.lift(self.residue_ring()(F.unit())) * R_ + F = list(F) + + if compute_unit: + from sage.misc.all import prod + unit *= self.lift(self.residue_ring()(prod([ psi.leading_coefficient()**e for psi,e in F ]))) + + # A potential speedup that we tried to implement here: + # When F factors as T^n - a, then instead of using any lift of T^n - a + # we tried to take a lift that approximates well an n-th root of the + # constant coefficient of f[0]. Doing so saved a few invocations of + # mac_lane_step but in the end made hardly any difference. + + F = [(self.lift_to_key(psi/psi.leading_coefficient()),e) for psi,e in F] + + if compute_unit: + for g,e in F: + v_g = self(g) + unit *= self._pow(self.equivalence_unit(-v_g, reciprocal=True), e, error=-v_g*e, effective_degree=0) + unit = self.simplify(unit, effective_degree=0) + + if phi_divides: + for i,(g,e) in enumerate(F): + if g == self.phi(): + F[i] = (self.phi(),e+phi_divides) + break + else: + F.append((self.phi(),phi_divides)) + + ret = Factorization(F, unit=unit, sort=False) + + if compute_unit: + assert self.is_equivalent(ret.prod(), f) # this might fail because of leading zeros in inexact rings + assert self.is_equivalence_unit(ret.unit()) + + return ret + + def minimal_representative(self, f): + r""" + Return a minimal representative for ``f``, i.e., a pair `e, a` such + that ``f`` :meth:`~sage.rings.valuation.valuation.DiscretePseudoValuation.is_equivalent` to `e a`, `e` is an + :meth:`equivalence unit `, and `a` :meth:`is_minimal` and monic. + + INPUT: + + - ``f`` -- a non-zero polynomial which is not an equivalence unit + + OUTPUT: + + A factorization which has `e` as its unit and `a` as its unique factor. + + ALGORITHM: + + We use the algorithm described in the proof of Lemma 4.1 of [Mac1936II]_. + In the expansion `f=\sum_i f_i\phi^i` take `e=f_i` for the largest `i` + with `f_i\phi^i` minimal (see :meth:`~sage.rings.valuation.developing_valuation.DevelopingValuation.effective_degree`). + Let `h` be the :meth:`~InductiveValuation.equivalence_reciprocal` of `e` and take `a` given + by the terms of minimal valuation in the expansion of `e f`. + + EXAMPLES:: + + sage: R. = Qq(4,10) + sage: S. = R[] + sage: v = GaussValuation(S) + sage: v.minimal_representative(x + 2) + (1 + O(2^10))*x + + sage: v = v.augmentation(x, 1) + sage: v.minimal_representative(x + 2) + (1 + O(2^10))*x + 2 + O(2^11) + sage: f = x^3 + 6*x + 4 + sage: F = v.minimal_representative(f); F + (2 + 2^2 + O(2^11)) * ((1 + O(2^10))*x + 2 + O(2^11)) + sage: v.is_minimal(F[0][0]) + True + sage: v.is_equivalent(F.prod(), f) + True + + """ + f = self.domain().coerce(f) + + from sage.categories.fields import Fields + if not self.domain().base_ring() in Fields(): + raise NotImplementedError("only implemented for polynomial rings over fields") + + if f.is_zero(): + raise ValueError("zero has no minimal representative") + + degree = self.effective_degree(f) + if degree == 0: + raise ValueError("equivalence units can not have a minimal representative") + + e = list(self.coefficients(f))[degree] + h = self.equivalence_reciprocal(e).map_coefficients(lambda c:_lift_to_maximal_precision(c)) + g = h*f + vg = self(g) + + coeffs = [c if v == vg else c.parent().zero() for v,c in zip(self.valuations(g), self.coefficients(g))] + coeffs[degree] = self.domain().base_ring().one() + ret = sum([c*self._phi**i for i,c in enumerate(coeffs)]) + + assert self.effective_degree(ret) == degree + assert ret.is_monic() + assert self.is_minimal(ret) + + from sage.structure.factorization import Factorization + ret = Factorization([(ret, 1)], unit=e, sort=False) + + assert self.is_equivalent(ret.prod(), f) # this might fail because of leading zeros + return ret + + @abstract_method + def lift_to_key(self, F): + """ + Lift the irreducible polynomial ``F`` from the + :meth:`~sage.rings.valuation.valuation_space.DiscretePseudoValuationSpace.ElementMethods.residue_ring` + to a key polynomial over this valuation. + + INPUT: + + - ``F`` -- an irreducible non-constant monic polynomial in + :meth:`~sage.rings.valuation.valuation_space.DiscretePseudoValuationSpace.ElementMethods.residue_ring` + of this valuation + + OUTPUT: + + A polynomial `f` in the domain of this valuation which is a key + polynomial for this valuation and which is such that an + :meth:`augmentation` with this polynomial adjoins a root of ``F`` to + the resulting :meth:`~sage.rings.valuation.valuation_space.DiscretePseudoValuationSpace.ElementMethods.residue_ring`. + + More specifically, if ``F`` is not the generator of the residue ring, + then multiplying ``f`` with the :meth:`~InductiveValuation.equivalence_reciprocal` of the + :meth:`~InductiveValuation.equivalence_unit` of the valuation of ``f``, produces a unit + which reduces to ``F``. + + EXAMPLES:: + + sage: R. = Qq(4,10) + sage: S. = R[] + sage: v = GaussValuation(S) + sage: y = v.residue_ring().gen() + sage: u0 = v.residue_ring().base_ring().gen() + sage: f = v.lift_to_key(y^2 + y + u0); f + (1 + O(2^10))*x^2 + (1 + O(2^10))*x + u + O(2^10) + + """ + + def _eliminate_denominators(self, f): + r""" + Return a polynomial in the domain of this valuation that + :meth:`is_equivalent` to ``f``. + + INPUT: + + - ``f`` -- a polynomial with coefficients in the fraction field of the + base ring of the domain of this valuation. + + EXAMPLES:: + + sage: R. = ZZ[] + sage: v = GaussValuation(R, ZZ.valuation(2)) + sage: v._eliminate_denominators(x/3) + x + + In general such a polynomial may not exist:: + + sage: w = v.augmentation(x, 1) + sage: w._eliminate_denominators(x/2) + Traceback (most recent call last): + ... + ValueError: element has no approximate inverse in this ring + + In general it exists iff the coefficients of minimal valuation in the + `\phi`-adic expansion of ``f`` do not have denominators of positive + valuation and if the same is true for these coefficients in their + expansion; at least if the coefficient ring's residue ring is already a + field:: + + sage: w._eliminate_denominators(x^3/2 + x) + x + + """ + if f in self.domain(): + return self.domain()(f) + + nonfractions = self.domain().base_ring() + fractions = nonfractions.fraction_field() + + extended_domain = self.domain().change_ring(fractions) + + g = extended_domain.coerce(f) + + w = self.extension(extended_domain) + # drop coefficients whose valuation is not minimal (recursively) + valuation = w(g) + g = w.simplify(g, error=valuation, force=True, phiadic=True) + + if g in self.domain(): + return self.domain()(g) + + nonfraction_valuation = self.restriction(nonfractions) + # if this fails then there is no equivalent polynomial in the domain of this valuation + ret = g.map_coefficients( + lambda c: c.numerator()*nonfraction_valuation.inverse(c.denominator(), + valuation + + nonfraction_valuation(c.denominator()) + - nonfraction_valuation(c.numerator()) + + nonfraction_valuation.value_group().gen()), + nonfractions) + assert w.is_equivalent(f, ret) + return ret + + + def _test_eliminate_denominators(self, **options): + r""" + Test the correctness of :meth:`_eliminate_denominators`. + + EXAMPLES:: + + sage: R. = ZZ[] + sage: v = GaussValuation(R, ZZ.valuation(2)) + sage: v._test_eliminate_denominators() + + """ + tester = self._tester(**options) + + nonfractions = self.domain().base_ring() + fractions = nonfractions.fraction_field() + extended_domain = self.domain().change_ring(fractions) + w = self.extension(extended_domain) + + S = tester.some_elements(w.domain().some_elements()) + for f in S: + try: + g = self._eliminate_denominators(f) + except ValueError: + continue + tester.assertTrue(g.parent() is self.domain()) + tester.assertTrue(w.is_equivalent(f, g)) + + def _test_lift_to_key(self, **options): + r""" + Test the correctness of :meth:`lift_to_key`. + + EXAMPLES:: + + sage: R. = QQ[] + sage: v = GaussValuation(R, valuations.TrivialValuation(QQ)) + sage: v._test_lift_to_key() + + """ + tester = self._tester(**options) + + try: + k = self.residue_ring() + except NotImplementedError: + from sage.categories.fields import Fields + if self.domain().base() in Fields(): + raise + return + + S = tester.some_elements(self.residue_ring().some_elements()) + for F in S: + if F.is_monic() and not F.is_constant() and F.is_irreducible(): + try: + f = self.lift_to_key(F) + except NotImplementedError: + from sage.categories.fields import Fields + if self.domain().base() in Fields(): + raise + continue + tester.assertIs(f.parent(), self.domain()) + tester.assertTrue(self.is_key(f)) + + # check that augmentation produces a valuation with roots of F + # in the residue ring + from sage.rings.all import infinity + w = self.augmentation(f, infinity) + F = F.change_ring(w.residue_ring()) + roots = F.roots(multiplicities=False) + tester.assertGreaterEqual(len(roots), 1) + + # check that f has the right reduction + if F == F.parent().gen(): + tester.assertTrue(self.is_equivalent(f, self.phi())) + else: + tester.assertEqual(self.reduce(f * self.equivalence_reciprocal(self.equivalence_unit(self(f)))), F) + + + def _test_is_equivalence_irreducible(self, **options): + r""" + Test the correctness of :meth:`is_equivalence_irreducible`. + + EXAMPLES:: + + sage: R. = QQ[] + sage: v = GaussValuation(R, valuations.TrivialValuation(QQ)) + sage: v._test_is_equivalence_irreducible() + + """ + tester = self._tester(**options) + S = tester.some_elements(self.domain().some_elements()) + for f in S: + if f.is_constant(): continue + is_equivalence_irreducible = self.is_equivalence_irreducible(f) + F = self.equivalence_decomposition(f) + tester.assertEqual(is_equivalence_irreducible, len(F)==0 or (len(F)==1 and F[0][1]==1)) + if self.is_equivalence_unit(f): + tester.assertTrue(f.is_constant() or self.is_equivalence_irreducible(f)) + + tester.assertTrue(self.is_equivalence_irreducible(self.phi())) + tester.assertTrue(self.is_equivalence_irreducible(-self.phi())) + tester.assertFalse(self.is_equivalence_irreducible(self.phi() ** 2)) + + +class FinalInductiveValuation(InductiveValuation): + r""" + Abstract base class for an inductive valuation which can not be augmented further. + + TESTS:: + + sage: R. = QQ[] + sage: v = GaussValuation(R, valuations.TrivialValuation(QQ)) + sage: w = v.augmentation(x^2 + x + 1, infinity) + sage: from sage.rings.valuation.inductive_valuation import FinalInductiveValuation + sage: isinstance(w, FinalInductiveValuation) + True + + """ + + +class InfiniteInductiveValuation(FinalInductiveValuation, InfiniteDiscretePseudoValuation): + r""" + Abstract base class for an inductive valuation which is not discrete, i.e., + which assigns infinite valuation to its last key polynomial. + + EXAMPLES:: + + sage: R. = QQ[] + sage: v = GaussValuation(R, QQ.valuation(2)) + sage: w = v.augmentation(x^2 + x + 1, infinity) + + """ + def __init__(self, parent, base_valuation): + r""" + TESTS:: + + sage: R. = QQ[] + sage: v = GaussValuation(R, QQ.valuation(2)) + sage: w = v.augmentation(x^2 + x + 1, infinity) + sage: from sage.rings.valuation.inductive_valuation import InfiniteInductiveValuation + sage: isinstance(w, InfiniteInductiveValuation) + True + + """ + FinalInductiveValuation.__init__(self, parent, base_valuation) + InfiniteDiscretePseudoValuation.__init__(self, parent) + + def change_domain(self, ring): + r""" + Return this valuation over ``ring``. + + EXAMPLES: + + We can turn an infinite valuation into a valuation on the quotient:: + + sage: R. = QQ[] + sage: v = GaussValuation(R, QQ.valuation(2)) + sage: w = v.augmentation(x^2 + x + 1, infinity) + sage: w.change_domain(R.quo(x^2 + x + 1)) + 2-adic valuation + + """ + from sage.rings.polynomial.polynomial_quotient_ring import is_PolynomialQuotientRing + if is_PolynomialQuotientRing(ring) and ring.base() is self.domain() and ring.modulus() == self.phi(): + return self.restriction(self.domain().base())._extensions_to_quotient(ring, approximants=[self])[0] + return super(InfiniteInductiveValuation, self).change_domain(ring) + + +def _lift_to_maximal_precision(c): + r""" + Lift ``c`` to maximal precision if the parent is not exact. + + EXAMPLES:: + + sage: R = Zp(2,5) + sage: x = R(1,2); x + 1 + O(2^2) + sage: from sage.rings.valuation.inductive_valuation import _lift_to_maximal_precision + sage: _lift_to_maximal_precision(x) + 1 + O(2^5) + + sage: x = 1 + sage: _lift_to_maximal_precision(x) + 1 + + """ + return c if c.parent().is_exact() else c.lift_to_precision() + diff --git a/src/sage/rings/valuation/limit_valuation.py b/src/sage/rings/valuation/limit_valuation.py new file mode 100644 index 00000000000..9a240749ab4 --- /dev/null +++ b/src/sage/rings/valuation/limit_valuation.py @@ -0,0 +1,948 @@ +# -*- coding: utf-8 -*- +r""" +Valuations which are defined as limits of valuations. + +The discrete valuation of a complete field extends uniquely to a finite field +extension. This is not the case anymore for fields which are not complete with +respect to their discrete valuation. In this case, the extensions essentially +correspond to the factors of the defining polynomial of the extension over the +completion. However, these factors only exist over the completion and this +makes it difficult to write down such valuations with a representation of +finite length. + +More specifically, let `v` be a discrete valuation on `K` and let `L=K[x]/(G)` +a finite extension thereof. An extension of `v` to `L` can be represented as a +discrete pseudo-valuation `w'` on `K[x]` which sends `G` to infinity. +However, such `w'` might not be described by an :mod:`augmented valuation ` +over a :mod:`Gauss valuation ` anymore. Instead, we may need to write is as a +limit of augmented valuations. + +The classes in this module provide the means of writing down such limits and +resulting valuations on quotients. + +AUTHORS: + +- Julian Rüth (2016-10-19): initial version + +EXAMPLES: + +In this function field, the unique place of ``K`` which corresponds to the zero +point has two extensions to ``L``. The valuations corresponding to these +extensions can only be approximated:: + + sage: K. = FunctionField(QQ) + sage: R. = K[] + sage: L. = K.extension(y^2 - x) + + sage: v = K.valuation(1) + sage: w = v.extensions(L); w + [[ (x - 1)-adic valuation, v(y + 1) = 1 ]-adic valuation, + [ (x - 1)-adic valuation, v(y - 1) = 1 ]-adic valuation] + +The same phenomenon can be observed for valuations on number fields:: + + sage: K = QQ + sage: R. = K[] + sage: L. = K.extension(t^2 + 1) + sage: v = QQ.valuation(5) + sage: w = v.extensions(L); w + [[ 5-adic valuation, v(t + 2) = 1 ]-adic valuation, + [ 5-adic valuation, v(t + 3) = 1 ]-adic valuation] + +.. NOTE:: + + We often rely on approximations of valuations even if we could represent the + valuation without using a limit. This is done to improve performance as many + computations already can be done correctly with an approximation:: + + sage: K. = FunctionField(QQ) + sage: R. = K[] + sage: L. = K.extension(y^2 - x) + + sage: v = K.valuation(1/x) + sage: w = v.extension(L); w + Valuation at the infinite place + sage: w._base_valuation._base_valuation._improve_approximation() + sage: w._base_valuation._base_valuation._approximation + [ Gauss valuation induced by Valuation at the infinite place, v(y) = 1/2, v(y^2 - 1/x) = +Infinity ] + +REFERENCES: + +Limits of inductive valuations are discussed in [Mac1936I]_ and [Mac1936II]_. An +overview can also be found in Section 4.6 of [Rüt2014]_. + +""" +#***************************************************************************** +# Copyright (C) 2016-2017 Julian Rüth +# +# Distributed under the terms of the GNU General Public License (GPL) +# as published by the Free Software Foundation; either version 2 of +# the License, or (at your option) any later version. +# http://www.gnu.org/licenses/ +#***************************************************************************** +from sage.misc.abstract_method import abstract_method +from valuation import DiscretePseudoValuation, InfiniteDiscretePseudoValuation, DiscreteValuation +from sage.structure.factory import UniqueFactory + +class LimitValuationFactory(UniqueFactory): + r""" + Return a limit valuation which sends the polynomial ``G`` to infinity and + is greater than or equal than ``base_valuation``. + + INPUT: + + - ``base_valuation`` -- a discrete (pseudo-)valuation on a polynomial ring + which is a discrete valuation on the coefficient ring which can be + unqiuely augmented (possibly only in the limit) to a pseudo-valuation + that sends ``G`` to infinity. + + - ``G`` -- a squarefree polynomial in the domain of ``base_valuation``. + + EXAMPLES:: + + sage: R. = QQ[] + sage: v = GaussValuation(R, QQ.valuation(2)) + sage: w = valuations.LimitValuation(v, x) + sage: w(x) + +Infinity + + """ + def create_key(self, base_valuation, G): + r""" + Create a key from the parameters of this valuation. + + EXAMPLES: + + Note that this does not normalize ``base_valuation`` in any way. It is + easily possible to create the same limit in two different ways:: + + sage: R. = QQ[] + sage: v = GaussValuation(R, QQ.valuation(2)) + sage: w = valuations.LimitValuation(v, x) # indirect doctest + sage: v = v.augmentation(x, infinity) + sage: u = valuations.LimitValuation(v, x) + sage: u == w + False + + The point here is that this is not meant to be invoked from user code. + But mostly from other factories which have made sure that the + parameters are normalized already. + + """ + if not base_valuation.restriction(G.parent().base_ring()).is_discrete_valuation(): + raise ValueError("base_valuation must be discrete on the coefficient ring.") + return base_valuation, G + + def create_object(self, version, key): + r""" + Create an object from ``key``. + + EXAMPLES:: + + sage: R. = QQ[] + sage: v = GaussValuation(R, QQ.valuation(2)) + sage: w = valuations.LimitValuation(v, x^2 + 1) # indirect doctest + + """ + base_valuation, G = key + from valuation_space import DiscretePseudoValuationSpace + parent = DiscretePseudoValuationSpace(base_valuation.domain()) + return parent.__make_element_class__(MacLaneLimitValuation)(parent, base_valuation, G) + +LimitValuation = LimitValuationFactory("sage.rings.valuation.limit_valuation.LimitValuation") + +class LimitValuation_generic(DiscretePseudoValuation): + r""" + Base class for limit valuations. + + A limit valuation is realized as an approximation of a valuation and means + to improve that approximation when necessary. + + EXAMPLES:: + + sage: K. = FunctionField(QQ) + sage: R. = K[] + sage: L. = K.extension(y^2 - x) + + sage: v = K.valuation(0) + sage: w = v.extension(L) + sage: w._base_valuation + [ Gauss valuation induced by (x)-adic valuation, v(y) = 1/2 , … ] + + The currently used approximation can be found in the ``_approximation`` + field:: + + sage: w._base_valuation._approximation + [ Gauss valuation induced by (x)-adic valuation, v(y) = 1/2 ] + + TESTS:: + + sage: from sage.rings.valuation.limit_valuation import LimitValuation_generic + sage: isinstance(w._base_valuation, LimitValuation_generic) + True + sage: TestSuite(w._base_valuation).run() # long time + + """ + def __init__(self, parent, approximation): + r""" + TESTS:: + + sage: R. = QQ[] + sage: K. = QQ.extension(x^2 + 1) + sage: v = K.valuation(2) + sage: from sage.rings.valuation.limit_valuation import LimitValuation_generic + sage: isinstance(v._base_valuation, LimitValuation_generic) + True + + """ + DiscretePseudoValuation.__init__(self, parent) + + self._initial_approximation = approximation + self._approximation = approximation + + def reduce(self, f, check=True): + r""" + Return the reduction of ``f`` as an element of the :meth:`~sage.rings.valuation.valuation_space.DiscretePseudoValuationSpace.ElementMethods.residue_ring`. + + INPUT: + + - ``f`` -- an element in the domain of this valuation of non-negative + valuation + + - ``check`` -- whether or not to check that ``f`` has non-negative + valuation (default: ``True``) + + EXAMPLES:: + + sage: K. = FunctionField(QQ) + sage: R. = K[] + sage: L. = K.extension(y^2 - (x - 1)) + + sage: v = K.valuation(0) + sage: w = v.extension(L) + sage: w.reduce(y) # indirect doctest + u1 + + """ + f = self.domain().coerce(f) + self._improve_approximation_for_reduce(f) + F = self._approximation.reduce(f, check=check) + return self.residue_ring()(F) + + def _call_(self, f): + r""" + Return the valuation of ``f``. + + EXAMPLES:: + + sage: K. = FunctionField(QQ) + sage: R. = K[] + sage: L. = K.extension(y^2 - x) + + sage: v = K.valuation(0) + sage: w = v.extension(L) + sage: w(y) # indirect doctest + 1/2 + + """ + self._improve_approximation_for_call(f) + return self._approximation(f) + + @abstract_method + def _improve_approximation_for_reduce(self, f): + r""" + Replace our approximation with a sufficiently precise approximation to + correctly compute the reduction of ``f``. + + EXAMPLES:: + + sage: K. = FunctionField(QQ) + sage: R. = K[] + sage: L. = K.extension(y^2 - (x - 1337)) + + For the unique extension over the place at 1337, the initial + approximation is sufficient to compute the reduction of ``y``:: + + sage: v = K.valuation(1337) + sage: w = v.extension(L) + sage: u = w._base_valuation + sage: u._approximation + [ Gauss valuation induced by (x - 1337)-adic valuation, v(y) = 1/2 ] + sage: w.reduce(y) + 0 + sage: u._approximation + [ Gauss valuation induced by (x - 1337)-adic valuation, v(y) = 1/2 ] + + However, at a place over 1341, the initial approximation is not sufficient + for some values (note that 1341-1337 is a square):: + + sage: v = K.valuation(1341) + sage: w = v.extensions(L)[1] + sage: u = w._base_valuation + sage: u._approximation + [ Gauss valuation induced by (x - 1341)-adic valuation, v(y - 2) = 1 ] + sage: w.reduce((y - 2) / (x - 1341)) # indirect doctest + 1/4 + sage: u._approximation + [ Gauss valuation induced by (x - 1341)-adic valuation, v(y - 1/4*x + 1333/4) = 2 ] + sage: w.reduce((y - 1/4*x + 1333/4) / (x - 1341)^2) # indirect doctest + -1/64 + sage: u._approximation + [ Gauss valuation induced by (x - 1341)-adic valuation, v(y + 1/64*x^2 - 1349/32*x + 1819609/64) = 3 ] + + """ + + @abstract_method + def _improve_approximation_for_call(self, f): + r""" + Replace our approximation with a sufficiently precise approximation to + correctly compute the valuation of ``f``. + + EXAMPLES:: + + sage: K. = FunctionField(QQ) + sage: R. = K[] + sage: L. = K.extension(y^2 - (x - 23)) + + For the unique extension over the place at 23, the initial + approximation is sufficient to compute all valuations:: + + sage: v = K.valuation(23) + sage: w = v.extension(L) + sage: u = w._base_valuation + sage: u._approximation + [ Gauss valuation induced by (x - 23)-adic valuation, v(y) = 1/2 ] + sage: w(x - 23) + 1 + sage: u._approximation + [ Gauss valuation induced by (x - 23)-adic valuation, v(y) = 1/2 ] + + However, due to performance reasons, sometimes we improve the + approximation though it would not have been necessary (performing the + improvement step is faster in this case than checking whether the + approximation is sufficient):: + + sage: w(y) # indirect doctest + 1/2 + sage: u._approximation + [ Gauss valuation induced by (x - 23)-adic valuation, v(y) = 1/2, v(y^2 - x + 23) = +Infinity ] + + """ + + def _repr_(self): + r""" + Return a printable representation of this valuation. + + EXAMPLES:: + + sage: K = QQ + sage: R. = K[] + sage: L. = K.extension(t^2 + 1) + sage: v = QQ.valuation(2) + sage: w = v.extension(L) + sage: w._base_valuation # indirect doctest + [ Gauss valuation induced by 2-adic valuation, v(t + 1) = 1/2 , … ] + + """ + from sage.rings.all import infinity + from augmented_valuation import AugmentedValuation_base + if self._initial_approximation(self._G) is not infinity: + if isinstance(self._initial_approximation, AugmentedValuation_base): + return repr(self._initial_approximation)[:-1] + ", … ]" + return repr(self._initial_approximation) + + +class MacLaneLimitValuation(LimitValuation_generic, InfiniteDiscretePseudoValuation): + r""" + A limit valuation that is a pseudo-valuation on polynomial ring `K[x]` + which sends a square-free polynomial `G` to infinity. + + This uses the MacLane algorithm to compute the next element in the limit. + + It starts from a first valuation ``approximation`` which has a unique + augmentation that sends `G` to infinity and whose uniformizer must be a + uniformizer of the limit and whose residue field must contain the residue + field of the limit. + + EXAMPLES:: + + sage: R. = QQ[] + sage: K. = QQ.extension(x^2 + 1) + + sage: v = K.valuation(2) + sage: u = v._base_valuation; u + [ Gauss valuation induced by 2-adic valuation, v(x + 1) = 1/2 , … ] + + """ + def __init__(self, parent, approximation, G): + r""" + TESTS:: + + sage: R. = QQ[] + sage: K. = QQ.extension(x^2 + 1) + sage: v = K.valuation(2) + sage: u = v._base_valuation + sage: from sage.rings.valuation.limit_valuation import MacLaneLimitValuation + sage: isinstance(u, MacLaneLimitValuation) + True + + """ + LimitValuation_generic.__init__(self, parent, approximation) + InfiniteDiscretePseudoValuation.__init__(self, parent) + + self._G = G + self._next_coefficients = None + self._next_valuations = None + + def extensions(self, ring): + r""" + Return the extensions of this valuation to ``ring``. + + EXAMPLES:: + + sage: v = GaussianIntegers().valuation(2) + sage: u = v._base_valuation + sage: u.extensions(QQ['x']) + [[ Gauss valuation induced by 2-adic valuation, v(x + 1) = 1/2 , … ]] + + """ + if self.domain() is ring: + return [self] + from sage.rings.polynomial.polynomial_ring import is_PolynomialRing + if is_PolynomialRing(ring) and self.domain().base_ring().is_subring(ring.base_ring()): + if self.domain().base_ring().fraction_field() is ring.base_ring(): + return [LimitValuation(self._initial_approximation.change_domain(ring), + self._G.change_ring(ring.base_ring()))] + else: + # we need to recompute the mac lane approximants over this base + # ring because it could split differently + pass + return super(MacLaneLimitValuation, self).extensions(ring) + + def lift(self, F): + r""" + Return a lift of ``F`` from the :meth:`~sage.rings.valuation.valuation_space.DiscretePseudoValuationSpace.ElementMethods.residue_ring` to the domain of + this valuatiion. + + EXAMPLES:: + + sage: K. = FunctionField(QQ) + sage: R. = K[] + sage: L. = K.extension(y^4 - x^2 - 2*x - 1) + + sage: v = K.valuation(1) + sage: w = v.extensions(L)[1]; w + [ (x - 1)-adic valuation, v(y^2 - 2) = 1 ]-adic valuation + sage: s = w.reduce(y); s + u1 + sage: w.lift(s) # indirect doctest + y + + """ + F = self.residue_ring().coerce(F) + return self._initial_approximation.lift(F) + + def uniformizer(self): + r""" + Return a uniformizing element for this valuation. + + EXAMPLES:: + + sage: K. = FunctionField(QQ) + sage: R. = K[] + sage: L. = K.extension(y^2 - x) + + sage: v = K.valuation(0) + sage: w = v.extension(L) + sage: w.uniformizer() # indirect doctest + y + + """ + return self._initial_approximation.uniformizer() + + def _call_(self, f): + r""" + Return the valuation of ``f``. + + EXAMPLES:: + + sage: K = QQ + sage: R. = K[] + sage: vK = K.valuation(2) + sage: f = (x^2 + 7) * (x^2 + 9) + sage: V = vK.mac_lane_approximants(f, require_incomparability=True) + + sage: w = valuations.LimitValuation(V[0], f) + sage: w((x^2 + 7) * (x + 3)) + 3/2 + + sage: w = valuations.LimitValuation(V[1], f) + sage: w((x^2 + 7) * (x + 3)) + +Infinity + + sage: w = valuations.LimitValuation(V[2], f) + sage: w((x^2 + 7) * (x + 3)) + +Infinity + + """ + self._improve_approximation_for_call(f) + if self._G.divides(f): + from sage.rings.all import infinity + return infinity + return self._approximation(f) + + def _improve_approximation(self): + r""" + Perform one step of the Mac Lane algorithm to improve our approximation. + + EXAMPLES:: + + sage: K = QQ + sage: R. = K[] + sage: L. = K.extension(t^2 + 1) + sage: v = QQ.valuation(2) + sage: w = v.extension(L) + sage: u = w._base_valuation + sage: u._approximation + [ Gauss valuation induced by 2-adic valuation, v(t + 1) = 1/2 ] + sage: u._improve_approximation() + sage: u._approximation + [ Gauss valuation induced by 2-adic valuation, v(t + 1) = 1/2, v(t^2 + 1) = +Infinity ] + + This method has no effect, if the approximation is already an infinite + valuation:: + + sage: u._improve_approximation() + sage: u._approximation + [ Gauss valuation induced by 2-adic valuation, v(t + 1) = 1/2, v(t^2 + 1) = +Infinity ] + + """ + from sage.rings.all import infinity + if self._approximation(self._G) is infinity: + # an infinite valuation can not be improved further + return + + approximations = self._approximation.mac_lane_step(self._G, + assume_squarefree=True, + assume_equivalence_irreducible=True, + check=False, + principal_part_bound=1 if self._approximation.E()*self._approximation.F() == self._approximation.phi().degree() else None, + report_degree_bounds_and_caches=True) + assert(len(approximations)==1) + self._approximation, _, _, self._next_coefficients, self._next_valuations = approximations[0] + + def _improve_approximation_for_call(self, f): + r""" + Replace our approximation with a sufficiently precise approximation to + correctly compute the valuation of ``f``. + + EXAMPLES: + + In this examples, the approximation is increased unnecessarily. The + first approximation would have been precise enough to compute the + valuation of ``t + 2``. However, it is faster to improve the + approximation (perform one step of the Mac Lane algorithm) than to + check for this:: + + sage: K = QQ + sage: R. = K[] + sage: L. = K.extension(t^2 + 1) + sage: v = QQ.valuation(5) + sage: w = v.extensions(L)[0] + sage: u = w._base_valuation + sage: u._approximation + [ Gauss valuation induced by 5-adic valuation, v(t + 2) = 1 ] + sage: w(t + 2) # indirect doctest + 1 + sage: u._approximation + [ Gauss valuation induced by 5-adic valuation, v(t + 7) = 2 ] + + ALGORITHM: + + Write `L=K[x]/(G)` and consider `g` a representative of the class + of ``f`` in `K[x]` (of minimal degree.) Write `v` for + ``self._approximation` and `\phi` for the last key polynomial of + `v`. With repeated quotient and remainder `g` has a unique + expansion as `g=\sum a_i\phi^i`. Suppose that `g` is an + equivalence-unit with respect to ``self._approximation``, i.e., + `v(a_0) < v(a_i\phi^i)` for all `i\ne 0`. If we denote the limit + valuation as `w`, then `v(a_i\phi^i)=w(a_i\phi^i)` since the + valuation of key polynomials does not change during augmentations + (Theorem 6.4 in [Mac1936II]_.) By the strict triangle inequality, + `w(g)=v(g)`. + Note that any `g` which is coprime to `G` is an equivalence-unit + after finitely many steps of the Mac Lane algorithm. Indeed, + otherwise the valuation of `g` would be infinite (follows from + Theorem 5.1 in [Mac1936II]_) since the valuation of the key + polynomials increases. + When `f` is not coprime to `G`, consider `s=gcd(f,G)` and write + `G=st`. Since `G` is squarefree, either `s` or `t` have finite + valuation. With the above algorithm, this can be decided in + finitely many steps. From this we can deduce the valuation of `s` + (and in fact replace `G` with the factor with infinite valuation + for all future computations.) + + """ + from sage.rings.all import infinity + if self._approximation(self._approximation.phi()) is infinity: + # an infinite valuation can not be improved further + return + + if f == 0: + # zero always has infinite valuation (actually, this might + # not be desirable for inexact zero elements with leading + # zero coefficients.) + return + + while not self._approximation.is_equivalence_unit(f): + # TODO: I doubt that this really works over inexact fields + s = self._G.gcd(f) + if s.is_constant(): + self._improve_approximation() + else: + t = self._G // s + + while True: + if self._approximation.is_equivalence_unit(s): + # t has infinite valuation + self._G = t + return self._improve_approximation_for_call(f // s) + if self._approximation.is_equivalence_unit(t): + # s has infinite valuation + self._G = s + return + + self._improve_approximation() + + def _improve_approximation_for_reduce(self, f): + r""" + Replace our approximation with a sufficiently precise approximation to + correctly compute the reduction of ``f``. + + EXAMPLES:: + + sage: K = QQ + sage: R. = K[] + sage: L. = K.extension(t^2 + 1) + sage: v = QQ.valuation(13) + sage: w = v.extensions(L)[0] + sage: u = w._base_valuation + sage: u._approximation + [ Gauss valuation induced by 13-adic valuation, v(t + 5) = 1 ] + sage: w.reduce((t + 5) / 13) # indirect doctest + 8 + sage: u._approximation + [ Gauss valuation induced by 13-adic valuation, v(t + 70) = 2 ] + + ALGORITHM: + + The reduction produced by the approximation is correct for an + equivalence-unit, see :meth:`_improve_approximation_for_call`. + + """ + if self._approximation(f) > 0: + return + self._improve_approximation_for_call(f) + + def residue_ring(self): + r""" + Return the residue ring of this valuation, which is always a field. + + EXAMPLES:: + + sage: K = QQ + sage: R. = K[] + sage: L. = K.extension(t^2 + 1) + sage: v = QQ.valuation(2) + sage: w = v.extension(L) + sage: w.residue_ring() + Finite Field of size 2 + + """ + R = self._initial_approximation.residue_ring() + from sage.categories.fields import Fields + if R in Fields(): + # the approximation ends in v(phi)=infty + return R + else: + from sage.rings.polynomial.polynomial_ring import is_PolynomialRing + assert(is_PolynomialRing(R)) + return R.base_ring() + + def _ge_(self, other): + r""" + Return whether this valuation is greater or equal than ``other`` + everywhere. + + EXAMPLES:: + + sage: R. = QQ[] + sage: F = (x^2 + 7) * (x^2 + 9) + sage: G = (x^2 + 7) + sage: V = QQ.valuation(2).mac_lane_approximants(F, require_incomparability=True) + sage: valuations.LimitValuation(V[0], F) >= valuations.LimitValuation(V[1], F) + False + sage: valuations.LimitValuation(V[1], F) >= valuations.LimitValuation(V[1], G) + True + sage: valuations.LimitValuation(V[2], F) >= valuations.LimitValuation(V[2], G) + True + + """ + if other.is_trivial(): + return other.is_discrete_valuation() + if isinstance(other, MacLaneLimitValuation): + if self._approximation.restriction(self._approximation.domain().base_ring()) == other._approximation.restriction(other._approximation.domain().base_ring()): + # Two MacLane limit valuations v,w over the same constant + # valuation are either equal or incomparable; neither v>w nor + # v= other._initial_approximation + or self._initial_approximation <= other._initial_approximation) + + return super(MacLaneLimitValuation, self)._ge_(other) + + def restriction(self, ring): + r""" + Return the restriction of this valuation to ``ring``. + + EXAMPLES:: + + sage: K = QQ + sage: R. = K[] + sage: L. = K.extension(t^2 + 1) + sage: v = QQ.valuation(2) + sage: w = v.extension(L) + sage: w._base_valuation.restriction(K) + 2-adic valuation + + """ + if ring.is_subring(self.domain().base()): + return self._initial_approximation.restriction(ring) + return super(MacLaneLimitValuation, self).restriction(ring) + + def _weakly_separating_element(self, other): + r""" + Return an element in the domain of this valuation which has + positive valuation with respect to this valuation and higher + valuation with respect to this valuation than with respect to + ``other``. + + EXAMPLES:: + + sage: K = QQ + sage: R. = K[] + sage: L. = K.extension(t^2 + 1) + sage: v = QQ.valuation(2) + sage: w = v.extension(L) + sage: v = QQ.valuation(5) + sage: u,uu = v.extensions(L) + sage: w._base_valuation._weakly_separating_element(u._base_valuation) # long time + 2 + sage: u._base_valuation._weakly_separating_element(uu._base_valuation) # long time + t + 2 + + sage: K. = FunctionField(QQ) + sage: v = K.valuation(1/x) + sage: R. = K[] + sage: L. = K.extension(y^2 - 1/(x^2 + 1)) + sage: u,uu = v.extensions(L) + sage: v = K.valuation(x) + sage: w,ww = v.extensions(L) + sage: v = K.valuation(1) + sage: v = v.extension(L) + sage: u.separating_element([uu,ww,w,v]) # long time, random output + ((8*x^4 + 12*x^2 + 4)/(x^2 - x))*y + (8*x^4 + 8*x^2 + 1)/(x^3 - x^2) + + The underlying algorithm is quite naive and might not terminate in + reasonable time. In particular, the order of the arguments sometimes + has a huge impact on the runtime:: + + sage: u.separating_element([ww,w,v,uu]) # not tested, takes forever + + """ + from scaled_valuation import ScaledValuation_generic + v = self.restriction(self.domain().base()) + if isinstance(v, ScaledValuation_generic): + v = v._base_valuation + u = other.restriction(self.domain().base()) + if isinstance(u, ScaledValuation_generic): + u = u._base_valuation + + if u == v: + # phi of the initial approximant must be good enough to separate it + # from any other approximant of an extension + ret = self._initial_approximation.phi() + assert(self(ret) > other(ret)) # I could not come up with an example where this fails + return ret + else: + # if the valuations are sane, it should be possible to separate + # them with constants + return self.domain()(v._weakly_separating_element(u)) + + def value_semigroup(self): + r""" + Return the value semigroup of this valuation. + + TESTS:: + + sage: K = QQ + sage: R. = K[] + sage: L. = K.extension(t^2 + 1) + sage: v = QQ.valuation(5) + sage: u,uu = v.extensions(L) + sage: u.value_semigroup() + Additive Abelian Semigroup generated by -1, 1 + + """ + return self._initial_approximation.value_semigroup() + + def element_with_valuation(self, s): + r""" + Return an element with valuation ``s``. + + TESTS:: + + sage: K = QQ + sage: R. = K[] + sage: L. = K.extension(t^2 + 1) + sage: v = QQ.valuation(2) + sage: u = v.extension(L) + sage: u.element_with_valuation(1/2) + t + 1 + + """ + return self._initial_approximation.element_with_valuation(s) + + def _relative_size(self, f): + r""" + Return an estimate on the coefficient size of ``f``. + + The number returned is an estimate on the factor between the number of + bits used by ``f`` and the minimal number of bits used by an element + congruent to ``f``. + + This is used by :meth:`simplify` to decide whether simplification of + coefficients is going to lead to a significant shrinking of the + coefficients of ``f``. + + EXAMPLES:: + + sage: K = QQ + sage: R. = K[] + sage: L. = K.extension(t^2 + 1) + sage: v = QQ.valuation(2) + sage: u = v.extension(L) + sage: u._relative_size(1024*t + 1024) + 6 + + """ + return self._initial_approximation._relative_size(f) + + def simplify(self, f, error=None, force=False): + r""" + Return a simplified version of ``f``. + + Produce an element which differs from ``f`` by an element of valuation + strictly greater than the valuation of ``f`` (or strictly greater than + ``error`` if set.) + + EXAMPLES:: + + sage: K = QQ + sage: R. = K[] + sage: L. = K.extension(t^2 + 1) + sage: v = QQ.valuation(2) + sage: u = v.extension(L) + sage: u.simplify(t + 1024, force=True) + t + + """ + f = self.domain().coerce(f) + + self._improve_approximation_for_call(f) + # now _approximation is sufficiently precise to compute a valid + # simplification of f + + if error is None: + error = self.upper_bound(f) + + return self._approximation.simplify(f, error, force=force) + + def lower_bound(self, f): + r""" + Return a lower bound of this valuation at ``x``. + + Use this method to get an approximation of the valuation of ``x`` + when speed is more important than accuracy. + + EXAMPLES:: + + sage: K = QQ + sage: R. = K[] + sage: L. = K.extension(t^2 + 1) + sage: v = QQ.valuation(2) + sage: u = v.extension(L) + sage: u.lower_bound(1024*t + 1024) + 10 + sage: u(1024*t + 1024) + 21/2 + + """ + f = self.domain().coerce(f) + return self._approximation.lower_bound(f) + + def upper_bound(self, f): + r""" + Return an upper bound of this valuation at ``x``. + + Use this method to get an approximation of the valuation of ``x`` + when speed is more important than accuracy. + + EXAMPLES:: + + sage: K = QQ + sage: R. = K[] + sage: L. = K.extension(t^2 + 1) + sage: v = QQ.valuation(2) + sage: u = v.extension(L) + sage: u.upper_bound(1024*t + 1024) + 21/2 + sage: u(1024*t + 1024) + 21/2 + + """ + f = self.domain().coerce(f) + self._improve_approximation_for_call(f) + return self._approximation.upper_bound(f) + + def is_negative_pseudo_valuation(self): + r""" + Return whether this valuation attains `-\infty`. + + EXAMPLES: + + For a Mac Lane limit valuation, this is never the case, so this + method always returns ``False``:: + + sage: K = QQ + sage: R. = K[] + sage: L. = K.extension(t^2 + 1) + sage: v = QQ.valuation(2) + sage: u = v.extension(L) + sage: u.is_negative_pseudo_valuation() + False + + """ + return False diff --git a/src/sage/rings/valuation/mapped_valuation.py b/src/sage/rings/valuation/mapped_valuation.py new file mode 100644 index 00000000000..d6dc170056d --- /dev/null +++ b/src/sage/rings/valuation/mapped_valuation.py @@ -0,0 +1,594 @@ +# -*- coding: utf-8 -*- +r""" +Valuations which are implemented through a map to another valuation + +EXAMPLES: + +Extensions of valuations over finite field extensions `L=K[x]/(G)` are realized +through an infinite valuation on `K[x]` which maps `G` to infinity:: + + sage: K. = FunctionField(QQ) + sage: R. = K[] + sage: L. = K.extension(y^2 - x) + + sage: v = K.valuation(0) + sage: w = v.extension(L); w + (x)-adic valuation + + sage: w._base_valuation + [ Gauss valuation induced by (x)-adic valuation, v(y) = 1/2 , … ] + +AUTHORS: + +- Julian Rüth (2016-11-10): initial version + +""" +#***************************************************************************** +# Copyright (C) 2016-2017 Julian Rüth +# +# Distributed under the terms of the GNU General Public License (GPL) +# as published by the Free Software Foundation; either version 2 of +# the License, or (at your option) any later version. +# http://www.gnu.org/licenses/ +#***************************************************************************** +from valuation import DiscreteValuation, DiscretePseudoValuation +from sage.misc.abstract_method import abstract_method + +class MappedValuation_base(DiscretePseudoValuation): + r""" + A valuation which is implemented through another proxy "base" valuation. + + EXAMPLES:: + + sage: K. = FunctionField(QQ) + sage: R. = K[] + sage: L. = K.extension(y^2 - x) + + sage: v = K.valuation(0) + sage: w = v.extension(L); w + (x)-adic valuation + + TESTS:: + + sage: TestSuite(w).run() # long time + + """ + def __init__(self, parent, base_valuation): + r""" + .. TODO:: + + It is annoying that we have to wrap any possible method on + ``base_valuation`` in this class. It would be nice if this would + somehow be done automagically, e.g., by adding annotations to the + methods in ``base_valuation`` that explain which parameters and + return values need to be mapped and how. + + TESTS:: + + sage: K. = FunctionField(QQ) + sage: R. = K[] + sage: L. = K.extension(y^2 - x^2 + 1) + + sage: v = K.valuation(0) + sage: w = v.extension(L); w + (x)-adic valuation + sage: from sage.rings.valuation.mapped_valuation import MappedValuation_base + sage: isinstance(w, MappedValuation_base) + True + + """ + DiscretePseudoValuation.__init__(self, parent) + + self._base_valuation = base_valuation + + @abstract_method + def _repr_(self): + r""" + Return a printable representation of this valuation. + + Subclasses must override this method. + + EXAMPLES:: + + sage: K = QQ + sage: R. = K[] + sage: L. = K.extension(t^2 + 1) + sage: v = valuations.pAdicValuation(QQ, 2) + sage: v.extension(L) # indirect doctest + 2-adic valuation + + """ + + def residue_ring(self): + r""" + Return the residue ring of this valuation. + + EXAMPLES:: + + sage: K = QQ + sage: R. = K[] + sage: L. = K.extension(t^2 + 1) + sage: v = valuations.pAdicValuation(QQ, 2) + sage: v.extension(L).residue_ring() + Finite Field of size 2 + + """ + return self._base_valuation.residue_ring() + + def uniformizer(self): + r""" + Return a uniformizing element of this valuation. + + EXAMPLES:: + + sage: K = QQ + sage: R. = K[] + sage: L. = K.extension(t^2 + 1) + sage: v = valuations.pAdicValuation(QQ, 2) + sage: v.extension(L).uniformizer() + t + 1 + + """ + return self._from_base_domain(self._base_valuation.uniformizer()) + + def _to_base_domain(self, f): + r""" + Return ``f`` as an element in the domain of ``_base_valuation``. + + EXAMPLES:: + + sage: K. = FunctionField(QQ) + sage: R. = K[] + sage: L. = K.extension(y^2 - x) + + sage: v = K.valuation(0) + sage: w = v.extensions(L)[0] + sage: w._to_base_domain(y).parent() + Univariate Polynomial Ring in y over Rational function field in x over Rational Field + + """ + return self._base_valuation.domain().coerce(f) + + def _from_base_domain(self, f): + r""" + Return ``f`` as an element in the domain of this valuation. + + EXAMPLES:: + + sage: K. = FunctionField(QQ) + sage: R. = K[] + sage: L. = K.extension(y^2 - x) + + sage: v = K.valuation(0) + sage: w = v.extension(L) + sage: w._from_base_domain(w._base_valuation.domain().gen()).parent() + Function field in y defined by y^2 - x + + """ + return self.domain().coerce(f) + + def _call_(self, f): + r""" + Evaluate this valuation at ``f``. + + EXAMPLES:: + + sage: K. = FunctionField(QQ) + sage: R. = K[] + sage: L. = K.extension(y^2 - x) + + sage: v = K.valuation(0) + sage: w = v.extension(L) + sage: w(y) # indirect doctest + 1/2 + + """ + return self._base_valuation(self._to_base_domain(f)) + + def reduce(self, f): + r""" + Return the reduction of ``f`` in the :meth:`~sage.rings.valuation.valuation_space.DiscretePseudoValuationSpace.ElementMethods.residue_field` of this valuation. + + EXAMPLES:: + + sage: K. = FunctionField(QQ) + sage: R. = K[] + sage: L. = K.extension(y^2 - (x - 2)) + + sage: v = K.valuation(0) + sage: w = v.extension(L) + sage: w.reduce(y) + u1 + + """ + return self._from_base_residue_ring(self._base_valuation.reduce(self._to_base_domain(f))) + + def lift(self, F): + r""" + Lift ``F`` from the :meth:`~sage.rings.valuation.valuation_space.DiscretePseudoValuationSpace.ElementMethods.residue_field` + of this valuation into its domain. + + EXAMPLES:: + + sage: K. = FunctionField(QQ) + sage: R. = K[] + sage: L. = K.extension(y^2 - x) + + sage: v = K.valuation(2) + sage: w = v.extension(L) + sage: w.lift(w.residue_field().gen()) + y + + """ + F = self.residue_ring().coerce(F) + F = self._to_base_residue_ring(F) + f = self._base_valuation.lift(F) + return self._from_base_domain(f) + + def _to_base_residue_ring(self, F): + r""" + Return ``F``, an element of :meth:`~sage.rings.valuation.valuation_space.DiscretePseudoValuationSpace.ElementMethods.residue_ring`, + as an element of the residue ring of the ``_base_valuation``. + + EXAMPLES:: + + sage: K. = FunctionField(QQ) + sage: R. = K[] + sage: L. = K.extension(y^2 - x) + + sage: v = K.valuation(0) + sage: w = v.extensions(L)[0] + sage: w._to_base_residue_ring(1) + 1 + + """ + return self._base_valuation.residue_ring().coerce(F) + + def _from_base_residue_ring(self, F): + r""" + Return ``F``, an element of the residue ring of ``_base_valuation``, as + an element of this valuation's :meth:`~sage.rings.valuation.valuation_space.DiscretePseudoValuationSpace.ElementMethods.residue_ring`. + + EXAMPLES:: + + sage: K. = FunctionField(QQ) + sage: R. = K[] + sage: L. = K.extension(y^2 - x) + + sage: v = K.valuation(0) + sage: w = v.extensions(L)[0] + sage: w._from_base_residue_ring(1) + 1 + + """ + return self.residue_ring().coerce(F) + + def _test_to_from_base_domain(self, **options): + r""" + Check the correctness of :meth:`to_base_domain` and + :meth:`from_base_domain`. + + EXAMPLES:: + + sage: K. = FunctionField(QQ) + sage: R. = K[] + sage: L. = K.extension(y^2 - x) + + sage: v = K.valuation(0) + sage: w = v.extensions(L)[0] + sage: w._test_to_from_base_domain() + + """ + tester = self._tester(**options) + + for x in tester.some_elements(self.domain().some_elements()): + tester.assertEqual(x, self._from_base_domain(self._to_base_domain(x))) + # note that the converse might not be true + + def _test_to_from_base_residue_ring(self, **options): + r""" + Check the correctness of :meth:`to_base_residue_ring` and + :meth:`from_base_residue_ring`. + + EXAMPLES:: + + sage: K. = FunctionField(QQ) + sage: R. = K[] + sage: L. = K.extension(y^2 - x) + + sage: v = K.valuation(0) + sage: w = v.extensions(L)[0] + sage: w._test_to_from_base_residue_ring() + + """ + tester = self._tester(**options) + + for x in tester.some_elements(self.residue_ring().some_elements()): + tester.assertEqual(x, self._from_base_residue_ring(self._to_base_residue_ring(x))) + for x in tester.some_elements(self._base_valuation.residue_ring().some_elements()): + tester.assertEqual(x, self._to_base_residue_ring(self._from_base_residue_ring(x))) + + +class FiniteExtensionFromInfiniteValuation(MappedValuation_base, DiscreteValuation): + r""" + A valuation on a quotient of the form `L=K[x]/(G)` with an irreducible `G` + which is internally backed by a pseudo-valuations on `K[x]` which sends `G` + to infinity. + + INPUT: + + - ``parent`` -- the containing valuation space (usually the space of + discrete valuations on `L`) + + - ``base_valuation`` -- an infinite valuation on `K[x]` which takes `G` to + infinity + + EXAMPLES:: + + sage: K. = FunctionField(QQ) + sage: R. = K[] + sage: L. = K.extension(y^2 - x) + + sage: v = K.valuation(0) + sage: w = v.extension(L); w + (x)-adic valuation + + """ + def __init__(self, parent, base_valuation): + r""" + TESTS:: + + sage: K. = FunctionField(QQ) + sage: R. = K[] + sage: L. = K.extension(y^2 - x) + + sage: v = K.valuation(0) + sage: w = v.extension(L) + sage: from sage.rings.valuation.mapped_valuation import FiniteExtensionFromInfiniteValuation + sage: isinstance(w, FiniteExtensionFromInfiniteValuation) + True + sage: TestSuite(w).run() # long time + + """ + MappedValuation_base.__init__(self, parent, base_valuation) + DiscreteValuation.__init__(self, parent) + + def _eq_(self, other): + r""" + Return whether this valuation is indistinguishable from ``other``. + + EXAMPLES:: + + sage: K = QQ + sage: R. = K[] + sage: L. = K.extension(t^2 + 1) + sage: v = valuations.pAdicValuation(QQ, 2) + sage: w = v.extension(L) + sage: ww = v.extension(L) + sage: w == ww # indirect doctest + True + + """ + return (isinstance(other, FiniteExtensionFromInfiniteValuation) + and self._base_valuation == other._base_valuation) + + def restriction(self, ring): + r""" + Return the restriction of this valuation to ``ring``. + + EXAMPLES:: + + sage: K = QQ + sage: R. = K[] + sage: L. = K.extension(t^2 + 1) + sage: v = valuations.pAdicValuation(QQ, 2) + sage: w = v.extension(L) + sage: w.restriction(K) is v + True + + """ + if ring.is_subring(self._base_valuation.domain().base()): + return self._base_valuation.restriction(ring) + return super(FiniteExtensionFromInfiniteValuation, self).restriction(ring) + + def _weakly_separating_element(self, other): + r""" + Return an element in the domain of this valuation which has + positive valuation with respect to this valuation and higher + valuation with respect to this valuation than with respect to + ``other``. + + EXAMPLES:: + + sage: K = QQ + sage: R. = K[] + sage: L. = K.extension(t^2 + 1) + sage: v = valuations.pAdicValuation(QQ, 2) + sage: w = v.extension(L) + sage: v = valuations.pAdicValuation(QQ, 5) + sage: u,uu = v.extensions(L) + sage: u.separating_element([w,uu]) # indirect doctest + 1/20*t + 7/20 + + """ + if isinstance(other, FiniteExtensionFromInfiniteValuation): + return self.domain()(self._base_valuation._weakly_separating_element(other._base_valuation)) + super(FiniteExtensionFromInfiniteValuation, self)._weakly_separating_element(other) + + def _relative_size(self, x): + r""" + Return an estimate on the coefficient size of ``x``. + + The number returned is an estimate on the factor between the number of + bits used by ``x`` and the minimal number of bits used by an element + congruent to ``x``. + + This is used by :meth:`simplify` to decide whether simplification of + coefficients is going to lead to a significant shrinking of the + coefficients of ``x``. + + EXAMPLES:: + + sage: K = QQ + sage: R. = K[] + sage: L. = K.extension(t^2 + 1) + sage: v = valuations.pAdicValuation(QQ, 2) + sage: w = v.extension(L) + sage: w._relative_size(1024*t + 1024) + 6 + + """ + return self._base_valuation._relative_size(self._to_base_domain(x)) + + def simplify(self, x, error=None, force=False): + r""" + Return a simplified version of ``x``. + + Produce an element which differs from ``x`` by an element of + valuation strictly greater than the valuation of ``x`` (or strictly + greater than ``error`` if set.) + + EXAMPLES:: + + sage: K = QQ + sage: R. = K[] + sage: L. = K.extension(t^2 + 1) + sage: v = valuations.pAdicValuation(QQ, 5) + sage: u,uu = v.extensions(L) + sage: f = 125*t + 1 + sage: u.simplify(f, error=u(f), force=True) + 1 + + """ + x = self.domain().coerce(x) + + if error is None: + error = self.upper_bound(x) + + return self._from_base_domain(self._base_valuation.simplify(self._to_base_domain(x), error, force=force)) + + def lower_bound(self, x): + r""" + Return an lower bound of this valuation at ``x``. + + Use this method to get an approximation of the valuation of ``x`` + when speed is more important than accuracy. + + EXAMPLES:: + + sage: K = QQ + sage: R. = K[] + sage: L. = K.extension(t^2 + 1) + sage: v = valuations.pAdicValuation(QQ, 5) + sage: u,uu = v.extensions(L) + sage: u.lower_bound(t + 2) + 0 + sage: u(t + 2) + 1 + + """ + x = self.domain().coerce(x) + return self._base_valuation.lower_bound(self._to_base_domain(x)) + + def upper_bound(self, x): + r""" + Return an upper bound of this valuation at ``x``. + + Use this method to get an approximation of the valuation of ``x`` + when speed is more important than accuracy. + + EXAMPLES:: + + sage: K = QQ + sage: R. = K[] + sage: L. = K.extension(t^2 + 1) + sage: v = valuations.pAdicValuation(QQ, 5) + sage: u,uu = v.extensions(L) + sage: u.upper_bound(t + 2) >= 1 + True + sage: u(t + 2) + 1 + + """ + x = self.domain().coerce(x) + return self._base_valuation.upper_bound(self._to_base_domain(x)) + + +class FiniteExtensionFromLimitValuation(FiniteExtensionFromInfiniteValuation): + r""" + An extension of a valuation on a finite field extensions `L=K[x]/(G)` which + is induced by an infinite limit valuation on `K[x]`. + + EXAMPLES:: + + sage: K. = FunctionField(QQ) + sage: R. = K[] + sage: L. = K.extension(y^2 - x) + sage: v = K.valuation(1) + sage: w = v.extensions(L); w + [[ (x - 1)-adic valuation, v(y + 1) = 1 ]-adic valuation, + [ (x - 1)-adic valuation, v(y - 1) = 1 ]-adic valuation] + + TESTS:: + + sage: TestSuite(w[0]).run() # long time + sage: TestSuite(w[1]).run() # long time + + """ + def __init__(self, parent, approximant, G, approximants): + r""" + EXAMPLES: + + Note that this implementation is also used when the underlying limit is + only taken over a finite sequence of valuations:: + + sage: K. = FunctionField(QQ) + sage: R. = K[] + sage: L. = K.extension(y^2 - x) + sage: v = K.valuation(2) + sage: w = v.extension(L); w + (x - 2)-adic valuation + sage: from sage.rings.valuation.mapped_valuation import FiniteExtensionFromLimitValuation + sage: isinstance(w, FiniteExtensionFromLimitValuation) + True + + """ + # keep track of all extensions to this field extension so we can print + # this valuation nicely, dropping any unnecessary information + self._approximants = approximants + + from valuation_space import DiscretePseudoValuationSpace + from limit_valuation import LimitValuation + limit = LimitValuation(approximant, G) + FiniteExtensionFromInfiniteValuation.__init__(self, parent, limit) + + def _repr_(self): + """ + Return a printable representation of this valuation. + + EXAMPLES:: + + sage: valuations.pAdicValuation(GaussianIntegers().fraction_field(), 2) # indirect doctest + 2-adic valuation + + """ + from limit_valuation import MacLaneLimitValuation + if isinstance(self._base_valuation, MacLaneLimitValuation): + # print the minimal information that singles out this valuation from all approximants + assert(self._base_valuation._initial_approximation in self._approximants) + approximants = [v.augmentation_chain()[::-1] for v in self._approximants] + augmentations = self._base_valuation._initial_approximation.augmentation_chain()[::-1] + unique_approximant = None + for l in range(len(augmentations)): + if len([a for a in approximants if a[:l+1] == augmentations[:l+1]]) == 1: + unique_approximant = augmentations[:l+1] + break + assert(unique_approximant is not None) + if unique_approximant[0].is_gauss_valuation(): + unique_approximant[0] = unique_approximant[0].restriction(unique_approximant[0].domain().base_ring()) + if len(unique_approximant) == 1: + return repr(unique_approximant[0]) + from augmented_valuation import AugmentedValuation_base + return "[ %s ]-adic valuation"%(", ".join("v(%r) = %r"%(v._phi, v._mu) if (isinstance(v, AugmentedValuation_base) and v.domain() == self._base_valuation.domain()) else repr(v) for v in unique_approximant)) + return "%s-adic valuation"%(self._base_valuation) + diff --git a/src/sage/rings/valuation/scaled_valuation.py b/src/sage/rings/valuation/scaled_valuation.py new file mode 100644 index 00000000000..2b499cf1d54 --- /dev/null +++ b/src/sage/rings/valuation/scaled_valuation.py @@ -0,0 +1,334 @@ +# -*- coding: utf-8 -*- +r""" +Valuations which are scaled versions of another valuation + +EXAMPLES:: + + sage: 3*ZZ.valuation(3) + 3 * 3-adic valuation + +AUTHORS: + +- Julian Rüth (2016-11-10): initial version + +""" +#***************************************************************************** +# Copyright (C) 2016-2017 Julian Rüth +# +# Distributed under the terms of the GNU General Public License (GPL) +# as published by the Free Software Foundation; either version 2 of +# the License, or (at your option) any later version. +# http://www.gnu.org/licenses/ +#***************************************************************************** +from sage.structure.factory import UniqueFactory + +from valuation import DiscreteValuation + +class ScaledValuationFactory(UniqueFactory): + r""" + Return a valuation which scales the valuation ``base`` by the factor ``s``. + + EXAMPLES:: + + sage: 3*ZZ.valuation(2) # indirect doctest + 3 * 2-adic valuation + + """ + def create_key(self, base, s): + r""" + Create a key which uniquely identifies a valuation. + + TESTS:: + + sage: 3*ZZ.valuation(2) is 2*(3/2*ZZ.valuation(2)) # indirect doctest + True + + """ + from sage.rings.all import infinity, QQ + if s is infinity or s not in QQ or s <= 0: + # for these values we can not return a TrivialValuation() in + # create_object() because that would override that instance's + # _factory_data and lead to pickling errors + raise ValueError("s must be a positive rational") + if base.is_trivial(): + # for the same reason we can not accept trivial valuations here + raise ValueError("base must not be trivial") + s = QQ.coerce(s) + if s == 1: + # we would override the _factory_data of base if we just returned + # it in create_object() so we just refuse to do so + raise ValueError("s must not be 1") + + if isinstance(base, ScaledValuation_generic): + return self.create_key(base._base_valuation, s*base._scale) + + return base, s + + def create_object(self, version, key): + r""" + Create a valuation from ``key``. + + TESTS:: + + sage: 3*ZZ.valuation(2) # indirect doctest + 3 * 2-adic valuation + + """ + base, s = key + + assert not isinstance(base, ScaledValuation_generic) + + from valuation_space import DiscretePseudoValuationSpace + parent = DiscretePseudoValuationSpace(base.domain()) + return parent.__make_element_class__(ScaledValuation_generic)(parent, base, s) + + +ScaledValuation = ScaledValuationFactory("sage.rings.valuation.scaled_valuation.ScaledValuation") + +class ScaledValuation_generic(DiscreteValuation): + r""" + A valuation which scales another ``base_valuation`` by a finite positive factor ``s``. + + EXAMPLES:: + + sage: v = 3*ZZ.valuation(3); v + 3 * 3-adic valuation + + TESTS:: + + sage: TestSuite(v).run() # long time + + """ + def __init__(self, parent, base_valuation, s): + r""" + .. TODO:: + + It is annoying that we have to wrap any possible method on + ``base_valuation`` in this class. It would be nice if this would + somehow be done automagically, e.g., by adding annotations to the + methods in ``base_valuation`` that explain which parameters and + return values need to be scaled. + + TESTS:: + + sage: v = 3*ZZ.valuation(2) + sage: from sage.rings.valuation.scaled_valuation import ScaledValuation_generic + sage: isinstance(v, ScaledValuation_generic) + True + + """ + DiscreteValuation.__init__(self, parent) + + self._base_valuation = base_valuation + self._scale = s + + def _repr_(self): + r""" + Return a printable representation of this valuation. + + EXAMPLES:: + + sage: 3*ZZ.valuation(2) # indirect doctest + 3 * 2-adic valuation + + """ + return "%r * %r"%(self._scale, self._base_valuation) + + def residue_ring(self): + r""" + Return the residue field of this valuation. + + EXAMPLES:: + + sage: v = 3*ZZ.valuation(2) + sage: v.residue_ring() + Finite Field of size 2 + + """ + return self._base_valuation.residue_ring() + + def uniformizer(self): + r""" + Return a uniformizing element of this valuation. + + EXAMPLES:: + + sage: v = 3*ZZ.valuation(2) + sage: v.uniformizer() + 2 + + """ + return self._base_valuation.uniformizer() + + def _call_(self, f): + r""" + Evaluate this valuation at ``f``. + + EXAMPLES:: + + sage: v = 3*ZZ.valuation(2) + sage: v(2) + 3 + + """ + return self._scale * self._base_valuation(f) + + def reduce(self, f): + r""" + Return the reduction of ``f`` in the :meth:`~sage.rings.valuation.valuation_space.DiscretePseudoValuationSpace.ElementMethods.residue_field` of this valuation. + + EXAMPLES:: + + sage: v = 3*ZZ.valuation(2) + sage: v.reduce(1) + 1 + + """ + return self._base_valuation.reduce(f) + + def lift(self, F): + r""" + Lift ``F`` from the :meth:`~sage.rings.valuation.valuation_space.DiscretePseudoValuationSpace.ElementMethods.residue_field` + of this valuation into its + domain. + + EXAMPLES:: + + sage: v = 3*ZZ.valuation(2) + sage: v.lift(1) + 1 + + """ + return self._base_valuation.lift(F) + + def extensions(self, ring): + r""" + Return the extensions of this valuation to ``ring``. + + EXAMPLES:: + + sage: v = 3*ZZ.valuation(5) + sage: v.extensions(GaussianIntegers().fraction_field()) + [3 * [ 5-adic valuation, v(x + 2) = 1 ]-adic valuation, + 3 * [ 5-adic valuation, v(x + 3) = 1 ]-adic valuation] + + """ + return [ScaledValuation(w, self._scale) for w in self._base_valuation.extensions(ring)] + + def restriction(self, ring): + r""" + Return the restriction of this valuation to ``ring``. + + EXAMPLES:: + + sage: v = 3*QQ.valuation(5) + sage: v.restriction(ZZ) + 3 * 5-adic valuation + + """ + return ScaledValuation(self._base_valuation.restriction(ring), self._scale) + + def _strictly_separating_element(self, other): + r""" + Return an element in the domain of this valuation which has positive + valuation with respect to this valuation but negative valuation with + respect to ``other``. + + EXAMPLES:: + + sage: v2 = QQ.valuation(2) + sage: v3 = 12 * QQ.valuation(3) + sage: v2._strictly_separating_element(v3) + 2/3 + + """ + return self._base_valuation._strictly_separating_element(other) + + def _weakly_separating_element(self, other): + r""" + Return an element in the domain of this valuation which has + positive valuation with respect to this valuation and higher + valuation with respect to this valuation than with respect to + ``other``. + + EXAMPLES:: + + sage: v2 = QQ.valuation(2) + sage: v3 = 12 * QQ.valuation(3) + sage: v2._weakly_separating_element(v3) + 2 + + """ + return self._base_valuation._weakly_separating_element(other) + + def _ge_(self, other): + r""" + Return whether this valuation is greater or equal to ``other``, a + valuation on the same domain. + + EXAMPLES:: + + sage: v2 = QQ.valuation(2) + sage: 2*v2 >= v2 + True + sage: v2/2 >= 2*v2 + False + sage: 3*v2 > 2*v2 + True + + Test that non-scaled valuations call through to this method to resolve + the scaling:: + + sage: v2 > v2/2 + True + + """ + if self == other: + return True + if isinstance(other, ScaledValuation_generic): + return (self._scale / other._scale) * self._base_valuation >= other._base_valuation + if self._scale >= 1: + if self._base_valuation >= other: + return True + else: + assert not self.is_trivial() + if self._base_valuation <= other: + return False + return super(ScaledValuation_generic, self)._ge_(other) + + def _le_(self, other): + r""" + Return whether this valuation is smaller or equal to ``other``, a + valuation on the same domain. + + EXAMPLES:: + + sage: v2 = QQ.valuation(2) + sage: 2*v2 <= v2 + False + sage: v2/2 <= 2*v2 + True + sage: 3*v2 < 2*v2 + False + + Test that non-scaled valuations call through to this method to resolve + the scaling:: + + sage: v2 < v2/2 + False + + """ + return other / self._scale >= self._base_valuation + + def value_semigroup(self): + r""" + Return the value semigroup of this valuation. + + EXAMPLES:: + + sage: v2 = QQ.valuation(2) + sage: (2*v2).value_semigroup() + Additive Abelian Semigroup generated by -2, 2 + + """ + return self._scale * self._base_valuation.value_semigroup() diff --git a/src/sage/rings/valuation/trivial_valuation.py b/src/sage/rings/valuation/trivial_valuation.py new file mode 100644 index 00000000000..1c348ebfe7d --- /dev/null +++ b/src/sage/rings/valuation/trivial_valuation.py @@ -0,0 +1,408 @@ +# -*- coding: utf-8 -*- +r""" +Trivial valuations + +AUTHORS: + +- Julian Rüth (2016-10-14): initial version + +EXAMPLES:: + + sage: v = valuations.TrivialValuation(QQ); v + Trivial valuation on Rational Field + sage: v(1) + 0 + +""" +#***************************************************************************** +# Copyright (C) 2016-2017 Julian Rüth +# +# Distributed under the terms of the GNU General Public License (GPL) +# as published by the Free Software Foundation; either version 2 of +# the License, or (at your option) any later version. +# http://www.gnu.org/licenses/ +#***************************************************************************** +from valuation import DiscretePseudoValuation, DiscreteValuation, InfiniteDiscretePseudoValuation +from valuation_space import DiscretePseudoValuationSpace +from sage.structure.factory import UniqueFactory + +class TrivialValuationFactory(UniqueFactory): + r""" + Create a trivial valuation on ``domain``. + + EXAMPLES:: + + sage: v = valuations.TrivialValuation(QQ); v + Trivial valuation on Rational Field + sage: v(1) + 0 + + """ + def __init__(self, clazz, parent, *args, **kwargs): + r""" + TESTS:: + + sage: from sage.rings.valuation.trivial_valuation import TrivialValuationFactory + sage: isinstance(valuations.TrivialValuation, TrivialValuationFactory) + True + + """ + UniqueFactory.__init__(self, *args, **kwargs) + self._class = clazz + self._parent = parent + + def create_key(self, domain): + r""" + Create a key that identifies this valuation. + + EXAMPLES:: + + sage: valuations.TrivialValuation(QQ) is valuations.TrivialValuation(QQ) # indirect doctest + True + + """ + return domain, + + def create_object(self, version, key, **extra_args): + r""" + Create a trivial valuation from ``key``. + + EXAMPLES:: + + sage: valuations.TrivialValuation(QQ) # indirect doctest + Trivial valuation on Rational Field + + """ + domain, = key + parent = self._parent(domain) + return parent.__make_element_class__(self._class)(parent) + +class TrivialDiscretePseudoValuation_base(DiscretePseudoValuation): + r""" + Base class for code shared by trivial valuations. + + EXAMPLES:: + + sage: v = valuations.TrivialPseudoValuation(ZZ); v + Trivial pseudo-valuation on Integer Ring + + TESTS:: + + sage: TestSuite(v).run() # long time + + """ + def uniformizer(self): + r""" + Return a uniformizing element for this valuation. + + EXAMPLES:: + + sage: v = valuations.TrivialPseudoValuation(ZZ) + sage: v.uniformizer() + Traceback (most recent call last): + ... + ValueError: Trivial valuations do not define a uniformizing element + + """ + raise ValueError("Trivial valuations do not define a uniformizing element") + + def is_trivial(self): + r""" + Return whether this valuation is trivial. + + EXAMPLES:: + + sage: v = valuations.TrivialPseudoValuation(QQ) + sage: v.is_trivial() + True + + """ + return True + + def is_negative_pseudo_valuation(self): + r""" + Return whether this valuatios attains the value `-\infty`. + + EXAMPLES:: + + sage: v = valuations.TrivialPseudoValuation(QQ) + sage: v.is_negative_pseudo_valuation() + False + + """ + return False + +class TrivialDiscretePseudoValuation(TrivialDiscretePseudoValuation_base, InfiniteDiscretePseudoValuation): + r""" + The trivial pseudo-valuation that is `\infty` everywhere. + + EXAMPLES:: + + sage: v = valuations.TrivialPseudoValuation(QQ); v + Trivial pseudo-valuation on Rational Field + + TESTS:: + + sage: TestSuite(v).run() # long time + + """ + def __init__(self, parent): + r""" + TESTS:: + + sage: from sage.rings.valuation.trivial_valuation import TrivialDiscretePseudoValuation + sage: v = valuations.TrivialPseudoValuation(QQ) + sage: isinstance(v, TrivialDiscretePseudoValuation) + True + + """ + TrivialDiscretePseudoValuation_base.__init__(self, parent) + InfiniteDiscretePseudoValuation.__init__(self, parent) + + def _call_(self, x): + r""" + Evaluate this valuation at ``x``. + + EXAMPLES:: + + sage: v = valuations.TrivialPseudoValuation(QQ) + sage: v(0) + +Infinity + sage: v(1) + +Infinity + + """ + from sage.rings.all import infinity + return infinity + + def _repr_(self): + r""" + Return a printable representation of this valuation. + + EXAMPLES:: + + sage: valuations.TrivialPseudoValuation(QQ) # indirect doctest + Trivial pseudo-valuation on Rational Field + + """ + return "Trivial pseudo-valuation on %r"%(self.domain(),) + + def value_group(self): + r""" + Return the value group of this valuation. + + EXAMPLES: + + A trivial discrete pseudo-valuation has no value group:: + + sage: v = valuations.TrivialPseudoValuation(QQ) + sage: v.value_group() + Traceback (most recent call last): + ... + ValueError: The trivial pseudo-valuation that is infinity everywhere does not have a value group. + + """ + raise ValueError("The trivial pseudo-valuation that is infinity everywhere does not have a value group.") + + def residue_ring(self): + r""" + Return the residue ring of this valuation. + + EXAMPLES:: + + sage: valuations.TrivialPseudoValuation(QQ).residue_ring() + Quotient of Rational Field by the ideal (1) + + """ + return self.domain().quo(self.domain().one()) + + def reduce(self, x): + r""" + Reduce ``x`` modulo the positive elements of this valuation. + + EXAMPLES:: + + sage: v = valuations.TrivialPseudoValuation(QQ) + sage: v.reduce(1) + 0 + + """ + self.domain().coerce(x) + return self.residue_ring().zero() + + def lift(self, X): + r""" + Return a lift of ``X`` to the domain of this valuation. + + EXAMPLES:: + + sage: v = valuations.TrivialPseudoValuation(QQ) + sage: v.lift(v.residue_ring().zero()) + 0 + + """ + self.residue_ring().coerce(X) # ignore the output + return self.domain().zero() + + def _ge_(self, other): + r""" + Return whether this valuation is bigger or equal than ``other`` + everywhere. + + EXAMPLES:: + + sage: v = valuations.TrivialPseudoValuation(QQ) + sage: w = valuations.TrivialValuation(QQ) + sage: v >= w + True + + """ + # the trivial discrete valuation is the biggest valuation + return True + +class TrivialDiscreteValuation(TrivialDiscretePseudoValuation_base, DiscreteValuation): + r""" + The trivial valuation that is zero on non-zero elements. + + EXAMPLES:: + + sage: v = valuations.TrivialValuation(QQ); v + Trivial valuation on Rational Field + + TESTS:: + + sage: TestSuite(v).run() # long time + + """ + def __init__(self, parent): + r""" + TESTS:: + + sage: from sage.rings.valuation.trivial_valuation import TrivialDiscreteValuation + sage: v = valuations.TrivialValuation(QQ) + sage: isinstance(v, TrivialDiscreteValuation) + True + + """ + TrivialDiscretePseudoValuation_base.__init__(self, parent) + DiscreteValuation.__init__(self, parent) + + def _call_(self, x): + r""" + Evaluate this valuation at ``x``. + + EXAMPLES:: + + sage: v = valuations.TrivialValuation(QQ) + sage: v(0) + +Infinity + sage: v(1) + 0 + + """ + from sage.rings.all import infinity + return infinity if x == 0 else self.codomain().zero() + + def _repr_(self): + r""" + Return a printable representation of this valuation. + + EXAMPLES:: + + sage: valuations.TrivialValuation(QQ) # indirect doctest + Trivial valuation on Rational Field + + """ + return "Trivial valuation on %r"%(self.domain(),) + + def value_group(self): + r""" + Return the value group of this valuation. + + EXAMPLES: + + A trivial discrete valuation has a trivial value group:: + + sage: v = valuations.TrivialValuation(QQ) + sage: v.value_group() + Trivial Additive Abelian Group + + """ + from .value_group import DiscreteValueGroup + return DiscreteValueGroup(0) + + def residue_ring(self): + r""" + Return the residue ring of this valuation. + + EXAMPLES:: + + sage: valuations.TrivialValuation(QQ).residue_ring() + Rational Field + + """ + return self.domain() + + def reduce(self, x): + r""" + Reduce ``x`` modulo the positive elements of this valuation. + + EXAMPLES:: + + sage: v = valuations.TrivialValuation(QQ) + sage: v.reduce(1) + 1 + + """ + return self.domain().coerce(x) + + def lift(self, X): + r""" + Return a lift of ``X`` to the domain of this valuation. + + EXAMPLES:: + + sage: v = valuations.TrivialValuation(QQ) + sage: v.lift(v.residue_ring().zero()) + 0 + + """ + return self.residue_ring().coerce(X) + + def extensions(self, ring): + r""" + Return the unique extension of this valuation to ``ring``. + + EXAMPLES:: + + sage: v = valuations.TrivialValuation(ZZ) + sage: v.extensions(QQ) + [Trivial valuation on Rational Field] + + """ + if self.domain().is_subring(ring): + from sage.rings.valuation.trivial_valuation import TrivialValuation + return [TrivialValuation(ring)] + return super(DiscretePseudoValuation, self).extensions(ring) + + def _ge_(self, other): + r""" + Return whether this valuation is bigger or equal than ``other`` + everywhere. + + EXAMPLES:: + + sage: v = valuations.TrivialPseudoValuation(QQ) + sage: w = valuations.TrivialValuation(QQ) + sage: w >= v + False + + """ + # the trivial discrete valuation is the smallest valuation + if self is other: + return True + return False + +TrivialValuation = TrivialValuationFactory(TrivialDiscreteValuation, DiscretePseudoValuationSpace, "sage.rings.valuation.trivial_valuation.TrivialValuation") +TrivialPseudoValuation = TrivialValuationFactory(TrivialDiscretePseudoValuation, DiscretePseudoValuationSpace, "sage.rings.valuation.trivial_valuation.TrivialPseudoValuation") + diff --git a/src/sage/rings/valuation/valuation.py b/src/sage/rings/valuation/valuation.py new file mode 100644 index 00000000000..a55bb5bc332 --- /dev/null +++ b/src/sage/rings/valuation/valuation.py @@ -0,0 +1,1119 @@ +# -*- coding: utf-8 -*- +r""" +Discrete valuations + +This file defines abstract base classes for discrete (pseudo-)valuations. + +AUTHORS: + +- Julian Rüth (2013-03-16): initial version + +EXAMPLES: + +Discrete valuations can be created on a variety of rings:: + + sage: ZZ.valuation(2) + 2-adic valuation + sage: GaussianIntegers().valuation(3) + 3-adic valuation + sage: QQ.valuation(5) + 5-adic valuation + sage: Zp(7).valuation() + 7-adic valuation + +:: + + sage: K. = FunctionField(QQ) + sage: K.valuation(x) + (x)-adic valuation + sage: K.valuation(x^2 + 1) + (x^2 + 1)-adic valuation + sage: K.valuation(1/x) + Valuation at the infinite place + +:: + + sage: R. = QQ[] + sage: v = QQ.valuation(2) + sage: w = GaussValuation(R, v) + sage: w.augmentation(x, 3) + [ Gauss valuation induced by 2-adic valuation, v(x) = 3 ] + +We can also define discrete pseudo-valuations, i.e., discrete valuations that +send more than just zero to infinity:: + + sage: w.augmentation(x, infinity) + [ Gauss valuation induced by 2-adic valuation, v(x) = +Infinity ] + +""" +#***************************************************************************** +# Copyright (C) 2013-2017 Julian Rüth +# +# Distributed under the terms of the GNU General Public License (GPL) +# as published by the Free Software Foundation; either version 2 of +# the License, or (at your option) any later version. +# http://www.gnu.org/licenses/ +#***************************************************************************** +from sage.categories.morphism import Morphism +from sage.structure.richcmp import op_EQ, op_NE, op_LE, op_LT, op_GE, op_GT + +from sage.misc.cachefunc import cached_method + +class DiscretePseudoValuation(Morphism): + r""" + Abstract base class for discrete pseudo-valuations, i.e., discrete + valuations which might send more that just zero to infinity. + + INPUT: + + - ``domain`` -- an integral domain + + EXAMPLES:: + + sage: v = ZZ.valuation(2); v # indirect doctest + 2-adic valuation + + TESTS:: + + sage: TestSuite(v).run() # long time + + """ + def __init__(self, parent): + r""" + TESTS:: + + sage: from sage.rings.valuation.valuation import DiscretePseudoValuation + sage: isinstance(ZZ.valuation(2), DiscretePseudoValuation) + True + + """ + Morphism.__init__(self, parent=parent) + + def is_equivalent(self, f, g): + r""" + Return whether ``f`` and ``g`` are equivalent. + + EXAMPLES:: + + sage: v = QQ.valuation(2) + sage: v.is_equivalent(2, 1) + False + sage: v.is_equivalent(2, -2) + True + sage: v.is_equivalent(2, 0) + False + sage: v.is_equivalent(0, 0) + True + + """ + from sage.rings.all import infinity + if self(f) is infinity: + return self(g) is infinity + + return self(f-g) > self(f) + + def __hash__(self): + r""" + The hash value of this valuation. + + We redirect to :meth:`_hash_`, so that subclasses can only override + :meth:`_hash_` and :meth:`_eq_` if they want to provide a different + notion of equality but they can leave the partial and total operators + untouched. + + EXAMPLES:: + + sage: v = QQ.valuation(2) + sage: hash(v) == hash(v) # indirect doctest + True + + """ + return self._hash_() + + def _hash_(self): + r""" + Return a hash value for this valuation. + + We override the strange default provided by + :class:`sage.categories.marphism.Morphism` here and implement equality by + ``id``. This works fine for objects which use unique representation. + + Note that the vast majority of valuations come out of a + :class:`sage.structure.factory.UniqueFactory` and therefore override + our implementation of :meth:`__hash__` and :meth:`__eq__`. + + EXAMPLES:: + + sage: v = QQ.valuation(2) + sage: hash(v) == hash(v) # indirect doctest + True + + """ + return id(self) + + def _richcmp_(self, other, op): + r""" + Compare this element to ``other``. + + We redirect to methods :meth:`_eq_`, :meth:`_lt_`, and :meth:`_gt_` to + make it easier for subclasses to override only parts of this + functionality. + + Note that valuations usually implement ``x == y`` as ``x`` and ``y`` + are indistinguishable. Whereas ``x <= y`` and ``x >= y`` are + implemented with respect to the natural partial order of valuations. + As a result, ``x <= y and x >= y`` does not imply ``x == y``. + + EXAMPLES:: + + sage: v = QQ.valuation(2) + sage: v == v + True + sage: v != v + False + sage: w = QQ.valuation(3) + sage: v == w + False + sage: v != w + True + + Note that this does not affect comparison of valuations which do not + coerce into a common parent. This is by design in Sage, see + :meth:`sage.structure.element.Element.__richcmp__`. When the valuations + do not coerce into a common parent, a rather random comparison of + ``id`` happens:: + + sage: w = valuations.TrivialValuation(GF(2)) + sage: w <= v # random output + True + sage: v <= w # random output + False + + """ + if op == op_LT: + return self <= other and not (self >= other) + if op == op_LE: + return self._le_(other) + if op == op_EQ: + return self._eq_(other) + if op == op_NE: + return not self == other + if op == op_GT: + return self >= other and not (self <= other) + if op == op_GE: + return self._ge_(other) + raise NotImplementedError("Operator not implemented for this valuation") + + def _eq_(self, other): + r""" + Return whether this valuation and ``other`` are indistinguishable. + + We override the strange default provided by + :class:`sage.categories.marphism.Morphism` here and implement equality by + ``id``. This is the right behaviour in many cases. + + Note that the vast majority of valuations come out of a + :class:`sage.structure.factory.UniqueFactory` and therefore override + our implementation of :meth:`__hash__` and :meth:`__eq__`. + + When overriding this method, you can assume that ``other`` is a + (pseudo-)valuation on the same domain. + + EXAMPLES:: + + sage: v = valuations.TrivialValuation(QQ) + sage: v == v + True + + """ + return self is other + + def _le_(self, other): + r""" + Return whether this valuation is less than or equal to ``other`` + pointwise. + + When overriding this method, you can assume that ``other`` is a + (pseudo-)valuation on the same domain. + + EXAMPLES:: + + sage: v = valuations.TrivialValuation(QQ) + sage: w = QQ.valuation(2) + sage: v <= w + True + + Note that this does not affect comparison of valuations which do not + coerce into a common parent. This is by design in Sage, see + :meth:`sage.structure.element.Element.__richcmp__`. When the valuations + do not coerce into a common parent, a rather random comparison of + ``id`` happens:: + + sage: w = valuations.TrivialValuation(GF(2)) + sage: w <= v # random output + True + sage: v <= w # random output + False + + """ + return other >= self + + def _ge_(self, other): + r""" + Return whether this valuation is greater than or equal to ``other`` + pointwise. + + When overriding this method, you can assume that ``other`` is a + (pseudo-)valuation on the same domain. + + EXAMPLES:: + + sage: v = valuations.TrivialValuation(QQ) + sage: w = QQ.valuation(2) + sage: v >= w + False + + Note that this does not affect comparison of valuations which do not + coerce into a common parent. This is by design in Sage, see + :meth:`sage.structure.element.Element.__richcmp__`. When the valuations + do not coerce into a common parent, a rather random comparison of + ``id`` happens:: + + sage: w = valuations.TrivialValuation(GF(2)) + sage: w <= v # random output + True + sage: v <= w # random output + False + + """ + if self == other: return True + from scaled_valuation import ScaledValuation_generic + if isinstance(other, ScaledValuation_generic): + return other <= self + raise NotImplementedError("Operator not implemented for this valuation") + + # Remove the default implementation of Map.__reduce__ that does not play + # nice with factories (a factory, does not override Map.__reduce__ because + # it is not the generic reduce of object) and that does not match equality + # by id. + __reduce__ = object.__reduce__ + + def _test_valuation_inheritance(self, **options): + r""" + Test that every instance of this class is either a + :class:`InfiniteDiscretePseudoValuation` or a + :class:`DiscreteValuation`. + + EXAMPLES:: + + sage: QQ.valuation(2)._test_valuation_inheritance() + + """ + tester = self._tester(**options) + tester.assertTrue(isinstance(self, InfiniteDiscretePseudoValuation) != isinstance(self, DiscreteValuation)) + +class InfiniteDiscretePseudoValuation(DiscretePseudoValuation): + r""" + Abstract base class for infinite discrete pseudo-valuations, i.e., discrete + pseudo-valuations which are not discrete valuations. + + EXAMPLES:: + + sage: v = QQ.valuation(2) + sage: R. = QQ[] + sage: v = GaussValuation(R, v) + sage: w = v.augmentation(x, infinity); w # indirect doctest + [ Gauss valuation induced by 2-adic valuation, v(x) = +Infinity ] + + TESTS:: + + sage: from sage.rings.valuation.valuation import InfiniteDiscretePseudoValuation + sage: isinstance(w, InfiniteDiscretePseudoValuation) + True + sage: TestSuite(w).run() # long time + + """ + def is_discrete_valuation(self): + r""" + Return whether this valuation is a discrete valuation. + + EXAMPLES:: + + sage: v = QQ.valuation(2) + sage: R. = QQ[] + sage: v = GaussValuation(R, v) + sage: v.is_discrete_valuation() + True + sage: w = v.augmentation(x, infinity) + sage: w.is_discrete_valuation() + False + + """ + return False + +class NegativeInfiniteDiscretePseudoValuation(InfiniteDiscretePseudoValuation): + r""" + Abstract base class for pseudo-valuations which attain the value `\infty` + and `-\infty`, i.e., whose domain contains an element of valuation `\infty` + and its inverse. + + EXAMPLES:: + + sage: R. = QQ[] + sage: v = GaussValuation(R, valuations.TrivialValuation(QQ)).augmentation(x, infinity) + sage: K. = FunctionField(QQ) + sage: w = K.valuation(v) + + TESTS:: + + sage: TestSuite(w).run() # long time + + """ + def is_negative_pseudo_valuation(self): + r""" + Return whether this valuation attains the value `-\infty`. + + EXAMPLES:: + + sage: R. = QQ[] + sage: v = GaussValuation(R, valuations.TrivialValuation(QQ)).augmentation(x, infinity) + sage: v.is_negative_pseudo_valuation() + False + sage: K. = FunctionField(QQ) + sage: w = K.valuation(v) + sage: w.is_negative_pseudo_valuation() + True + + """ + return True + + +class DiscreteValuation(DiscretePseudoValuation): + r""" + Abstract base class for discrete valuations. + + EXAMPLES:: + + sage: v = QQ.valuation(2) + sage: R. = QQ[] + sage: v = GaussValuation(R, v) + sage: w = v.augmentation(x, 1337); w # indirect doctest + [ Gauss valuation induced by 2-adic valuation, v(x) = 1337 ] + + TESTS:: + + sage: from sage.rings.valuation.valuation import DiscreteValuation + sage: isinstance(w, DiscreteValuation) + True + sage: TestSuite(w).run() # long time + + """ + def is_discrete_valuation(self): + r""" + Return whether this valuation is a discrete valuation. + + EXAMPLES:: + + sage: v = valuations.TrivialValuation(ZZ) + sage: v.is_discrete_valuation() + True + + """ + return True + + def mac_lane_approximants(self, G, assume_squarefree=False, require_final_EF=True, required_precision=-1, require_incomparability=False, require_maximal_degree=False, algorithm="serial"): + r""" + Return approximants on `K[x]` for the extensions of this valuation to + `L=K[x]/(G)`. + + If `G` is an irreducible polynomial, then this corresponds to + extensions of this valuation to the completion of `L`. + + INPUT: + + - ``G`` -- a monic squarefree integral polynomial in a + univariate polynomial ring over the domain of this valuation + + - ``assume_squarefree`` -- a boolean (default: ``False``), whether to + assume that ``G`` is squarefree. If ``True``, the squafreeness of + ``G`` is not verified though it is necessary when + ``require_final_EF`` is set for the algorithm to terminate. + + - ``require_final_EF`` -- a boolean (default: ``True``); whether to + require the returned key polynomials to be in one-to-one + correspondance to the extensions of this valuation to ``L`` and + require them to have the ramification index and residue degree of the + valuations they correspond to. + + - ``required_precision`` -- a number or infinity (default: -1); whether + to require the last key polynomial of the returned valuations to have + at least that valuation. + + - ``require_incomparability`` -- a boolean (default: ``False``); + whether to require require the returned valuations to be incomparable + (with respect to the partial order on valuations defined by comparing + them pointwise.) + + - ``require_maximal_degree`` -- a boolean (deault: ``False``); whether + to require the last key polynomial of the returned valuation to have + maximal degree. This is most relevant when using this algorithm to + compute approximate factorizations of ``G``, when set to ``True``, + the last key polynomial has the same degree as the corresponding + factor. + + - ``algorithm`` -- one of ``"serial"`` or ``"parallel"`` (default: + ``"serial"``); whether or not to parallelize the algorithm + + EXAMPLES:: + + sage: v = QQ.valuation(2) + sage: R. = QQ[] + sage: v.mac_lane_approximants(x^2 + 1) + [[ Gauss valuation induced by 2-adic valuation, v(x + 1) = 1/2 ]] + sage: v.mac_lane_approximants(x^2 + 1, required_precision=infinity) + [[ Gauss valuation induced by 2-adic valuation, v(x + 1) = 1/2, v(x^2 + 1) = +Infinity ]] + sage: v.mac_lane_approximants(x^2 + x + 1) + [[ Gauss valuation induced by 2-adic valuation, v(x^2 + x + 1) = +Infinity ]] + + Note that ``G`` does not need to be irreducible. Here, we detect a + factor `x + 1` and an approximate factor `x + 1` (which is an + approximation to `x - 1`):: + + sage: v.mac_lane_approximants(x^2 - 1) + [[ Gauss valuation induced by 2-adic valuation, v(x + 1) = +Infinity ], + [ Gauss valuation induced by 2-adic valuation, v(x + 1) = 1 ]] + + However, it needs to be squarefree:: + + sage: v.mac_lane_approximants(x^2) + Traceback (most recent call last): + ... + ValueError: G must be squarefree + + TESTS: + + Some difficult cases provided by Mark van Hoeij:: + + sage: k = GF(2) + sage: K. = FunctionField(k) + sage: R. = K[] + sage: F = y^21 + x*y^20 + (x^3 + x + 1)*y^18 + (x^3 + 1)*y^17 + (x^4 + x)*y^16 + (x^7 + x^6 + x^3 + x + 1)*y^15 + x^7*y^14 + (x^8 + x^7 + x^6 + x^4 + x^3 + 1)*y^13 + (x^9 + x^8 + x^4 + 1)*y^12 + (x^11 + x^9 + x^8 + x^5 + x^4 + x^3 + x^2)*y^11 + (x^12 + x^9 + x^8 + x^7 + x^5 + x^3 + x + 1)*y^10 + (x^14 + x^13 + x^10 + x^9 + x^8 + x^7 + x^6 + x^3 + x^2 + 1)*y^9 + (x^13 + x^9 + x^8 + x^6 + x^4 + x^3 + x)*y^8 + (x^16 + x^15 + x^13 + x^12 + x^11 + x^7 + x^3 + x)*y^7 + (x^17 + x^16 + x^13 + x^9 + x^8 + x)*y^6 + (x^17 + x^16 + x^12 + x^7 + x^5 + x^2 + x + 1)*y^5 + (x^19 + x^16 + x^15 + x^12 + x^6 + x^5 + x^3 + 1)*y^4 + (x^18 + x^15 + x^12 + x^10 + x^9 + x^7 + x^4 + x)*y^3 + (x^22 + x^21 + x^20 + x^18 + x^13 + x^12 + x^9 + x^8 + x^7 + x^5 + x^4 + x^3)*y^2 + (x^23 + x^22 + x^20 + x^17 + x^15 + x^14 + x^12 + x^9)*y + x^25 + x^23 + x^19 + x^17 + x^15 + x^13 + x^11 + x^5 + sage: x = K._ring.gen() + sage: v0 = K.valuation(GaussValuation(K._ring, valuations.TrivialValuation(k)).augmentation(x,1)) + sage: v0.mac_lane_approximants(F, assume_squarefree=True) # assumes squarefree for speed + [[ Gauss valuation induced by (x)-adic valuation, v(y + x + 1) = 3/2 ], + [ Gauss valuation induced by (x)-adic valuation, v(y) = 1 ], + [ Gauss valuation induced by (x)-adic valuation, v(y) = 4/3 ], + [ Gauss valuation induced by (x)-adic valuation, v(y^15 + y^13 + y^12 + y^10 + y^9 + y^8 + y^4 + y^3 + y^2 + y + 1) = 1 ]] + sage: v0 = K.valuation(GaussValuation(K._ring, valuations.TrivialValuation(k)).augmentation(x+1,1)) + sage: v0.mac_lane_approximants(F, assume_squarefree=True) # assumes squarefree for speed + [[ Gauss valuation induced by (x + 1)-adic valuation, v(y + x^2 + 1) = 7/2 ], + [ Gauss valuation induced by (x + 1)-adic valuation, v(y) = 3/4 ], + [ Gauss valuation induced by (x + 1)-adic valuation, v(y) = 7/2 ], + [ Gauss valuation induced by (x + 1)-adic valuation, v(y^13 + y^12 + y^10 + y^7 + y^6 + y^3 + 1) = 1 ]] + sage: v0 = valuations.FunctionFieldValuation(K, GaussValuation(K._ring, valuations.TrivialValuation(k)).augmentation(x^3+x^2+1,1)) + sage: v0.mac_lane_approximants(F, assume_squarefree=True) # assumes squarefree for speed + [[ Gauss valuation induced by (x^3 + x^2 + 1)-adic valuation, v(y + x^3 + x^2 + x) = 2, v(y^2 + (x^6 + x^4 + 1)*y + x^14 + x^10 + x^9 + x^8 + x^5 + x^4 + x^3 + x^2 + x) = 5 ], + [ Gauss valuation induced by (x^3 + x^2 + 1)-adic valuation, v(y^2 + (x^2 + x)*y + 1) = 1 ], + [ Gauss valuation induced by (x^3 + x^2 + 1)-adic valuation, v(y^3 + (x + 1)*y^2 + (x + 1)*y + x^2 + x + 1) = 1 ], + [ Gauss valuation induced by (x^3 + x^2 + 1)-adic valuation, v(y^3 + x^2*y + x) = 1 ], + [ Gauss valuation induced by (x^3 + x^2 + 1)-adic valuation, v(y^4 + (x + 1)*y^3 + x^2*y^2 + (x^2 + x)*y + x) = 1 ], + [ Gauss valuation induced by (x^3 + x^2 + 1)-adic valuation, v(y^7 + x^2*y^6 + (x + 1)*y^4 + x^2*y^3 + (x^2 + x + 1)*y^2 + x^2*y + x) = 1 ]] + + Cases with trivial residue field extensions:: + + sage: K. = FunctionField(QQ) + sage: S. = K[] + sage: F = y^2 - x^2 - x^3 - 3 + sage: v0 = GaussValuation(K._ring, QQ.valuation(3)) + sage: v1 = v0.augmentation(K._ring.gen(),1/3) + sage: mu0 = valuations.FunctionFieldValuation(K, v1) + sage: mu0.mac_lane_approximants(F) + [[ Gauss valuation induced by Valuation on rational function field induced by [ Gauss valuation induced by 3-adic valuation, v(x) = 1/3 ], v(y + 2*x) = 2/3 ], + [ Gauss valuation induced by Valuation on rational function field induced by [ Gauss valuation induced by 3-adic valuation, v(x) = 1/3 ], v(y + x) = 2/3 ]] + + Over a complete base field:: + + sage: k=Qp(2,10) + sage: v = k.valuation() + + sage: R.=k[] + sage: G = x + sage: v.mac_lane_approximants(G) + [Gauss valuation induced by 2-adic valuation] + sage: v.mac_lane_approximants(G, required_precision = infinity) + [[ Gauss valuation induced by 2-adic valuation, v((1 + O(2^10))*x) = +Infinity ]] + + sage: G = x^2 + 1 + sage: v.mac_lane_approximants(G) + [[ Gauss valuation induced by 2-adic valuation, v((1 + O(2^10))*x + (1 + O(2^10))) = 1/2 ]] + sage: v.mac_lane_approximants(G, required_precision = infinity) + [[ Gauss valuation induced by 2-adic valuation, v((1 + O(2^10))*x + (1 + O(2^10))) = 1/2, v((1 + O(2^10))*x^2 + (1 + O(2^10))) = +Infinity ]] + + sage: G = x^4 + 2*x^3 + 2*x^2 - 2*x + 2 + sage: v.mac_lane_approximants(G) + [[ Gauss valuation induced by 2-adic valuation, v((1 + O(2^10))*x) = 1/4 ]] + sage: v.mac_lane_approximants(G, required_precision=infinity) + [[ Gauss valuation induced by 2-adic valuation, v((1 + O(2^10))*x) = 1/4, v((1 + O(2^10))*x^4 + (2 + O(2^11))*x^3 + (2 + O(2^11))*x^2 + (2 + 2^2 + 2^3 + 2^4 + 2^5 + 2^6 + 2^7 + 2^8 + 2^9 + 2^10 + O(2^11))*x + (2 + O(2^11))) = +Infinity ]] + + The factorization of primes in the Gaussian integers can be read off + the Mac Lane approximants:: + + sage: v0 = QQ.valuation(2) + sage: R. = QQ[] + sage: G = x^2 + 1 + sage: v0.mac_lane_approximants(G) + [[ Gauss valuation induced by 2-adic valuation, v(x + 1) = 1/2 ]] + + sage: v0 = QQ.valuation(3) + sage: v0.mac_lane_approximants(G) + [[ Gauss valuation induced by 3-adic valuation, v(x^2 + 1) = +Infinity ]] + + sage: v0 = QQ.valuation(5) + sage: v0.mac_lane_approximants(G) + [[ Gauss valuation induced by 5-adic valuation, v(x + 2) = 1 ], + [ Gauss valuation induced by 5-adic valuation, v(x + 3) = 1 ]] + sage: v0.mac_lane_approximants(G, required_precision = 10) + [[ Gauss valuation induced by 5-adic valuation, v(x + 3626068) = 10 ], + [ Gauss valuation induced by 5-adic valuation, v(x + 6139557) = 10 ]] + + The same example over the 5-adic numbers. In the quadratic extension + `\QQ[x]/(x^2+1)`, 5 factors `-(x - 2)(x + 2)`, this behaviour can be + read off the Mac Lane approximants:: + + sage: k=Qp(5,4) + sage: v = k.valuation() + sage: R.=k[] + sage: G = x^2 + 1 + sage: v1,v2 = v.mac_lane_approximants(G); v1,v2 + ([ Gauss valuation induced by 5-adic valuation, v((1 + O(5^4))*x + (2 + O(5^4))) = 1 ], + [ Gauss valuation induced by 5-adic valuation, v((1 + O(5^4))*x + (3 + O(5^4))) = 1 ]) + sage: w1, w2 = v.mac_lane_approximants(G, required_precision = 2); w1,w2 + ([ Gauss valuation induced by 5-adic valuation, v((1 + O(5^4))*x + (2 + 5 + O(5^4))) = 2 ], + [ Gauss valuation induced by 5-adic valuation, v((1 + O(5^4))*x + (3 + 3*5 + O(5^4))) = 2 ]) + + Note how the latter give a better approximation to the factors of `x^2 + 1`:: + + sage: v1.phi() * v2.phi() - G + (O(5^4))*x^2 + (5 + O(5^4))*x + (5 + O(5^4)) + sage: w1.phi() * w2.phi() - G + (O(5^4))*x^2 + (5^2 + O(5^4))*x + (5^3 + O(5^4)) + + In this example, the process stops with a factorization of `x^2 + 1`:: + + sage: v.mac_lane_approximants(G, required_precision=infinity) + [[ Gauss valuation induced by 5-adic valuation, v((1 + O(5^4))*x + (2 + 5 + 2*5^2 + 5^3 + O(5^4))) = +Infinity ], + [ Gauss valuation induced by 5-adic valuation, v((1 + O(5^4))*x + (3 + 3*5 + 2*5^2 + 3*5^3 + O(5^4))) = +Infinity ]] + + This obviously cannot happen over the rationals where we only get an + approximate factorization:: + + sage: v = QQ.valuation(5) + sage: R.=QQ[] + sage: G = x^2 + 1 + sage: v.mac_lane_approximants(G) + [[ Gauss valuation induced by 5-adic valuation, v(x + 2) = 1 ], [ Gauss valuation induced by 5-adic valuation, v(x + 3) = 1 ]] + sage: v.mac_lane_approximants(G, required_precision=5) + [[ Gauss valuation induced by 5-adic valuation, v(x + 1068) = 6 ], + [ Gauss valuation induced by 5-adic valuation, v(x + 2057) = 5 ]] + + Initial versions ran into problems with the trivial residue field + extensions in this case:: + + sage: K = Qp(3, 20, print_mode='digits') + sage: R. = K[] + + sage: alpha = T^3/4 + sage: G = 3^3*T^3*(alpha^4 - alpha)^2 - (4*alpha^3 - 1)^3 + sage: G = G/G.leading_coefficient() + sage: K.valuation().mac_lane_approximants(G) + [[ Gauss valuation induced by 3-adic valuation, v((...1)*T + (...2)) = 1/9, v((...1)*T^9 + (...20)*T^8 + (...210)*T^7 + (...20)*T^6 + (...20)*T^5 + (...10)*T^4 + (...220)*T^3 + (...20)*T^2 + (...110)*T + (...122)) = 55/27 ]] + + A similar example:: + + sage: R. = QQ[] + sage: v = QQ.valuation(3) + sage: G = (x^3 + 3)^3 - 81 + sage: v.mac_lane_approximants(G) + [[ Gauss valuation induced by 3-adic valuation, v(x) = 1/3, v(x^3 + 3*x + 3) = 13/9 ]] + + Another problematic case:: + + sage: R. = QQ[] + sage: Delta = x^12 + 20*x^11 + 154*x^10 + 664*x^9 + 1873*x^8 + 3808*x^7 + 5980*x^6 + 7560*x^5 + 7799*x^4 + 6508*x^3 + 4290*x^2 + 2224*x + 887 + sage: K. = NumberField(x^6 + 108) + sage: K.is_galois() + True + sage: vK = QQ.valuation(2).extension(K) + sage: vK(2) + 1 + sage: vK(theta) + 1/3 + sage: G=Delta.change_ring(K) + sage: vK.mac_lane_approximants(G) + [[ Gauss valuation induced by 2-adic valuation, v(x + 1) = 1/4, v(x^4 + 2*x^2 + 1/2*theta^4 + theta^3 + 5*theta + 1) = 5/3 ], + [ Gauss valuation induced by 2-adic valuation, v(x + 1) = 1/4, v(x^4 + 2*x^2 + 3/2*theta^4 + theta^3 + 5*theta + 1) = 5/3 ], + [ Gauss valuation induced by 2-adic valuation, v(x + 1) = 1/4, v(x^4 + 2*x^2 + theta^4 + theta^3 + 1) = 5/3 ]] + + An easy case that produced the wrong error at some point:: + + sage: R. = QQ[] + sage: v = QQ.valuation(2) + sage: v.mac_lane_approximants(x^2 - 1/2) + Traceback (most recent call last): + ... + ValueError: G must be integral + + Some examples that Sebastian Pauli used in a talk at Sage Days 87. + + :: + + sage: R = ZpFM(3, 7, print_mode='terse') + sage: S. = R[] + sage: v = R.valuation() + sage: f = x^4 + 234 + sage: len(v.mac_lane_approximants(f, assume_squarefree=True)) # is_squarefree() is not properly implemented yet + 2 + + :: + + sage: R = ZpFM(2, 50, print_mode='terse') + sage: S. = R[] + sage: f = (x^32 + 16)*(x^32 + 16 + 2^16*x^2) + 2^34 + sage: v = R.valuation() + sage: len(v.mac_lane_approximants(f, assume_squarefree=True)) # is_squarefree() is not properly implemented yet + 2 + + A case that triggered an assertion at some point:: + + sage: v = QQ.valuation(3) + sage: R. = QQ[] + sage: f = x^36 + 60552000*x^33 + 268157412*x^30 + 173881701*x^27 + 266324841*x^24 + 83125683*x^21 + 111803814*x^18 + 31925826*x^15 + 205726716*x^12 +17990262*x^9 + 351459648*x^6 + 127014399*x^3 + 359254116 + sage: v.mac_lane_approximants(f) + [[ Gauss valuation induced by 3-adic valuation, v(x) = 1/3, v(x^3 + 6) = 3/2, v(x^12 + 24*x^9 + 216*x^6 + 864*x^3 + 2025) = 13/2, v(x^36 + 60552000*x^33 + 268157412*x^30 + 173881701*x^27 + 266324841*x^24 + 83125683*x^21 + 111803814*x^18 + 31925826*x^15 + 205726716*x^12 + 17990262*x^9 + 351459648*x^6 + 127014399*x^3 + 359254116) = +Infinity ]] + + """ + R = G.parent() + if R.base_ring() is not self.domain(): + raise ValueError("G must be defined over the domain of this valuation") + + from sage.misc.misc import verbose + verbose("Approximants of %r on %r towards %r"%(self, self.domain(), G), level=3) + + from sage.rings.all import infinity + from sage.rings.valuation.gauss_valuation import GaussValuation + + if not all([self(c) >= 0 for c in G.coefficients()]): + raise ValueError("G must be integral") + + if require_maximal_degree: + # we can only assert maximality of degrees when E and F are final + require_final_EF = True + + if not assume_squarefree: + if require_final_EF and not G.is_squarefree(): + raise ValueError("G must be squarefree") + else: + # if only required_precision is set, we do not need to check + # whether G is squarefree. If G is not squarefree, we compute + # valuations corresponding to approximants for all the + # squarefree factors of G (up to required_precision.) + pass + + def is_sufficient(leaf, others): + if leaf.valuation.mu() < required_precision: + return False + if require_final_EF and not leaf.ef: + return False + if require_maximal_degree and leaf.valuation.phi().degree() != leaf.valuation.E()*leaf.valuation.F(): + return False + if require_incomparability: + if any(leaf.valuation <= o.valuation for o in others): + return False + return True + + seed = MacLaneApproximantNode(GaussValuation(R,self), None, G.degree() == 1, G.degree(), None, None) + seed.forced_leaf = is_sufficient(seed, []) + + def create_children(node): + new_leafs = [] + if node.forced_leaf: + return new_leafs + augmentations = node.valuation.mac_lane_step(G, + report_degree_bounds_and_caches=True, + coefficients=node.coefficients, + valuations=node.valuations, + check=False, + principal_part_bound=node.principal_part_bound) + for w, bound, principal_part_bound, coefficients, valuations in augmentations: + ef = bound == w.E()*w.F() + new_leafs.append(MacLaneApproximantNode(w, node, ef, principal_part_bound, coefficients, valuations)) + for leaf in new_leafs: + if is_sufficient(leaf, [l for l in new_leafs if l is not leaf]): + leaf.forced_leaf = True + return new_leafs + + def reduce_tree(v, w): + return v + w + + from sage.all import RecursivelyEnumeratedSet + tree = RecursivelyEnumeratedSet([seed], + successors = create_children, + structure = 'forest', + enumeration = 'breadth') + # this is a tad faster but annoying for profiling / debugging + if algorithm == 'parallel': + nodes = tree.map_reduce( + map_function = lambda x: [x], + reduce_init = []) + elif algorithm == 'serial': + from sage.parallel.map_reduce import RESetMapReduce + nodes = RESetMapReduce( + forest = tree, + map_function = lambda x: [x], + reduce_init = []).run_serial() + else: + raise NotImplementedError(algorithm) + leafs = set([node.valuation for node in nodes]) + for node in nodes: + if node.parent is None: + continue + v = node.parent.valuation + if v in leafs: + leafs.remove(v) + + # The order of the leafs is not predictable in parallel mode and in + # serial mode it depends on the hash functions and so on the underlying + # archictecture (32/64 bit). There is no natural ordering on these + # valuations but it is very convenient for doctesting to return them in + # some stable order, so we just order them by their string + # representation which should be very fast. + try: + ret = sorted(leafs, key=str) + except Exception: + # if for some reason the valuation can not be printed, we leave them unsorted + ret = list(leafs) + + return ret + + @cached_method + def _pow(self, x, e, error): + r""" + Return `x^e`. + + This method does not compute the exact value of `x^e` but only an + element that differs from the correct result by an error with valuation + at least ``error``. + + EXAMPLES:: + + sage: v = QQ.valuation(2) + sage: v._pow(2, 2, error=4) + 4 + sage: v._pow(2, 1000, error=4) + 0 + + """ + if e == 0: + return self.domain().one() + if e == 1: + return self.simplify(x, error=error) + if e % 2 == 0: + return self._pow(self.simplify(x*x, error=error*2/e), e//2, error=error) + else: + return self.simplify(x*self._pow(x, e-1, error=error*(e-1)/e), error=error) + + def mac_lane_approximant(self, G, valuation, approximants = None): + r""" + Return the approximant from :meth:`mac_lane_approximants` for ``G`` + which is approximated by or approximates ``valuation``. + + INPUT: + + - ``G`` -- a monic squarefree integral polynomial in a univariate + polynomial ring over the domain of this valuation + + - ``valuation`` -- a valuation on the parent of ``G`` + + - ``approximants`` -- the output of :meth:`mac_lane_approximants`. + If not given, it is computed. + + EXAMPLES:: + + sage: v = QQ.valuation(2) + sage: R. = QQ[] + sage: G = x^2 + 1 + + We can select an approximant by approximating it:: + + sage: w = GaussValuation(R, v).augmentation(x + 1, 1/2) + sage: v.mac_lane_approximant(G, w) + [ Gauss valuation induced by 2-adic valuation, v(x + 1) = 1/2 ] + + As long as this is the only matching approximant, the approximation can + be very coarse:: + + sage: w = GaussValuation(R, v) + sage: v.mac_lane_approximant(G, w) + [ Gauss valuation induced by 2-adic valuation, v(x + 1) = 1/2 ] + + Or it can be very specific:: + + sage: w = GaussValuation(R, v).augmentation(x + 1, 1/2).augmentation(G, infinity) + sage: v.mac_lane_approximant(G, w) + [ Gauss valuation induced by 2-adic valuation, v(x + 1) = 1/2 ] + + But it must be an approximation of an approximant:: + + sage: w = GaussValuation(R, v).augmentation(x, 1/2) + sage: v.mac_lane_approximant(G, w) + Traceback (most recent call last): + ... + ValueError: The valuation [ Gauss valuation induced by 2-adic valuation, v(x) = 1/2 ] is not an approximant for a valuation which extends 2-adic valuation with respect to x^2 + 1 since the valuation of x^2 + 1 does not increase in every step + + The ``valuation`` must single out one approximant:: + + sage: G = x^2 - 1 + sage: w = GaussValuation(R, v) + sage: v.mac_lane_approximant(G, w) + Traceback (most recent call last): + ... + ValueError: The valuation Gauss valuation induced by 2-adic valuation does not approximate a unique extension of 2-adic valuation with respect to x^2 - 1 + + sage: w = GaussValuation(R, v).augmentation(x + 1, 1) + sage: v.mac_lane_approximant(G, w) + Traceback (most recent call last): + ... + ValueError: The valuation [ Gauss valuation induced by 2-adic valuation, v(x + 1) = 1 ] does not approximate a unique extension of 2-adic valuation with respect to x^2 - 1 + + sage: w = GaussValuation(R, v).augmentation(x + 1, 2) + sage: v.mac_lane_approximant(G, w) + [ Gauss valuation induced by 2-adic valuation, v(x + 1) = +Infinity ] + + sage: w = GaussValuation(R, v).augmentation(x + 3, 2) + sage: v.mac_lane_approximant(G, w) + [ Gauss valuation induced by 2-adic valuation, v(x + 1) = 1 ] + + """ + if valuation.restriction(valuation.domain().base_ring()) is not self: + raise ValueError + + # Check that valuation is an approximant for a valuation + # on domain that extends its restriction to the base field. + from sage.rings.all import infinity + if valuation(G) is not infinity: + v = valuation + while not v.is_gauss_valuation(): + if v(G) <= v._base_valuation(G): + raise ValueError("The valuation %r is not an approximant for a valuation which extends %r with respect to %r since the valuation of %r does not increase in every step"%(valuation, self, G, G)) + v = v._base_valuation + + if approximants is None: + approximants = self.mac_lane_approximants(G) + + assert all(approximant.domain() is valuation.domain() for approximant in approximants) + + greater_approximants = [w for w in approximants if w >= valuation] + if len(greater_approximants) > 1: + raise ValueError("The valuation %r does not approximate a unique extension of %r with respect to %r"%(valuation, self, G)) + if len(greater_approximants) == 1: + return greater_approximants[0] + + smaller_approximants = [w for w in approximants if w <= valuation] + if len(smaller_approximants) > 1: + raise ValueError("The valuation %r is not approximated by a unique extension of %r with respect to %r"%(valuation, self, G)) + if len(smaller_approximants) == 0: + raise ValueError("The valuation %r is not related to an extension of %r with respect to %r"%(valuation, self, G)) + return smaller_approximants[0] + + def montes_factorization(self, G, assume_squarefree=False, required_precision=None): + """ + Factor ``G`` over the completion of the domain of this valuation. + + INPUT: + + - ``G`` -- a monic polynomial over the domain of this valuation + + - ``assume_squarefree`` -- a boolean (default: ``False``), whether to + assume ``G`` to be squarefree + + - ``required_precision`` -- a number or infinity (default: + infinity); if ``infinity``, the returned polynomials are actual factors of + ``G``, otherwise they are only factors with precision at least + ``required_precision``. + + ALGORITHM: + + We compute :meth:`mac_lane_approximants` with ``required_precision``. + The key polynomials approximate factors of ``G``. This can be very + slow unless ``required_precision`` is set to zero. Single factor + lifting could improve this significantly. + + EXAMPLES:: + + sage: k=Qp(5,4) + sage: v = k.valuation() + sage: R.=k[] + sage: G = x^2 + 1 + sage: v.montes_factorization(G) + ((1 + O(5^4))*x + (2 + 5 + 2*5^2 + 5^3 + O(5^4))) * ((1 + O(5^4))*x + (3 + 3*5 + 2*5^2 + 3*5^3 + O(5^4))) + + The computation might not terminate over incomplete fields (in + particular because the factors can not be represented there):: + + sage: R. = QQ[] + sage: v = QQ.valuation(2) + sage: v.montes_factorization(x^2 + 1) + x^2 + 1 + + sage: v.montes_factorization(x^2 - 1) + (x - 1) * (x + 1) + + sage: v.montes_factorization(x^2 - 1, required_precision=5) + (x + 1) * (x + 31) + + TESTS: + + Some examples that Sebastian Pauli used in a talk at Sage Days 87. + + In this example, ``f`` factors as three factors of degree 50 over an + unramified extension:: + + sage: R. = ZqFM(125) + sage: S. = R[] + sage: f = (x^6+2)^25 + 5 + sage: v = R.valuation() + sage: v.montes_factorization(f, assume_squarefree=True, required_precision=0) + ((1 + O(5^20))*x^50 + (2*5 + O(5^20))*x^45 + (5 + O(5^20))*x^40 + (5 + O(5^20))*x^30 + (2 + O(5^20))*x^25 + (3*5 + O(5^20))*x^20 + (2*5 + O(5^20))*x^10 + (2*5 + O(5^20))*x^5 + (5 + O(5^20))*x + 3 + 5 + O(5^20)) * ((1 + O(5^20))*x^50 + (3*5 + O(5^20))*x^45 + (5 + O(5^20))*x^40 + (5 + O(5^20))*x^30 + (3 + 4*5 + O(5^20))*x^25 + (3*5 + O(5^20))*x^20 + (2*5 + O(5^20))*x^10 + (3*5 + O(5^20))*x^5 + (4*5 + O(5^20))*x + 3 + 5 + O(5^20)) * ((1 + O(5^20))*x^50 + (3*5 + O(5^20))*x^40 + (3*5 + O(5^20))*x^30 + (4*5 + O(5^20))*x^20 + (5 + O(5^20))*x^10 + 3 + 5 + O(5^20)) + + In this case, ``f`` factors into degrees 1, 2, and 5 over a totally ramified extension:: + + sage: R = Zp(5) + sage: S. = R[] + sage: R. = R.extension(w^3 + 5) + sage: S. = R[] + sage: f = (x^3 + 5)*(x^5 + w) + 625 + sage: v = R.valuation() + sage: v.montes_factorization(f, assume_squarefree=True, required_precision=0) + ((1 + O(w^60))*x + 4*w + O(w^60)) * ((1 + O(w^60))*x^2 + (w + O(w^60))*x + w^2 + O(w^60)) * ((1 + O(w^60))*x^5 + w + O(w^60)) + + REFERENCES: + + The underlying algorithm is described in [Mac1936II]_ and thoroughly + analyzed in [GMN2008]_. + + """ + if required_precision is None: + from sage.rings.all import infinity + required_precision = infinity + + R = G.parent() + if R.base_ring() is not self.domain(): + raise ValueError("G must be defined over the domain of this valuation") + if not G.is_monic(): + raise ValueError("G must be monic") + if not all([self(c)>=0 for c in G.coefficients()]): + raise ValueError("G must be integral") + + # W contains approximate factors of G + W = self.mac_lane_approximants(G, required_precision=required_precision, require_maximal_degree=True, assume_squarefree=assume_squarefree) + ret = [w.phi() for w in W] + + from sage.structure.factorization import Factorization + return Factorization([ (g,1) for g in ret ], simplify=False) + + def _ge_(self, other): + r""" + Return whether this valuation is greater than or equal to ``other`` + pointwise. + + EXAMPLES:: + + sage: v = valuations.TrivialValuation(QQ) + sage: w = QQ.valuation(2) + sage: v >= w + False + + """ + if other.is_trivial(): + return other.is_discrete_valuation() + return super(DiscreteValuation, self)._ge_(other) + + +class MacLaneApproximantNode(object): + r""" + A node in the tree computed by :meth:`DiscreteValuation.mac_lane_approximants` + + Leaves in the computation of the tree of approximants + :meth:`~DiscreteValuation.mac_lane_approximants`. Each vertex consists of a + tuple ``(v,ef,p,coeffs,vals)`` where ``v`` is an approximant, i.e., a + valuation, ef is a boolean, ``p`` is the parent of this vertex, and + ``coeffs`` and ``vals`` are cached values. (Only ``v`` and ``ef`` are + relevant, everything else are caches/debug info.) The boolean ``ef`` + denotes whether ``v`` already has the final ramification index E and + residue degree F of this approximant. An edge V -- P represents the + relation ``P.v`` `≤` ``V.v`` (pointwise on the polynomial ring K[x]) between the + valuations. + + TESTS:: + + sage: v = ZZ.valuation(3) + sage: v.extension(GaussianIntegers()) # indirect doctest + 3-adic valuation + + """ + def __init__(self, valuation, parent, ef, principal_part_bound, coefficients, valuations): + r""" + TESTS:: + + sage: from sage.rings.valuation.valuation import MacLaneApproximantNode + sage: node = MacLaneApproximantNode(QQ.valuation(2), None, 1, None, None, None) + sage: TestSuite(node).run() + + """ + self.valuation = valuation + self.parent = parent + self.ef = ef + self.principal_part_bound = principal_part_bound + self.coefficients = coefficients + self.valuations = valuations + self.forced_leaf = False + + def __eq__(self, other): + r""" + Return whether this node is equal to ``other``. + + EXAMPLES:: + + sage: from sage.rings.valuation.valuation import MacLaneApproximantNode + sage: n = MacLaneApproximantNode(QQ.valuation(2), None, 1, None, None, None) + sage: m = MacLaneApproximantNode(QQ.valuation(3), None, 1, None, None, None) + sage: n == m + False + sage: n == n + True + + """ + if type(self) != type(other): + return False + return (self.valuation, self.parent, self.ef, self.principal_part_bound, self.coefficients, self.valuations, self.forced_leaf) == (other.valuation, other.parent, other.ef, other.principal_part_bound, other.coefficients, other.valuations, other.forced_leaf) + + def __ne__(self, other): + r""" + Return whether this node is not equal to ``other``. + + EXAMPLES:: + + sage: from sage.rings.valuation.valuation import MacLaneApproximantNode + sage: n = MacLaneApproximantNode(QQ.valuation(2), None, 1, None, None, None) + sage: m = MacLaneApproximantNode(QQ.valuation(3), None, 1, None, None, None) + sage: n != m + True + sage: n != n + False + + """ + return not (self == other) + + # mutable object - not hashable + __hash__ = None diff --git a/src/sage/rings/valuation/valuation_space.py b/src/sage/rings/valuation/valuation_space.py new file mode 100644 index 00000000000..e1958fd1cf7 --- /dev/null +++ b/src/sage/rings/valuation/valuation_space.py @@ -0,0 +1,1664 @@ +# -*- coding: utf-8 -*- +r""" +Spaces of valuations + +This module provides spaces of exponential pseudo-valuations on integral +domains. It currently only provides support for such valuations if they are +discrete, i.e., their image is a discrete additive subgroup of the rational +numbers extended by `\infty`. + +AUTHORS: + +- Julian Rüth (2016-10-14): initial version + +EXAMPLES:: + + sage: QQ.valuation(2).parent() + Discrete pseudo-valuations on Rational Field + +.. NOTE:: + + Note that many tests not only in this module do not create instances of + valuations directly since this gives the wrong inheritance structure on + the resulting objects:: + + sage: from sage.rings.valuation.valuation_space import DiscretePseudoValuationSpace + sage: from sage.rings.valuation.trivial_valuation import TrivialDiscretePseudoValuation + sage: H = DiscretePseudoValuationSpace(QQ) + sage: v = TrivialDiscretePseudoValuation(H) + sage: v._test_category() + Traceback (most recent call last): + ... + AssertionError: False is not true + + Instead, the valuations need to be created through the + ``__make_element_class__`` of the containing space:: + + sage: from sage.rings.valuation.trivial_valuation import TrivialDiscretePseudoValuation + sage: v = H.__make_element_class__(TrivialDiscretePseudoValuation)(H) + sage: v._test_category() + + The factories such as ``TrivialPseudoValuation`` provide the right + inheritance structure:: + + sage: v = valuations.TrivialPseudoValuation(QQ) + sage: v._test_category() + +""" +#***************************************************************************** +# Copyright (C) 2016-2017 Julian Rüth +# +# Distributed under the terms of the GNU General Public License (GPL) +# as published by the Free Software Foundation; either version 2 of +# the License, or (at your option) any later version. +# http://www.gnu.org/licenses/ +#***************************************************************************** +from sage.categories.homset import Homset +from sage.misc.lazy_attribute import lazy_attribute +from sage.misc.abstract_method import abstract_method +from sage.structure.unique_representation import UniqueRepresentation +from sage.misc.cachefunc import cached_method + +class DiscretePseudoValuationSpace(UniqueRepresentation, Homset): + r""" + The space of discrete pseudo-valuations on ``domain``. + + EXAMPLES:: + + sage: from sage.rings.valuation.valuation_space import DiscretePseudoValuationSpace + sage: H = DiscretePseudoValuationSpace(QQ) + sage: QQ.valuation(2) in H + True + + .. NOTE:: + + We do not distinguish between the space of discrete valuations and the + space of discrete pseudo-valuations. This is entirely for practical + reasons: We would like to model the fact that every discrete valuation + is also a discrete pseudo-valuation. At first, it seems to be + sufficient to make sure that the ``in`` operator works which can + essentially be achieved by overriding ``_element_constructor_`` of + the space of discrete pseudo-valuations to accept discrete valuations + by just returning them. Currently, however, if one does not change the + parent of an element in ``_element_constructor_`` to ``self``, then + one can not register that conversion as a coercion. Consequently, the + operators ``<=`` and ``>=`` can not be made to work between discrete + valuations and discrete pseudo-valuations on the same domain (because + the implementation only calls ``_richcmp`` if both operands have the + same parent.) Of course, we could override ``__ge__`` and ``__le__`` + but then we would likely run into other surprises. + So in the end, we went for a single homspace for all discrete + valuations (pseudo or not) as this makes the implementation much + easier. + + .. TODO:: + + The comparison problem might be fixed by :trac:`22029` or similar. + + TESTS:: + + sage: TestSuite(H).run() # long time + + """ + def __init__(self, domain): + r""" + TESTS:: + + sage: from sage.rings.valuation.valuation_space import DiscretePseudoValuationSpace + sage: isinstance(QQ.valuation(2).parent(), DiscretePseudoValuationSpace) + True + + """ + from value_group import DiscreteValuationCodomain + # A valuation is a map from an additive semigroup to an additive semigroup, however, it + # does not preserve that structure. It is therefore only a morphism in the category of sets. + from sage.categories.all import Sets + + UniqueRepresentation.__init__(self) + Homset.__init__(self, domain, DiscreteValuationCodomain(), category = Sets()) + + from sage.categories.domains import Domains + if domain not in Domains(): + raise ValueError("domain must be an integral domain") + + @lazy_attribute + def _abstract_element_class(self): + r""" + Return an abstract base class for all valuations in this space. + + This is used to extend every valuation with a number of generic methods + that are independent of implementation details. + + Usually, extensions of this kind would be done by implementing an + appropriate class ``MorphismMethods`` in the category of this homset. + However, there is no category whose arrows are the valuations, so we + need to move this magic down to the level of the actual homset. + + EXAMPLES:: + + sage: from sage.rings.valuation.valuation_space import DiscretePseudoValuationSpace + sage: isinstance(QQ.valuation(2), DiscretePseudoValuationSpace.ElementMethods) # indirect doctest + True + + """ + class_name = "%s._abstract_element_class"%self.__class__.__name__ + from sage.structure.dynamic_class import dynamic_class + return dynamic_class(class_name, (super(DiscretePseudoValuationSpace,self)._abstract_element_class, self.__class__.ElementMethods)) + + def _get_action_(self, S, op, self_on_left): + r""" + Return the ``op`` action of ``S`` on elements in this space. + + EXAMPLES:: + + sage: v = QQ.valuation(2) + sage: from operator import mul + sage: v.parent().get_action(ZZ, mul) # indirect doctest + Right action by Integer Ring on Discrete pseudo-valuations on Rational Field + + """ + from operator import mul, div + from sage.rings.all import QQ, InfinityRing, ZZ + if op == mul and (S is InfinityRing or S is QQ or S is ZZ): + return ScaleAction(S, self, not self_on_left, op) + return None + + def _an_element_(self): + r""" + Return a trivial valuation in this space. + + EXAMPLES:: + + sage: from sage.rings.valuation.valuation_space import DiscretePseudoValuationSpace + sage: DiscretePseudoValuationSpace(QQ).an_element() # indirect doctest + Trivial pseudo-valuation on Rational Field + + """ + from trivial_valuation import TrivialPseudoValuation + return TrivialPseudoValuation(self.domain()) + + def _repr_(self): + r""" + Return a printable representation of this space. + + EXAMPLES:: + + sage: from sage.rings.valuation.valuation_space import DiscretePseudoValuationSpace + sage: DiscretePseudoValuationSpace(QQ) # indirect doctest + Discrete pseudo-valuations on Rational Field + + """ + return "Discrete pseudo-valuations on %r"%(self.domain(),) + + def __contains__(self, x): + r""" + Return whether ``x`` is a valuation in this space. + + EXAMPLES:: + + sage: from sage.rings.valuation.valuation_space import DiscretePseudoValuationSpace + sage: H = DiscretePseudoValuationSpace(QQ) + sage: H.an_element() in H + True + sage: QQ.valuation(2) in H + True + + """ + # override the logic from Homset with the original implementation for Parent + # which entirely relies on a proper implementation of + # _element_constructor_ and coercion maps + from sage.structure.parent import Parent + return Parent.__contains__(self, x) + + def __call__(self, x): + r""" + Create an element in this space from ``x``. + + EXAMPLES:: + + sage: from sage.rings.valuation.valuation_space import DiscretePseudoValuationSpace + sage: H = DiscretePseudoValuationSpace(QQ) + sage: H(QQ.valuation(2)) + 2-adic valuation + + """ + # override the logic from Homset with the original implementation for Parent + # which entirely relies on a proper implementation of + # _element_constructor_ and coercion maps + from sage.structure.parent import Parent + return Parent.__call__(self, x) + + def _element_constructor_(self, x): + r""" + Create an element in this space from ``x``, + + EXAMPLES: + + We try to convert valuations defined on different domains by changing + their base ring:: + + sage: from sage.rings.valuation.valuation_space import DiscretePseudoValuationSpace + sage: Z = DiscretePseudoValuationSpace(ZZ) + sage: Q = DiscretePseudoValuationSpace(QQ) + sage: v = ZZ.valuation(2) + sage: v in Q + False + sage: Q(v) in Q + True + sage: Q(v) in Z + False + sage: Z(Q(v)) in Z + True + + """ + if isinstance(x.parent(), DiscretePseudoValuationSpace): + if x.domain() is not self.domain(): + try: + return self(x.change_domain(self.domain())) + except NotImplementedError: + pass + else: + return x + raise ValueError("element can not be converted into the space of %r"%(self,)) + + class ElementMethods: + r""" + Provides methods for discrete pseudo-valuations that are added + automatically to valuations in this space. + + EXAMPLES: + + Here is an example of a method that is automagically added to a + discrete valuation:: + + sage: from sage.rings.valuation.valuation_space import DiscretePseudoValuationSpace + sage: H = DiscretePseudoValuationSpace(QQ) + sage: QQ.valuation(2).is_discrete_pseudo_valuation() # indirect doctest + True + + The methods will be provided even if the concrete type is not created + with ``__make_element_class__``:: + + sage: from sage.rings.valuation.valuation import DiscretePseudoValuation + sage: m = DiscretePseudoValuation(H) + sage: m.parent() is H + True + sage: m.is_discrete_pseudo_valuation() + True + + However, the category framework advises you to use inheritance:: + + sage: m._test_category() + Traceback (most recent call last): + ... + AssertionError: False is not true + + Using ``__make_element_class__``, makes your concrete valuation inherit + from this class:: + + sage: m = H.__make_element_class__(DiscretePseudoValuation)(H) + sage: m._test_category() + + """ + def is_discrete_pseudo_valuation(self): + r""" + Return whether this valuation is a discrete pseudo-valuation. + + EXAMPLES:: + + sage: QQ.valuation(2).is_discrete_pseudo_valuation() + True + + """ + return True + + @abstract_method + def is_discrete_valuation(self): + r""" + Return whether this valuation is a discrete valuation, i.e., + whether it is a :meth:`discrete pseudo valuation + ` that only sends zero to `\infty`. + + EXAMPLES:: + + sage: QQ.valuation(2).is_discrete_valuation() + True + + """ + + def is_negative_pseudo_valuation(self): + r""" + Return whether this valuation is a discrete pseudo-valuation that + does attain `-\infty`, i.e., it is non-trivial and its domain + contains an element with valuation `\infty` that has an inverse. + + EXAMPLES:: + + sage: QQ.valuation(2).is_negative_pseudo_valuation() + False + + """ + from sage.categories.all import Fields + if self.is_discrete_valuation(): + return False + elif self.domain() in Fields(): + return True + raise NotImplementedError + + @cached_method + def is_trivial(self): + r""" + Return whether this valuation is trivial, i.e., whether it is + constant `\infty` or constant zero for everything but the zero + element. + + Subclasses need to override this method if they do not implement + :meth:`uniformizer`. + + EXAMPLES:: + + sage: QQ.valuation(7).is_trivial() + False + + """ + from sage.rings.all import infinity + if self(self.domain().one()) is infinity: + # the constant infinity + return True + if self(self.uniformizer()) != 0: + # not constant on the non-zero elements + return False + return True + + @abstract_method + def uniformizer(self): + r""" + Return an element in the domain which has positive valuation and + generates the value group of this valuation. + + EXAMPLES:: + + sage: QQ.valuation(11).uniformizer() + 11 + + Trivial valuations have no uniformizer:: + + sage: from sage.rings.valuation.valuation_space import DiscretePseudoValuationSpace + sage: v = DiscretePseudoValuationSpace(QQ).an_element() + sage: v.is_trivial() + True + sage: v.uniformizer() + Traceback (most recent call last): + ... + ValueError: Trivial valuations do not define a uniformizing element + + """ + + @cached_method + def value_group(self): + r""" + Return the value group of this discrete pseudo-valuation, the + discrete additive subgroup of the rational numbers which is + generated by the valuation of the :meth:`uniformizer`. + + EXAMPLES:: + + sage: QQ.valuation(2).value_group() + Additive Abelian Group generated by 1 + + A pseudo-valuation that is `\infty` everywhere, does not have a + value group:: + + sage: from sage.rings.valuation.valuation_space import DiscretePseudoValuationSpace + sage: v = DiscretePseudoValuationSpace(QQ).an_element() + sage: v.value_group() + Traceback (most recent call last): + ... + ValueError: The trivial pseudo-valuation that is infinity everywhere does not have a value group. + + """ + from value_group import DiscreteValueGroup + return DiscreteValueGroup(self(self.uniformizer())) + + def value_semigroup(self): + r""" + Return the value semigroup of this discrete pseudo-valuation, the + additive subsemigroup of the rational numbers which is generated by + the valuations of the elements in the domain. + + EXAMPLES: + + Most commonly, in particular over fields, the semigroup is the + group generated by the valuation of the uniformizer:: + + sage: G = QQ.valuation(2).value_semigroup(); G + Additive Abelian Semigroup generated by -1, 1 + sage: G in AdditiveMagmas().AdditiveAssociative().AdditiveUnital().AdditiveInverse() + True + + If the domain is a discrete valuation ring, then the semigroup + consists of the positive elements of the :meth:`value_group`:: + + sage: Zp(2).valuation().value_semigroup() + Additive Abelian Semigroup generated by 1 + + The semigroup can have a more complicated structure when the + uniformizer is not in the domain:: + + sage: v = ZZ.valuation(2) + sage: R. = ZZ[] + sage: w = GaussValuation(R, v) + sage: u = w.augmentation(x, 5/3) + sage: u.value_semigroup() + Additive Abelian Semigroup generated by 1, 5/3 + + """ + from sage.categories.fields import Fields + if self.domain() in Fields(): + from value_group import DiscreteValueSemigroup + # return the semigroup generated by the elements of the group + return DiscreteValueSemigroup([]) + self.value_group() + raise NotImplementedError("can not determine value semigroup of %r"%(self,)) + + def element_with_valuation(self, s): + r""" + Return an element in the domain of this valuation with valuation + ``s``. + + EXAMPLES:: + + sage: v = ZZ.valuation(2) + sage: v.element_with_valuation(10) + 1024 + + """ + from sage.rings.all import QQ, ZZ, infinity + s = QQ.coerce(s) + if s not in self.value_semigroup(): + raise ValueError("s must be in the value semigroup of this valuation but %r is not in %r"%(s, self.value_semigroup())) + if s == 0: + return self.domain().one() + exp = s/self.value_group().gen() + if exp not in ZZ: + raise NotImplementedError("s must be a multiple of %r but %r is not"%(self.value_group().gen(), s)) + ret = self.domain()(self.uniformizer() ** ZZ(exp)) + return self.simplify(ret, error=s) + + @abstract_method + def residue_ring(self): + r""" + Return the residue ring of this valuation, i.e., the elements of + non-negative valuation modulo the elements of positive valuation. + EXAMPLES:: + + sage: QQ.valuation(2).residue_ring() + Finite Field of size 2 + sage: valuations.TrivialValuation(QQ).residue_ring() + Rational Field + + Note that a residue ring always exists, even when a residue field + may not:: + + sage: valuations.TrivialPseudoValuation(QQ).residue_ring() + Quotient of Rational Field by the ideal (1) + sage: valuations.TrivialValuation(ZZ).residue_ring() + Integer Ring + sage: GaussValuation(ZZ['x'], ZZ.valuation(2)).residue_ring() + Univariate Polynomial Ring in x over Finite Field of size 2 (using ...) + + + """ + + def residue_field(self): + r""" + Return the residue field of this valuation, i.e., the field of + fractions of the :meth:`residue_ring`, the elements of non-negative + valuation modulo the elements of positive valuation. + + EXAMPLES:: + + sage: QQ.valuation(2).residue_field() + Finite Field of size 2 + sage: valuations.TrivialValuation(QQ).residue_field() + Rational Field + + sage: valuations.TrivialValuation(ZZ).residue_field() + Rational Field + sage: GaussValuation(ZZ['x'], ZZ.valuation(2)).residue_field() + Rational function field in x over Finite Field of size 2 + + """ + ret = self.residue_ring() + from sage.categories.fields import Fields + if ret in Fields(): + return ret + from sage.rings.polynomial.polynomial_ring import is_PolynomialRing + if is_PolynomialRing(ret): + from sage.rings.function_field.all import FunctionField + return FunctionField(ret.base_ring().fraction_field(), names=(ret.variable_name(),)) + return ret.fraction_field() + + + @abstract_method + def reduce(self, x): + r""" + Return the image of ``x`` in the :meth:`residue_ring` of this + valuation. + + EXAMPLES:: + + sage: v = QQ.valuation(2) + sage: v.reduce(2) + 0 + sage: v.reduce(1) + 1 + sage: v.reduce(1/3) + 1 + sage: v.reduce(1/2) + Traceback (most recent call last): + ... + ValueError: reduction is only defined for elements of non-negative valuation + + """ + + @abstract_method + def lift(self, X): + r""" + Return a lift of ``X`` in the domain which reduces down to ``X`` + again via :meth:`reduce`. + + EXAMPLES:: + + sage: v = QQ.valuation(2) + sage: v.lift(v.residue_ring().one()) + 1 + + """ + + def extension(self, ring): + r""" + Return the unique extension of this valuation to ``ring``. + + EXAMPLES:: + + sage: v = ZZ.valuation(2) + sage: w = v.extension(QQ) + sage: w.domain() + Rational Field + + """ + extensions = self.extensions(ring) + assert(len(extensions)) + if len(extensions) > 1: + raise ValueError("there is no unique extension of %r from %r to %r"%(self, self.domain(), ring)) + return extensions[0] + + def extensions(self, ring): + r""" + Return the extensions of this valuation to ``ring``. + + EXAMPLES:: + + sage: v = ZZ.valuation(2) + sage: v.extensions(QQ) + [2-adic valuation] + + """ + if ring is self.domain(): + return [self] + raise NotImplementedError("extending %r from %r to %r not implemented"%(self, self.domain(), ring)) + + def restriction(self, ring): + r""" + Return the restriction of this valuation to ``ring``. + + EXAMPLES:: + + sage: v = QQ.valuation(2) + sage: w = v.restriction(ZZ) + sage: w.domain() + Integer Ring + + """ + if ring is self.domain(): + return self + raise NotImplementedError("restricting %r from %r to %r not implemented"%(self, self.domain(), ring)) + + def change_domain(self, ring): + r""" + Return this valuation over ``ring``. + + Unlike :meth:`extension` or :meth:`restriction`, this might not be + completely sane mathematically. It is essentially a conversion of + this valuation into another space of valuations. + + EXAMPLES:: + + sage: v = QQ.valuation(3) + sage: v.change_domain(ZZ) + 3-adic valuation + + """ + if ring is self.domain(): + return self + if self.domain().is_subring(ring): + return self.extension(ring) + if ring.is_subring(self.domain()): + return self.restriction(ring) + raise NotImplementedError("changing %r from %r to %r not implemented"%(self, self.domain(), ring)) + + def scale(self, scalar): + r""" + Return this valuation scaled by ``scalar``. + + INPUT: + + - ``scalar`` -- a non-negative rational number or infinity + + EXAMPLES:: + + sage: v = ZZ.valuation(3) + sage: w = v.scale(3) + sage: w(3) + 3 + + Scaling can also be done through multiplication with a scalar:: + + sage: w/3 == v + True + + Multiplication by zero produces the trivial discrete valuation:: + + sage: w = 0*v + sage: w(3) + 0 + sage: w(0) + +Infinity + + Multiplication by infinity produces the trivial discrete + pseudo-valuation:: + + sage: w = infinity*v + sage: w(3) + +Infinity + sage: w(0) + +Infinity + + """ + from sage.rings.all import infinity + if scalar is infinity: + from trivial_valuation import TrivialPseudoValuation + return TrivialPseudoValuation(self.domain()) + if scalar == 0: + from trivial_valuation import TrivialValuation + return TrivialValuation(self.domain()) + if scalar == 1: + return self + if scalar < 0: + raise ValueError("scalar must be non-negative") + if self.is_trivial(): + return self + + from scaled_valuation import ScaledValuation_generic + if isinstance(self, ScaledValuation_generic): + return self._base_valuation.scale(scalar * self._scale) + + from scaled_valuation import ScaledValuation + return ScaledValuation(self, scalar) + + def separating_element(self, others): + r""" + Return an element in the domain of this valuation which has + positive valuation with respect to this valuation but negative + valuation with respect to the valuations in ``others``. + + EXAMPLES:: + + sage: v2 = QQ.valuation(2) + sage: v3 = QQ.valuation(3) + sage: v5 = QQ.valuation(5) + sage: v2.separating_element([v3,v5]) + 4/15 + + """ + try: + iter(others) + except TypeError: + raise ValueError("others must be a list of valuations") + + for other in others + [self]: + if other.parent() is not self.parent(): + raise ValueError("all valuations must be valuations on %r but %r is a valuation on %r"%(self.domain(), other, other.domain())) + if not other.is_discrete_valuation(): + raise ValueError("all valuationss must be discrete valuations but %r is not"%(other,)) + if other.is_trivial(): + raise ValueError("all valuations must be non-trivial but %r is not"%(other,)) + + if len(others)==0: + return self.uniformizer() + + # see the proof of Lemma 6.9 in http://www1.spms.ntu.edu.sg/~frederique/antchap6.pdf + ret = self._strictly_separating_element(others[0]) + for i in range(1, len(others)): + # ret is an element which separates self and others[:i] + if others[i](ret) < 0: + # it also separates self and others[i] + continue + + delta = self._strictly_separating_element(others[i]) + if others[i](ret) == 0: + # combining powers of ret and delta, we produce a + # separating element for self and others[:i+1] + factor = ret + ret = delta + while any(other(ret) >= 0 for other in others[:i]): + assert(others[i](ret) < 0) + ret *= factor + else: # others[i](ret) > 0 + # construct an element which approximates a unit with respect to others[i] + # and has negative valuation with respect to others[:i] + from sage.rings.all import NN + for r in iter(NN): + # When we enter this loop we are essentially out of + # luck. The size of the coefficients is likely going + # through the roof here and this is not going to + # terminate in reasonable time. + factor = (ret**r)/(1+ret**r) + ret = factor * delta + if all([other(ret) < 0 for other in others[:i+1]]): + break + return ret + + def _strictly_separating_element(self, other): + r""" + Return an element in the domain of this valuation which has + positive valuation with respect to this valuation but negative + valuation with respect to ``other``. + + .. NOTE:: + + Overriding this method tends to be a nuissance as you need to + handle all possible types (as in Python type) of valuations. + This is essentially the same problem that you have when + implementing operators such as ``+`` or ``>=``. A sufficiently + fancy multimethod implementation could solve that here but + there is currently nothing like that in Sage/Python. + + EXAMPLES:: + + sage: v2 = QQ.valuation(2) + sage: v3 = QQ.valuation(3) + sage: v2._strictly_separating_element(v3) + 2/3 + + """ + from sage.rings.all import ZZ, NN, infinity + + numerator = self._weakly_separating_element(other) + n = self(numerator) + nn = other(numerator) + assert(n > 0) + assert(nn is not infinity) + if (nn < 0): + return numerator + + denominator = other._weakly_separating_element(self) + d = self(denominator) + dd = other(denominator) + assert(dd > 0) + assert(d is not infinity) + if d < 0: + # The following may fail if denominator is not inverible in the domain, + # but we don't have a better option this generically. + return self.domain()(~denominator) + + # We need non-negative integers a and b such that + # a*n - b*d > 0 and a*nn - b*dd < 0 + if nn == 0: + # the above becomes b != 0 and a/b > d/n + b = 1 + a = (d/n + 1).floor() + else: + # Since n,nn,d,dd are all non-negative this is essentially equivalent to + # a/b > d/n and b/a > nn/dd + # which is + # dd/nn > a/b > d/n + assert(dd/nn > d/n) + from sage.rings.continued_fraction import continued_fraction + ab_cf = [] + dn_cf = continued_fraction(d/n) + ddnn_cf = continued_fraction(dd/nn) + for i, (x,y) in enumerate(zip(dn_cf, ddnn_cf)): + if x == y: + ab_cf.append(x) + elif x < y: + if y > x+1 or len(ddnn_cf) > i+1: + ab_cf.append(x+1) + else: + # the expansion of dd/nn is ending, so we can't append x+1 + ab_cf.extend([x,1,1]) + elif y < x: + if x > y+1 or len(dn_cf) > i+1: + ab_cf.append(y+1) + else: + ab_cf.extend([y,1,1]) + ab = continued_fraction(ab_cf).value() + a,b = ab.numerator(), ab.denominator() + + ret = self.domain()(numerator**a / denominator**b) + assert(self(ret) > 0) + assert(other(ret) < 0) + return ret + + def _weakly_separating_element(self, other): + r""" + Return an element in the domain of this valuation which has + positive valuation with respect to this valuation and higher + valuation with respect to this valuation than with respect to + ``other``. + + .. NOTE:: + + Overriding this method tends to be a nuissance as you need to + handle all possible types (as in Python type) of valuations. + This is essentially the same problem that you have when + implementing operators such as ``+`` or ``>=``. A sufficiently + fancy multimethod implementation could solve that here but + there is currently nothing like that in Sage/Python. + + EXAMPLES:: + + sage: v2 = QQ.valuation(2) + sage: v3 = QQ.valuation(3) + sage: v2._weakly_separating_element(v3) + 2 + + """ + ret = self.uniformizer() + if self(ret) > other(ret): + return ret + raise NotImplementedError("weakly separating element for %r and %r"%(self, other)) + + def shift(self, x, s): + r""" + Shift ``x`` in its expansion with respect to :meth:`uniformizer` by + ``s`` "digits". + + For non-negative ``s``, this just returns ``x`` multiplied by a + power of the uniformizer `\pi`. + + For negative ``s``, it does the same but when not over a field, it + drops coefficients in the `\pi`-adic expension which have negative + valuation. + + EXAMPLES:: + + sage: v = ZZ.valuation(2) + sage: v.shift(1, 10) + 1024 + sage: v.shift(11, -1) + 5 + + For some rings, there is no clear `\pi`-adic expansion. In this + case, this method performs negative shifts by iterated division by + the uniformizer and substraction of a lift of the reduction:: + + sage: R. = ZZ[] + sage: v = ZZ.valuation(2) + sage: w = GaussValuation(R, v) + sage: w.shift(x, 1) + 2*x + sage: w.shift(2*x, -1) + x + sage: w.shift(x + 2*x^2, -1) + x^2 + + """ + from sage.rings.all import ZZ + x = self.domain().coerce(x) + s = self.value_group()(s) + if s == 0: + return x + + s = ZZ(s / self.value_group().gen()) + if s > 0: + return x * self.uniformizer()**s + else: # s < 0 + if ~self.uniformizer() in self.domain(): + return self.domain()(x / self.uniformizer()**(-s)) + else: + for i in range(-s): + if self(x) < 0: + raise NotImplementedError("can not compute general shifts over non-fields which contain elements of negative valuation") + x -= self.lift(self.reduce(x)) + x //= self.uniformizer() + return x + + def simplify(self, x, error=None, force=False): + r""" + Return a simplified version of ``x``. + + Produce an element which differs from ``x`` by an element of + valuation strictly greater than the valuation of ``x`` (or strictly + greater than ``error`` if set.) + + If ``force`` is not set, then expensive simplifications may be avoided. + + EXAMPLES:: + + sage: v = ZZ.valuation(2) + sage: v.simplify(6, force=True) + 2 + sage: v.simplify(6, error=0, force=True) + 0 + + """ + x = self.domain().coerce(x) + + if error is not None and self(x) > error: + return self.domain().zero() + return x + + def lower_bound(self, x): + r""" + Return a lower bound of this valuation at ``x``. + + Use this method to get an approximation of the valuation of ``x`` + when speed is more important than accuracy. + + EXAMPLES:: + + sage: v = ZZ.valuation(2) + sage: v.lower_bound(2^10) + 10 + + """ + return self(x) + + def upper_bound(self, x): + r""" + Return an upper bound of this valuation at ``x``. + + Use this method to get an approximation of the valuation of ``x`` + when speed is more important than accuracy. + + EXAMPLES:: + + sage: v = ZZ.valuation(2) + sage: v.upper_bound(2^10) + 10 + + """ + return self(x) + + def inverse(self, x, precision): + r""" + Return an approximate inverse of ``x``. + + The element returned is such that the product differs from 1 by an + element of valuation at least ``precision``. + + INPUT: + + - ``x`` -- an element in the domain of this valuation + + - ``precision`` -- a rational or infinity + + EXAMPLES:: + + sage: v = ZZ.valuation(2) + sage: x = 3 + sage: y = v.inverse(3, 2); y + 3 + sage: x*y - 1 + 8 + + This might not be possible for elements of positive valuation:: + + sage: v.inverse(2, 2) + Traceback (most recent call last): + ... + ValueError: element has no approximate inverse in this ring + + Of course this always works over fields:: + + sage: v = QQ.valuation(2) + sage: v.inverse(2, 2) + 1/2 + + """ + try: + return x.inverse_of_unit() + except: + raise NotImplementedError("can not compute approximate inverse with respect to this valuation") + + def _relative_size(self, x): + r""" + Return an estimate on the coefficient size of ``x``. + + The number returned is an estimate on the factor between the number of + Bits used by ``x`` and the minimal number of bits used by an element + congruent to ``x``. + + This is used by :meth:`simplify` to decide whether simplification of + coefficients is going to lead to a significant shrinking of the + coefficients of ``x``. + + EXAMPLES:: + + sage: v = Qp(2).valuation() + sage: v._relative_size(2) + 1 + + Some valuations do not overwrite this method because simplification + does not increase the speed of valuations, e.g., some `p`-adic + valuations:: + + sage: v._relative_size(2**20) + 1 + + """ + return 1 + + def _test_is_negative_pseudo_valuation(self, **options): + r""" + Check that :meth:`is_negative_pseudo_valuation` works correctly. + + TESTS:: + + sage: v = ZZ.valuation(3) + sage: v._test_is_negative_pseudo_valuation() + + """ + tester = self._tester(**options) + + if self.is_discrete_valuation(): + tester.assertFalse(self.is_negative_pseudo_valuation()) + return + + if not self.is_negative_pseudo_valuation(): + X = self.domain().some_elements() + for x in tester.some_elements(X): + from sage.rings.all import infinity + tester.assertNotEqual(self(x), -infinity) + + def _test_bounds(self, **options): + r""" + Check that :meth:`lower_bound` and :meth:`upper_bound` work + correctly. + + TESTS:: + + sage: v = ZZ.valuation(3) + sage: v._test_bounds() + + """ + tester = self._tester(**options) + + X = self.domain().some_elements() + for x in tester.some_elements(X): + tester.assertGreaterEqual(self.upper_bound(x), self(x)) + tester.assertLessEqual(self.lower_bound(x), self(x)) + + def _test_simplify(self, **options): + r""" + Check that :meth:`simplify` works correctly. + + TESTS:: + + sage: v = ZZ.valuation(3) + sage: v._test_simplify() + + """ + tester = self._tester(**options) + + try: + k = self.residue_ring() + has_residue_ring = True + except NotImplementedError: + # over non-fields (and especially polynomial rings over + # non-fields) computation of the residue ring is often + # difficult and not very interesting + from sage.categories.fields import Fields + if self.domain() not in Fields(): + return + raise + + X = self.domain().some_elements() + for x in tester.some_elements(X): + y = self.simplify(x) + tester.assertEqual(self(x), self(y)) + if self(x) >= 0 and has_residue_ring: + tester.assertEqual(self.reduce(x), self.reduce(y)) + + if self.is_trivial() and not self.is_discrete_valuation(): + return + + S = self.value_group().some_elements() + from itertools import product + for x,s in tester.some_elements(product(X, S)): + y = self.simplify(x, error=s) + if self.domain().is_exact(): + tester.assertGreaterEqual(self(x-y), s) + elif hasattr(y, 'precision_absolute'): + tester.assertGreaterEqual(self(x-y), min(s, y.precision_absolute())) + + def _test_shift(self, **options): + r""" + Check that :meth:`shift` works correctly. + + TESTS:: + + sage: v = ZZ.valuation(3) + sage: v._test_shift() + + """ + if self.is_trivial() and not self.is_discrete_valuation(): + return + + try: + self.residue_ring() + except Exception: + # it is not clear what a shift should be in this case + return + + tester = self._tester(**options) + X = self.domain().some_elements() + S = self.value_group().some_elements() + from itertools import product + for x,s in tester.some_elements(product(X, S)): + if self(x) < 0 and ~self.uniformizer() not in self.domain(): + # it is not clear what a shift should be in this case + continue + y = self.shift(x, s) + if s >= 0: + tester.assertGreaterEqual(self(y),self(x)) + from sage.categories.all import Fields + if self.domain().is_exact() and self.domain() in Fields(): + # the shift here sometimes fails if elements implement + # __floordiv__ incorrectly, see #23971 + tester.assertEqual(x, self.shift(y, -s)) + + def _test_scale(self, **options): + r""" + Check that :meth:`scale` works correctly. + + TESTS:: + + sage: v = ZZ.valuation(3) + sage: v._test_scale() + + """ + tester = self._tester(**options) + + from sage.rings.all import infinity, QQ + from trivial_valuation import TrivialValuation, TrivialPseudoValuation + + tester.assertEqual(QQ(0)*self, TrivialValuation(self.domain())) + tester.assertEqual(infinity*self, TrivialPseudoValuation(self.domain())) + + for s in tester.some_elements(QQ.some_elements()): + if s < 0: + with tester.assertRaises(ValueError): + s * self + continue + if s == 0: + continue + + scaled = s * self + + tester.assertEqual(self.is_trivial(), scaled.is_trivial()) + if not self.is_trivial(): + tester.assertEqual(self.uniformizer(), scaled.uniformizer()) + tester.assertEqual(scaled(self.uniformizer()), s * self(self.uniformizer())) + unscaled = scaled / s + tester.assertEqual(self, unscaled) + + def _test_add(self, **options): + r""" + Check that the (strict) triangle equality is satisfied for the + valuation of this ring. + + TESTS:: + + sage: v = ZZ.valuation(3) + sage: v._test_add() + + """ + tester = self._tester(**options) + S = self.domain().some_elements() + from itertools import product + for x,y in tester.some_elements(product(S,S)): + tester.assertGreaterEqual(self(x+y),min(self(x),self(y))) + if self(x) != self(y): + tester.assertEqual(self(x+y),min(self(x),self(y))) + + def _test_infinite_zero(self, **options): + r""" + Check that zero is sent to infinity. + + TESTS:: + + sage: v = QQ.valuation(5) + sage: v._test_infinite_zero() + + """ + tester = self._tester(**options) + from sage.rings.all import infinity + tester.assertEqual(self(self.domain().zero()), infinity) + + def _test_mul(self, **options): + r""" + Check that multiplication translates to addition of valuations. + + TESTS:: + + sage: v = QQ.valuation(5) + sage: v._test_mul() + + """ + tester = self._tester(**options) + S = self.domain().some_elements() + from itertools import product + for x,y in tester.some_elements(product(S,S)): + from sage.rings.all import infinity + if set([self(x), self(y)]) == set([infinity, -infinity]): + continue + tester.assertEqual(self(x*y),self(x)+self(y)) + + def _test_no_infinite_units(self, **options): + r""" + Checks that no units are sent to infinity. + + TESTS:: + + sage: v = QQ.valuation(5) + sage: v._test_no_infinite_units() + + As multiplication translates to addition, pseudo-valuations which + send a unit to infinity are necessarily trivial:: + + sage: from sage.rings.valuation.valuation_space import DiscretePseudoValuationSpace + sage: v = DiscretePseudoValuationSpace(QQ).an_element() + sage: v(1) + +Infinity + sage: v.is_trivial() + True + + """ + if not self.is_discrete_valuation() and self.is_trivial(): + return + if self.is_negative_pseudo_valuation(): + return + + from sage.rings.all import infinity + tester = self._tester(**options) + for x in tester.some_elements(self.domain().some_elements()): + if self(x) is infinity: + tester.assertFalse(x.is_unit()) + + def _test_value_group(self, **options): + r""" + Check correctness of the value group. + + TESTS:: + + sage: v = QQ.valuation(5) + sage: v._test_value_group() + + """ + from sage.rings.all import infinity + tester = self._tester(**options) + # check consistency of trivial valuations first + if self.is_trivial(): + if self(self.domain().one()) is infinity: + # a trivial pseudo-valuation that sends everything to infinity + with tester.assertRaises(ValueError): + self.value_group() + return + + # check that all valuations are in the value group + for x in tester.some_elements(self.domain().some_elements()): + if self(x) is not infinity and self(x) is not -infinity: + tester.assertIn(self(x), self.value_group()) + + if not self.is_trivial(): + # check that the uniformizer generates the value group + tester.assertEqual(self.value_group().gen(), self(self.uniformizer())) + + def _test_value_semigroup(self, **options): + r""" + Check correctness of the value semigroup. + + TESTS:: + + sage: v = QQ.valuation(5) + sage: v._test_value_semigroup() + + """ + tester = self._tester(**options) + + if self.is_trivial() and not self.is_discrete_valuation(): + # the trivial pseudo-valuation does not have a value semigroup + return + + for s in tester.some_elements(self.value_semigroup().some_elements()): + tester.assertIn(s, self.value_group()) + + def _test_element_with_valuation(self, **options): + r""" + Check correctness of :meth:`element_with_valuation`. + + TESTS:: + + sage: v = QQ.valuation(5) + sage: v._test_element_with_valuation() + + """ + tester = self._tester(**options) + + if self.is_trivial() and not self.is_discrete_valuation(): + # the trivial pseudo-valuation does not have a value semigroup + return + + for s in tester.some_elements(self.value_semigroup().some_elements()): + tester.assertEqual(self(self.element_with_valuation(s)), s) + + def _test_residue_ring(self, **options): + r""" + Check the correctness of residue rings. + + TESTS:: + + sage: v = QQ.valuation(5) + sage: v._test_residue_ring() + + """ + tester = self._tester(**options) + + try: + r = self.residue_ring() + except NotImplementedError: + # over non-fields (and especially polynomial rings over + # non-fields) computation of the residue ring is often + # difficult and not very interesting + from sage.categories.fields import Fields + if self.domain() not in Fields(): + return + raise + + if r.zero() == r.one(): + # residue ring is the zero rng + tester.assertGreater(self(1), 0) + return + + c = self.residue_ring().characteristic() + if c != 0: + tester.assertGreater(self(c), 0) + + def _test_reduce(self, **options): + r""" + Check the correctness of reductions. + + TESTS:: + + sage: v = QQ.valuation(5) + sage: v._test_reduce() + + """ + tester = self._tester(**options) + + try: + k = self.residue_ring() + except NotImplementedError: + # over non-fields (and especially polynomial rings over + # non-fields) computation of the residue ring is often + # difficult and not very interesting + from sage.categories.fields import Fields + if self.domain() not in Fields(): + return + raise + + for x in tester.some_elements(self.domain().some_elements()): + if self(x) < 0: + with tester.assertRaises((ValueError, ArithmeticError)): + self.reduce(x) + continue + if self(x) == 0: + y = self.reduce(x) + tester.assertIn(y, self.residue_ring()) + tester.assertNotEqual(y, 0) + if x.is_unit() and ~x in self.domain(): + tester.assertTrue(y.is_unit()) + tester.assertIn(~y, self.residue_ring()) + tester.assertEqual(~y, self.reduce(self.domain()(~x))) + if self(x) > 0: + tester.assertEqual(self.reduce(x), 0) + + def _test_lift(self, **options): + r""" + Check the correctness of lifts. + + TESTS:: + + sage: v = QQ.valuation(5) + sage: v._test_lift() + + """ + tester = self._tester(**options) + + try: + k = self.residue_ring() + except NotImplementedError: + # over non-fields (and especially polynomial rings over + # non-fields) computation of the residue ring is often + # difficult and not very interesting + from sage.categories.fields import Fields + if self.domain() not in Fields(): + return + raise + + for X in tester.some_elements(self.residue_ring().some_elements()): + x = self.lift(X) + y = self.reduce(x) + tester.assertEqual(X, y) + if X != 0: + tester.assertEqual(self(x), 0) + + def _test_restriction(self, **options): + r""" + Check the correctness of reductions. + + TESTS:: + + sage: v = QQ.valuation(5) + sage: v._test_restriction() + + """ + tester = self._tester(**options) + + tester.assertEqual(self.restriction(self.domain()), self) + + def _test_extension(self, **options): + r""" + Check the correctness of extensions. + + TESTS:: + + sage: v = QQ.valuation(5) + sage: v._test_extension() + + """ + tester = self._tester(**options) + + tester.assertEqual(self.extension(self.domain()), self) + tester.assertEqual(self.extensions(self.domain()), [self]) + + def _test_change_domain(self, **options): + r""" + Check the correctness of :meth:`change_domain`. + + TESTS:: + + sage: v = QQ.valuation(5) + sage: v._test_change_domain() + + """ + tester = self._tester(**options) + + tester.assertEqual(self.change_domain(self.domain()), self) + + def _test_no_infinite_nonzero(self, **options): + r""" + Check that only zero is sent to infinity. + + TESTS:: + + sage: v = QQ.valuation(5) + sage: v._test_no_infinite_nonzero() + + """ + if not self.is_discrete_valuation(): + return + + from sage.rings.all import infinity + tester = self._tester(**options) + for x in tester.some_elements(self.domain().some_elements()): + if self(x) is infinity: + tester.assertEqual(x, 0) + + def _test_residue_field(self, **options): + r""" + Check the correctness of residue fields. + + TESTS:: + + sage: v = QQ.valuation(5) + sage: v._test_residue_field() + + """ + if not self.is_discrete_valuation(): + return + + tester = self._tester(**options) + try: + k = self.residue_field() + except ValueError: + from sage.categories.fields import Fields + # a discrete valuation on a field has a residue field + tester.assertFalse(self.domain() in Fields()) + return + except NotImplementedError: + # over non-fields (and especially polynomial rings over + # non-fields) computation of the residue ring is often + # difficult and not very interesting + from sage.categories.fields import Fields + if self.domain() not in Fields(): + return + raise + + c = self.residue_field().characteristic() + if c != 0: + tester.assertGreater(self(c), 0) + + def _test_ge(self, **options): + r""" + Check the correctness of the ``>=`` operator. + + TESTS:: + + sage: v = QQ.valuation(5) + sage: v._test_ge() + + """ + tester = self._tester(**options) + + tester.assertGreaterEqual(self, self) + + if self.is_negative_pseudo_valuation(): + return + + from trivial_valuation import TrivialPseudoValuation, TrivialValuation + tester.assertGreaterEqual(self, TrivialValuation(self.domain())) + tester.assertLessEqual(self, TrivialPseudoValuation(self.domain())) + + def _test_le(self, **options): + r""" + Check the correctness of the ``<=`` operator. + + TESTS:: + + sage: v = QQ.valuation(5) + sage: v._test_le() + + """ + tester = self._tester(**options) + + tester.assertGreaterEqual(self, self) + + if self.is_negative_pseudo_valuation(): + return + + from trivial_valuation import TrivialPseudoValuation, TrivialValuation + tester.assertLessEqual(TrivialValuation(self.domain()), self) + tester.assertGreaterEqual(TrivialPseudoValuation(self.domain()), self) + + def _test_inverse(self, **options): + r""" + Check the correctness of :meth:`inverse`. + + TESTS:: + + sage: v = QQ.valuation(5) + sage: v._test_inverse() + + """ + tester = self._tester(**options) + + for x in tester.some_elements(self.domain().some_elements()): + from sage.rings.all import infinity + for prec in (0, 1, 42, infinity): + try: + y = self.inverse(x, prec) + except NotImplementedError: + continue + except ValueError: + if prec is not infinity: + tester.assertNotEqual(self(x), 0) + tester.assertFalse(x.is_unit()) + continue + + tester.assertTrue(y.parent() is self.domain()) + if self.domain().is_exact(): + tester.assertGreaterEqual(self(x*y - 1), prec) + + +from sage.categories.action import Action +class ScaleAction(Action): + r""" + Action of integers, rationals and the infinity ring on valuations by + scaling it. + + EXAMPLES:: + + sage: v = QQ.valuation(5) + sage: from operator import mul + sage: v.parent().get_action(ZZ, mul, self_on_left=False) + Left action by Integer Ring on Discrete pseudo-valuations on Rational Field + + """ + def _call_(self, s, v): + r""" + Let ``s`` act on ``v``. + + EXAMPLES:: + + sage: v = QQ.valuation(5) + sage: 3*v # indirect doctest + 3 * 5-adic valuation + + """ + if not self.is_left(): + # for a right action, the parameters are swapped + s,v = v,s + return v.scale(s) diff --git a/src/sage/rings/valuation/valuations_catalog.py b/src/sage/rings/valuation/valuations_catalog.py new file mode 100644 index 00000000000..ebd7c2f8290 --- /dev/null +++ b/src/sage/rings/valuation/valuations_catalog.py @@ -0,0 +1,7 @@ +# not using absolute imports here as importing absolute_import would make +# "absolute_import" show up in -completion +from sage.rings.padics.padic_valuation import pAdicValuation +from sage.rings.function_field.function_field_valuation import FunctionFieldValuation +from sage.rings.valuation.gauss_valuation import GaussValuation +from sage.rings.valuation.trivial_valuation import TrivialDiscretePseudoValuation, TrivialPseudoValuation, TrivialValuation +from sage.rings.valuation.limit_valuation import LimitValuation diff --git a/src/sage/rings/valuation/value_group.py b/src/sage/rings/valuation/value_group.py new file mode 100644 index 00000000000..ccfdcf74797 --- /dev/null +++ b/src/sage/rings/valuation/value_group.py @@ -0,0 +1,694 @@ +# -*- coding: utf-8 -*- +r""" +Value groups of discrete valuations + +This file defines additive sub(semi-)groups of `\QQ` and related structures. + +AUTHORS: + +- Julian Rüth (2013-09-06): initial version + +EXAMPLES:: + + sage: v = ZZ.valuation(2) + sage: v.value_group() + Additive Abelian Group generated by 1 + sage: v.value_semigroup() + Additive Abelian Semigroup generated by 1 + +""" +#***************************************************************************** +# Copyright (C) 2013-2017 Julian Rüth +# +# Distributed under the terms of the GNU General Public License (GPL) +# as published by the Free Software Foundation; either version 2 of +# the License, or (at your option) any later version. +# http://www.gnu.org/licenses/ +#***************************************************************************** +from sage.structure.parent import Parent +from sage.structure.unique_representation import UniqueRepresentation +from sage.rings.all import QQ, ZZ, infinity +from sage.misc.cachefunc import cached_method + +class DiscreteValuationCodomain(UniqueRepresentation, Parent): + r""" + The codomain of discrete valuations, the rational numbers extended by + `\pm\infty`. + + EXAMPLES:: + + sage: from sage.rings.valuation.value_group import DiscreteValuationCodomain + sage: C = DiscreteValuationCodomain(); C + Codomain of Discrete Valuations + + TESTS:: + + sage: TestSuite(C).run() # long time + + """ + def __init__(self): + r""" + TESTS:: + + sage: from sage.rings.valuation.value_group import DiscreteValuationCodomain + sage: isinstance(QQ.valuation(2).codomain(), DiscreteValuationCodomain) + True + + """ + from sage.sets.finite_enumerated_set import FiniteEnumeratedSet + from sage.categories.additive_monoids import AdditiveMonoids + UniqueRepresentation.__init__(self) + Parent.__init__(self, facade=(QQ, FiniteEnumeratedSet([infinity, -infinity])), category=AdditiveMonoids()) + + def _element_constructor_(self, x): + r""" + Create an element from ``x``. + + INPUT: + + - ``x`` -- a rational number or `\infty` + + TESTS:: + + sage: from sage.rings.valuation.value_group import DiscreteValuationCodomain + sage: DiscreteValuationCodomain()(0) + 0 + sage: DiscreteValuationCodomain()(infinity) + +Infinity + sage: DiscreteValuationCodomain()(-infinity) + -Infinity + + """ + if x is infinity: + return x + if x is -infinity: + return x + if x not in QQ: + raise ValueError("must be a rational number or infinity") + return QQ.coerce(x) + + def _repr_(self): + r""" + Return a printable representation. + + EXAMPLES:: + + sage: from sage.rings.valuation.value_group import DiscreteValuationCodomain + sage: DiscreteValuationCodomain() # indirect doctest + Codomain of Discrete Valuations + + """ + return "Codomain of Discrete Valuations" + +class DiscreteValueGroup(UniqueRepresentation, Parent): + r""" + The value group of a discrete valuation, an additive subgroup of `\QQ` + generated by ``generator``. + + INPUT: + + - ``generator`` -- a rational number + + .. NOTE:: + + We do not rely on the functionality provided by additive abelian groups + in Sage since these require the underlying set to be the integers. + Therefore, we roll our own \Z-module here. + We could have used :class:`AdditiveAbelianGroupWrapper` here, but it + seems to be somewhat outdated. In particular, generic group + functionality should now come from the category and not from the + super-class. A facade of \Q appeared to be the better approach. + + EXAMPLES:: + + sage: from sage.rings.valuation.value_group import DiscreteValueGroup + sage: D1 = DiscreteValueGroup(0); D1 + Trivial Additive Abelian Group + sage: D2 = DiscreteValueGroup(4/3); D2 + Additive Abelian Group generated by 4/3 + sage: D3 = DiscreteValueGroup(-1/3); D3 + Additive Abelian Group generated by 1/3 + + TESTS:: + + sage: TestSuite(D1).run() # long time + sage: TestSuite(D2).run() # long time + sage: TestSuite(D3).run() # long time + + """ + @staticmethod + def __classcall__(cls, generator): + r""" + Normalizes ``generator`` to a positive rational so that this is a + unique parent. + + TESTS:: + + sage: from sage.rings.valuation.value_group import DiscreteValueGroup + sage: DiscreteValueGroup(1) is DiscreteValueGroup(-1) + True + + """ + generator = QQ.coerce(generator) + generator = generator.abs() + return super(DiscreteValueGroup, cls).__classcall__(cls, generator) + + def __init__(self, generator): + r""" + TESTS:: + + sage: from sage.rings.valuation.value_group import DiscreteValueGroup + sage: isinstance(DiscreteValueGroup(0), DiscreteValueGroup) + True + + """ + from sage.categories.modules import Modules + self._generator = generator + + # We can not set the facade to DiscreteValuationCodomain since there + # are some issues with iterated facades currently + UniqueRepresentation.__init__(self) + Parent.__init__(self, facade=QQ, category=Modules(ZZ)) + + def _element_constructor_(self, x): + r""" + Create an element in this group from ``x``. + + INPUT: + + - ``x`` -- a rational number + + TESTS:: + + sage: from sage.rings.valuation.value_group import DiscreteValueGroup + sage: DiscreteValueGroup(0)(0) + 0 + sage: DiscreteValueGroup(0)(1) + Traceback (most recent call last): + ... + ValueError: `1` is not in Trivial Additive Abelian Group. + sage: DiscreteValueGroup(1)(1) + 1 + sage: DiscreteValueGroup(1)(1/2) + Traceback (most recent call last): + ... + ValueError: `1/2` is not in Additive Abelian Group generated by 1. + + """ + x = QQ.coerce(x) + if x == 0 or (self._generator != 0 and x/self._generator in ZZ): + return x + + raise ValueError("`{0}` is not in {1}.".format(x,self)) + + def _repr_(self): + r""" + Return a printable representation for this group. + + EXAMPLES:: + + sage: from sage.rings.valuation.value_group import DiscreteValueGroup + sage: DiscreteValueGroup(0) # indirect doctest + Trivial Additive Abelian Group + + """ + if self.is_trivial(): + return "Trivial Additive Abelian Group" + return "Additive Abelian Group generated by %r"%(self._generator,) + + def __add__(self, other): + r""" + Return the subgroup of `\QQ` generated by this group and ``other``. + + INPUT: + + - ``other`` -- a discrete value group or a rational number + + EXAMPLES:: + + sage: from sage.rings.valuation.value_group import DiscreteValueGroup + sage: D = DiscreteValueGroup(1/2) + sage: D + 1/3 + Additive Abelian Group generated by 1/6 + sage: D + D + Additive Abelian Group generated by 1/2 + sage: D + 1 + Additive Abelian Group generated by 1/2 + sage: DiscreteValueGroup(2/7) + DiscreteValueGroup(4/9) + Additive Abelian Group generated by 2/63 + + """ + if isinstance(other, DiscreteValueGroup): + return DiscreteValueGroup(self._generator.gcd(other._generator)) + if isinstance(other, DiscreteValueSemigroup): + return other + self + from sage.structure.element import is_Element + if is_Element(other) and QQ.has_coerce_map_from(other.parent()): + return self + DiscreteValueGroup(other) + raise ValueError("`other` must be a DiscreteValueGroup or a rational number") + + def _mul_(self, other, switch_sides=False): + r""" + Return the group generated by ``other`` times the generator of this + group. + + INPUT: + + - ``other`` -- a rational number + + EXAMPLES:: + + sage: from sage.rings.valuation.value_group import DiscreteValueGroup + sage: D = DiscreteValueGroup(1/2) + sage: 1/2 * D + Additive Abelian Group generated by 1/4 + sage: D * (1/2) + Additive Abelian Group generated by 1/4 + sage: D * 0 + Trivial Additive Abelian Group + + """ + other = QQ.coerce(other) + return DiscreteValueGroup(self._generator*other) + + def index(self, other): + r""" + Return the index of ``other`` in this group. + + INPUT: + + - ``other`` -- a subgroup of this group + + EXAMPLES:: + + sage: from sage.rings.valuation.value_group import DiscreteValueGroup + sage: DiscreteValueGroup(3/8).index(DiscreteValueGroup(3)) + 8 + sage: DiscreteValueGroup(3).index(DiscreteValueGroup(3/8)) + Traceback (most recent call last): + ... + ValueError: other must be a subgroup of this group + sage: DiscreteValueGroup(3).index(DiscreteValueGroup(0)) + Traceback (most recent call last): + ... + ValueError: other must have finite index in this group + sage: DiscreteValueGroup(0).index(DiscreteValueGroup(0)) + 1 + sage: DiscreteValueGroup(0).index(DiscreteValueGroup(3)) + Traceback (most recent call last): + ... + ValueError: other must be a subgroup of this group + + """ + if not isinstance(other, DiscreteValueGroup): + raise ValueError("other must be a DiscreteValueGroup") + if other._generator not in self: + raise ValueError("other must be a subgroup of this group") + if other._generator == 0: + if self._generator == 0: + return ZZ(1) + else: + raise ValueError("other must have finite index in this group") + return ZZ(other._generator / self._generator) + + def numerator(self): + r""" + Return the numerator of a generator of this group. + + EXAMPLES:: + + sage: from sage.rings.valuation.value_group import DiscreteValueGroup + sage: DiscreteValueGroup(3/8).numerator() + 3 + + """ + return self._generator.numerator() + + def denominator(self): + r""" + Return the denominator of a generator of this group. + + EXAMPLES:: + + sage: from sage.rings.valuation.value_group import DiscreteValueGroup + sage: DiscreteValueGroup(3/8).denominator() + 8 + + """ + return self._generator.denominator() + + def gen(self): + r""" + Return a generator of this group. + + EXAMPLES:: + + sage: from sage.rings.valuation.value_group import DiscreteValueGroup + sage: DiscreteValueGroup(-3/8).gen() + 3/8 + + """ + return self._generator + + def some_elements(self): + r""" + Return some typical elements in this group. + + EXAMPLES:: + + sage: from sage.rings.valuation.value_group import DiscreteValueGroup + sage: DiscreteValueGroup(-3/8).some_elements() + [3/8, -3/8, 0, 42, 3/2, -3/2, 9/8, -9/8] + + """ + return [self._generator, -self._generator] + [x for x in QQ.some_elements() if x in self] + + def is_trivial(self): + r""" + Return whether this is the trivial additive abelian group. + + EXAMPLES:: + + sage: from sage.rings.valuation.value_group import DiscreteValueGroup + sage: DiscreteValueGroup(-3/8).is_trivial() + False + sage: DiscreteValueGroup(0).is_trivial() + True + + """ + return self._generator.is_zero() + +class DiscreteValueSemigroup(UniqueRepresentation, Parent): + r""" + The value semigroup of a discrete valuation, an additive subsemigroup of + `\QQ` generated by ``generators``. + + INPUT: + + - ``generators`` -- rational numbers + + EXAMPLES:: + + sage: from sage.rings.valuation.value_group import DiscreteValueSemigroup + sage: D1 = DiscreteValueSemigroup(0); D1 + Trivial Additive Abelian Semigroup + sage: D2 = DiscreteValueSemigroup(4/3); D2 + Additive Abelian Semigroup generated by 4/3 + sage: D3 = DiscreteValueSemigroup([-1/3, 1/2]); D3 + Additive Abelian Semigroup generated by -1/3, 1/2 + + TESTS:: + + sage: TestSuite(D1).run() # long time + sage: TestSuite(D2).run() # long time + sage: TestSuite(D3).run() # long time + + """ + @staticmethod + def __classcall__(cls, generators): + r""" + Normalize ``generators``. + + TESTS: + + We do not find minimal generators or something like that but just sort the + generators and drop generators that are trivially contained in the + semigroup generated by the remaining generators:: + + sage: from sage.rings.valuation.value_group import DiscreteValueSemigroup + sage: DiscreteValueSemigroup([1,2]) is DiscreteValueSemigroup([1]) + True + + In this case, the normalization is not sufficient to determine that + these are the same semigroup:: + + sage: DiscreteValueSemigroup([1,-1,1/3]) is DiscreteValueSemigroup([1/3,-1/3]) + False + + """ + if generators in QQ: + generators = [generators] + generators = list(set([QQ.coerce(g) for g in generators if g != 0])) + generators.sort() + simplified_generators = generators + + # this is not very efficient but there should never be more than a + # couple of generators + for g in generators: + for h in generators: + if g == h: continue + from sage.rings.all import NN + if h/g in NN: + simplified_generators.remove(h) + break + + return super(DiscreteValueSemigroup, cls).__classcall__(cls, tuple(simplified_generators)) + + def __init__(self, generators): + r""" + TESTS:: + + sage: from sage.rings.valuation.value_group import DiscreteValueSemigroup + sage: isinstance(DiscreteValueSemigroup([0]), DiscreteValueSemigroup) + True + + """ + from sage.categories.all import AdditiveMagmas + self._generators = generators + + category = AdditiveMagmas().AdditiveAssociative().AdditiveUnital() + if all([-g in generators for g in generators]): + # check whether this is trivially a group + # is_group() performs a complete check that is very costly and + # refines the category + category = category.AdditiveInverse() + + # We can not set the facade to DiscreteValuationCodomain since there + # are some issues with iterated facades currently + Parent.__init__(self, facade=QQ, category=category) + + def _solve_linear_program(self, target): + r""" + Return the coefficients of a linear combination to write ``target`` in + terms of the generators of this semigroup. + + Return ``None`` if no such combination exists. + + EXAMPLES:: + + sage: from sage.rings.valuation.value_group import DiscreteValueSemigroup + sage: D = DiscreteValueSemigroup([2,3,5]) + sage: D._solve_linear_program(12) + {0: 1, 1: 0, 2: 2} + sage: 1*2 + 0*3 + 2*5 + 12 + + """ + if len(self._generators) == 0: + if target == 0: + return {} + else: + return None + + if len(self._generators) == 1: + from sage.rings.all import NN + exp = target / self._generators[0] + if exp not in NN: + return None + return {0 : exp} + + if len(self._generators) == 2 and self._generators[0] == - self._generators[1]: + from sage.rings.all import ZZ + exp = target / self._generators[0] + if exp not in ZZ: + return None + return {0: exp, 1: 0} + + from sage.numerical.mip import MixedIntegerLinearProgram, MIPSolverException + P = MixedIntegerLinearProgram(maximization=False, solver="ppl") + x = P.new_variable(integer=True, nonnegative=True) + constraint = sum([g*x[i] for i,g in enumerate(self._generators)]) == target + P.add_constraint(constraint) + P.set_objective(None) + try: + P.solve() + except MIPSolverException: + return None + return P.get_values(x) + + def _element_constructor_(self, x): + r""" + Create an element in this group from ``x``. + + INPUT: + + - ``x`` -- a rational number + + TESTS:: + + sage: from sage.rings.valuation.value_group import DiscreteValueSemigroup + sage: DiscreteValueSemigroup([])(0) + 0 + sage: DiscreteValueSemigroup([])(1) + Traceback (most recent call last): + ... + ValueError: `1` is not in Trivial Additive Abelian Semigroup. + sage: DiscreteValueSemigroup([1])(1) + 1 + sage: DiscreteValueSemigroup([1])(-1) + Traceback (most recent call last): + ... + ValueError: `-1` is not in Additive Abelian Semigroup generated by 1. + + """ + x = QQ.coerce(x) + if x in self._generators or self._solve_linear_program(x) is not None: + return x + + raise ValueError("`{0}` is not in {1}.".format(x,self)) + + def _repr_(self): + r""" + Return a printable representation for this semigroup. + + EXAMPLES:: + + sage: from sage.rings.valuation.value_group import DiscreteValueSemigroup + sage: DiscreteValueSemigroup(0) # indirect doctest + Trivial Additive Abelian Semigroup + + """ + if self.is_trivial(): + return "Trivial Additive Abelian Semigroup" + return "Additive Abelian Semigroup generated by %s"%(', '.join([repr(g) for g in self._generators]),) + + def __add__(self, other): + r""" + Return the subsemigroup of `\QQ` generated by this semigroup and ``other``. + + INPUT: + + - ``other`` -- a discrete value (semi-)group or a rational number + + EXAMPLES:: + + sage: from sage.rings.valuation.value_group import DiscreteValueSemigroup, DiscreteValueGroup + sage: D = DiscreteValueSemigroup(1/2) + sage: D + 1/3 + Additive Abelian Semigroup generated by 1/3, 1/2 + sage: D + D + Additive Abelian Semigroup generated by 1/2 + sage: D + 1 + Additive Abelian Semigroup generated by 1/2 + sage: DiscreteValueGroup(2/7) + DiscreteValueSemigroup(4/9) + Additive Abelian Semigroup generated by -2/7, 2/7, 4/9 + + """ + if isinstance(other, DiscreteValueSemigroup): + return DiscreteValueSemigroup(self._generators + other._generators) + if isinstance(other, DiscreteValueGroup): + return DiscreteValueSemigroup(self._generators + (other._generator, -other._generator)) + from sage.structure.element import is_Element + if is_Element(other) and QQ.has_coerce_map_from(other.parent()): + return self + DiscreteValueSemigroup(other) + raise ValueError("`other` must be a DiscreteValueGroup, a DiscreteValueSemigroup or a rational number") + + def _mul_(self, other, switch_sides=False): + r""" + Return the semigroup generated by ``other`` times the generators of this + semigroup. + + INPUT: + + - ``other`` -- a rational number + + EXAMPLES:: + + sage: from sage.rings.valuation.value_group import DiscreteValueSemigroup + sage: D = DiscreteValueSemigroup(1/2) + sage: 1/2 * D + Additive Abelian Semigroup generated by 1/4 + sage: D * (1/2) + Additive Abelian Semigroup generated by 1/4 + sage: D * 0 + Trivial Additive Abelian Semigroup + + """ + other = QQ.coerce(other) + return DiscreteValueSemigroup([g*other for g in self._generators]) + + def gens(self): + r""" + Return the generators of this semigroup. + + EXAMPLES:: + + sage: from sage.rings.valuation.value_group import DiscreteValueSemigroup + sage: DiscreteValueSemigroup(-3/8).gens() + (-3/8,) + + """ + return tuple(self._generators) + + def some_elements(self): + r""" + Return some typical elements in this semigroup. + + EXAMPLES:: + + sage: from sage.rings.valuation.value_group import DiscreteValueSemigroup + sage: list(DiscreteValueSemigroup([-3/8,1/2]).some_elements()) + [0, -3/8, 1/2, ...] + + """ + yield self(0) + if self.is_trivial(): + return + for g in self._generators: + yield g + from sage.rings.all import ZZ + for x in (ZZ**len(self._generators)).some_elements(): + yield QQ.coerce(sum([abs(c)*g for c,g in zip(x,self._generators)])) + + def is_trivial(self): + r""" + Return whether this is the trivial additive abelian semigroup. + + EXAMPLES:: + + sage: from sage.rings.valuation.value_group import DiscreteValueSemigroup + sage: DiscreteValueSemigroup(-3/8).is_trivial() + False + sage: DiscreteValueSemigroup([]).is_trivial() + True + + """ + return len(self._generators) == 0 + + @cached_method + def is_group(self): + r""" + Return whether this semigroup is a group. + + EXAMPLES:: + + sage: from sage.rings.valuation.value_group import DiscreteValueSemigroup + sage: DiscreteValueSemigroup(1).is_group() + False + sage: D = DiscreteValueSemigroup([-1, 1]) + sage: D.is_group() + True + + Invoking this method also changes the category of this semigroup if it + is a group:: + + sage: D in AdditiveMagmas().AdditiveAssociative().AdditiveUnital().AdditiveInverse() + True + + """ + for x in self._generators: + if -x not in self: + return False + else: + self._refine_category_(self.category().AdditiveInverse()) + return True diff --git a/src/sage_setup/docbuild/__init__.py b/src/sage_setup/docbuild/__init__.py index fada71126cc..b8f79c2269a 100644 --- a/src/sage_setup/docbuild/__init__.py +++ b/src/sage_setup/docbuild/__init__.py @@ -633,7 +633,7 @@ def get_all_documents(self, refdir): ['reference/algebras', 'reference/arithgroup', ..., - 'reference/tensor_free_modules'] + 'reference/valuations'] """ documents = []