Skip to content

Commit

Permalink
gh-35345: Allow completion() to return a lazy series for infinite pre…
Browse files Browse the repository at this point in the history
…cision

    
<!-- ^^^^^
Please provide a concise, informative and self-explanatory title.
Don't put issue numbers in there, do this in the PR body below.
For example, instead of "Fixes #1234" use "Introduce new method to
calculate 1+1"
-->
### 📚 Description

We allow the `completion()` methods of polynomial rings to take infinite
precision, in which case they return the corresponding lazy object. This
also implements a `completion()` method to graded algebras and set a
default equivalent to by degree for some general compatibility. (Full
compatibility is not attempted due to general differences in the current
implementation. This can be done on a later ticket.)

### 📝 Checklist

<!-- Put an `x` in all the boxes that apply. -->
<!-- If your change requires a documentation PR, please link it
appropriately -->
<!-- If you're unsure about any of these, don't hesitate to ask. We're
here to help! -->

- [x] I have made sure that the title is self-explanatory and the
description concisely explains the PR.
- [ ] I have linked an issue or discussion.
- [x] I have created tests covering the changes.
- [x] I have updated the documentation accordingly.
    
URL: #35345
Reported by: Travis Scrimshaw
Reviewer(s): Martin Rubey
  • Loading branch information
Release Manager committed Apr 4, 2023
2 parents 47abd1b + 74f8c57 commit 3884add
Show file tree
Hide file tree
Showing 6 changed files with 183 additions and 61 deletions.
2 changes: 2 additions & 0 deletions src/sage/categories/graded_algebras_with_basis.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,8 @@ def formal_series_ring(self):
from sage.rings.lazy_series_ring import LazyCompletionGradedAlgebra
return LazyCompletionGradedAlgebra(self)

completion = formal_series_ring

class ElementMethods:
pass

Expand Down
42 changes: 27 additions & 15 deletions src/sage/groups/matrix_gps/finitely_generated.py
Original file line number Diff line number Diff line change
Expand Up @@ -831,46 +831,49 @@ def invariant_generators(self):
def molien_series(self, chi=None, return_series=True, prec=20, variable='t'):
r"""
Compute the Molien series of this finite group with respect to the
character ``chi``. It can be returned either as a rational function
in one variable or a power series in one variable. The base field
must be a finite field, the rationals, or a cyclotomic field.
character ``chi``.
It can be returned either as a rational function in one variable
or a power series in one variable. The base field must be a
finite field, the rationals, or a cyclotomic field.
Note that the base field characteristic cannot divide the group
order (i.e., the non-modular case).
ALGORITHM:
For a finite group `G` in characteristic zero we construct the Molien series as
For a finite group `G` in characteristic zero we construct
the Molien series as
.. MATH::
\frac{1}{|G|}\sum_{g \in G} \frac{\chi(g)}{\text{det}(I-tg)},
where `I` is the identity matrix and `t` an indeterminate.
For characteristic `p` not dividing the order of `G`, let `k` be the base field
and `N` the order of `G`. Define `\lambda` as a primitive `N`-th root of unity over `k`
and `\omega` as a primitive `N`-th root of unity over `\QQ`. For each `g \in G`
For characteristic `p` not dividing the order of `G`, let `k` be
the base field and `N` the order of `G`. Define `\lambda` as a
primitive `N`-th root of unity over `k` and `\omega` as a
primitive `N`-th root of unity over `\QQ`. For each `g \in G`
define `k_i(g)` to be the positive integer such that
`e_i = \lambda^{k_i(g)}` for each eigenvalue `e_i` of `g`. Then the Molien series
is computed as
`e_i = \lambda^{k_i(g)}` for each eigenvalue `e_i` of `g`.
Then the Molien series is computed as
.. MATH::
\frac{1}{|G|}\sum_{g \in G} \frac{\chi(g)}{\prod_{i=1}^n(1 - t\omega^{k_i(g)})},
\frac{1}{|G|}\sum_{g \in G} \frac{\chi(g)}{\prod_{i=1}^n
(1 - t\omega^{k_i(g)})},
where `t` is an indeterminant. [Dec1998]_
INPUT:
- ``chi`` -- (default: trivial character) a linear group character of this group
- ``return_series`` -- boolean (default: ``True``) if ``True``, then returns
the Molien series as a power series, ``False`` as a rational function
- ``prec`` -- integer (default: 20); power series default precision
- ``variable`` -- string (default: ``'t'``); Variable name for the Molien series
(possibly infinite, in which case it is computed lazily)
- ``variable`` -- string (default: ``'t'``); variable name for the Molien series
OUTPUT: single variable rational function or power series with integer coefficients
Expand Down Expand Up @@ -910,6 +913,11 @@ def molien_series(self, chi=None, return_series=True, prec=20, variable='t'):
sage: mol.parent()
Power Series Ring in t over Integer Ring
sage: mol = S3.molien_series(prec=oo); mol
1 + t + 2*t^2 + 3*t^3 + 4*t^4 + 5*t^5 + 7*t^6 + O(t^7)
sage: mol.parent()
Lazy Taylor Series Ring in t over Integer Ring
Octahedral Group::
sage: K.<v> = CyclotomicField(8)
Expand Down Expand Up @@ -1004,7 +1012,11 @@ def molien_series(self, chi=None, return_series=True, prec=20, variable='t'):
# divide by group order
mol /= N
if return_series:
PS = PowerSeriesRing(ZZ, variable, default_prec=prec)
if prec == float('inf'):
from sage.rings.lazy_series_ring import LazyPowerSeriesRing
PS = LazyPowerSeriesRing(ZZ, names=(variable,), sparse=P.is_sparse())
else:
PS = PowerSeriesRing(ZZ, variable, default_prec=prec)
return PS(mol)
return mol

Expand Down
58 changes: 57 additions & 1 deletion src/sage/rings/lazy_series_ring.py
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,28 @@ def _element_constructor_(self, x=None, valuation=None, degree=None, constant=No
...
ValueError: unable to convert ...
Converting from the corresponding rational functions::
sage: L = LazyLaurentSeriesRing(QQ, 't')
sage: tt = L.gen()
sage: R.<t> = LaurentPolynomialRing(QQ)
sage: f = (1 + t) / (1 + t + t^2); f
(t + 1)/(t^2 + t + 1)
sage: f.parent()
Fraction Field of Univariate Polynomial Ring in t over Rational Field
sage: L(f)
1 - t^2 + t^3 - t^5 + t^6 + O(t^7)
sage: L(f) == (1 + tt) / (1 + tt + tt^2)
True
sage: f = (3 + t) / (t^3 - t^5); f
(-t - 3)/(t^5 - t^3)
sage: f.parent()
Fraction Field of Univariate Polynomial Ring in t over Rational Field
sage: L(f)
3*t^-3 + t^-2 + 3*t^-1 + 1 + 3*t + t^2 + 3*t^3 + O(t^4)
sage: L(f) - (3 + tt) / (tt^3 - tt^5)
O(t^4)
TESTS:
Checking the valuation is consistent::
Expand Down Expand Up @@ -543,6 +565,15 @@ def _element_constructor_(self, x=None, valuation=None, degree=None, constant=No
return self.element_class(self, stream)
raise ValueError(f"unable to convert {x} into {self}")

# Check if we can realize the input as a rational function
try:
FF = self._laurent_poly_ring.fraction_field()
x = FF(x)
except (TypeError, ValueError, AttributeError):
pass
else:
return self(x.numerator()) / self(x.denominator())

else:
x = coefficients

Expand Down Expand Up @@ -1879,7 +1910,7 @@ def _element_constructor_(self, x=None, valuation=None, constant=None, degree=No
sage: g = L([1,3,5,7,9], 5, -1); g
z^5 + 3*z^6 + 5*z^7 + 7*z^8 + 9*z^9 - z^10 - z^11 - z^12 + O(z^13)
Finally, ``x`` can be a polynomial::
Additionally, ``x`` can be a polynomial::
sage: P.<x> = QQ[]
sage: p = x + 3*x^2 + x^5
Expand All @@ -1896,6 +1927,22 @@ def _element_constructor_(self, x=None, valuation=None, constant=None, degree=No
sage: L(p)
x + (x*y+y^2)
Finally ``x`` can be in the corresponding fraction field::
sage: R.<a,b,c> = PolynomialRing(ZZ)
sage: L = LazyPowerSeriesRing(ZZ, 'a,b,c')
sage: aa, bb, cc = L.gens()
sage: f = (1 + a + b) / (1 + a*b + c^3); f
(a + b + 1)/(c^3 + a*b + 1)
sage: f.parent()
Fraction Field of Multivariate Polynomial Ring in a, b, c over Integer Ring
sage: L(f)
1 + (a+b) + (-a*b) + (-a^2*b-a*b^2-c^3) + (a^2*b^2-a*c^3-b*c^3)
+ (a^3*b^2+a^2*b^3+2*a*b*c^3) + (-a^3*b^3+2*a^2*b*c^3+2*a*b^2*c^3+c^6)
+ O(a,b,c)^7
sage: L(f) == (1 + aa + bb) / (1 + aa*bb + cc^3)
True
TESTS::
sage: L.<x,y> = LazyPowerSeriesRing(ZZ)
Expand Down Expand Up @@ -2003,6 +2050,15 @@ def _element_constructor_(self, x=None, valuation=None, constant=None, degree=No
valuation=valuation)
return self.element_class(self, stream)

# Check if we can realize the input as a rational function
try:
FF = self._laurent_poly_ring.fraction_field()
x = FF(x)
except (TypeError, ValueError, AttributeError):
pass
else:
return self(x.numerator()) / self(x.denominator())

if callable(x) or isinstance(x, (GeneratorType, map, filter)):
if valuation is None:
valuation = 0
Expand Down
42 changes: 32 additions & 10 deletions src/sage/rings/polynomial/laurent_polynomial_ring_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,21 +185,39 @@ def construction(self):
else:
return LaurentPolynomialFunctor(vars[-1], True), LaurentPolynomialRing(self.base_ring(), vars[:-1])

def completion(self, p, prec=20, extras=None):
"""

def completion(self, p=None, prec=20, extras=None):
r"""
Return the completion of ``self``.
Currently only implemented for the ring of formal Laurent series.
The ``prec`` variable controls the precision used in the
Laurent series ring. If ``prec`` is `\infty`, then this
returns a :class:`LazyLaurentSeriesRing`.
EXAMPLES::
sage: P.<x>=LaurentPolynomialRing(QQ)
sage: P.<x> = LaurentPolynomialRing(QQ)
sage: P
Univariate Laurent Polynomial Ring in x over Rational Field
sage: PP=P.completion(x)
sage: PP = P.completion(x)
sage: PP
Laurent Series Ring in x over Rational Field
sage: f=1-1/x
sage: f = 1 - 1/x
sage: PP(f)
-x^-1 + 1
sage: 1/PP(f)
-x - x^2 - x^3 - x^4 - x^5 - x^6 - x^7 - x^8 - x^9 - x^10 - x^11 - x^12 - x^13 - x^14 - x^15 - x^16 - x^17 - x^18 - x^19 - x^20 + O(x^21)
sage: g = 1 / PP(f); g
-x - x^2 - x^3 - x^4 - x^5 - x^6 - x^7 - x^8 - x^9 - x^10 - x^11
- x^12 - x^13 - x^14 - x^15 - x^16 - x^17 - x^18 - x^19 - x^20 + O(x^21)
sage: 1 / g
-x^-1 + 1 + O(x^19)
sage: PP = P.completion(x, prec=oo); PP
Lazy Laurent Series Ring in x over Rational Field
sage: g = 1 / PP(f); g
-x - x^2 - x^3 + O(x^4)
sage: 1 / g == f
True
TESTS:
Expand All @@ -211,12 +229,16 @@ def completion(self, p, prec=20, extras=None):
sage: L.completion('x', 20).default_prec()
20
"""
if str(p) == self._names[0] and self._n == 1:
if p is None or str(p) == self._names[0] and self._n == 1:
if prec == float('inf'):
from sage.rings.lazy_series_ring import LazyLaurentSeriesRing
sparse = self.polynomial_ring().is_sparse()
return LazyLaurentSeriesRing(self.base_ring(), names=(self._names[0],), sparse=sparse)
from sage.rings.laurent_series_ring import LaurentSeriesRing
R = self.polynomial_ring().completion(self._names[0], prec)
return LaurentSeriesRing(R)
else:
raise TypeError("Cannot complete %s with respect to %s" % (self, p))

raise TypeError("cannot complete %s with respect to %s" % (self, p))

def remove_var(self, var):
"""
Expand Down
51 changes: 32 additions & 19 deletions src/sage/rings/polynomial/multi_polynomial_ring_base.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -186,44 +186,52 @@ cdef class MPolynomialRing_base(sage.rings.ring.CommutativeRing):
"""
return self.ideal(self.gens(), check=False)

def completion(self, names, prec=20, extras={}):
"""
Return the completion of self with respect to the ideal
def completion(self, names=None, prec=20, extras={}, **kwds):
r"""
Return the completion of ``self`` with respect to the ideal
generated by the variable(s) ``names``.
INPUT:
- ``names`` -- variable or list/tuple of variables (given either
as elements of the polynomial ring or as strings)
- ``prec`` -- default precision of resulting power series ring
- ``extras`` -- passed as keywords to ``PowerSeriesRing``
- ``names`` -- (optional) variable or list/tuple of variables
(given either as elements of the polynomial ring or as strings);
the default is all variables of ``self``
- ``prec`` -- default precision of resulting power series ring,
possibly infinite
- ``extras`` -- passed as keywords to :class:`PowerSeriesRing`
or :class:`LazyPowerSeriesRing`; can also be keyword arguments
EXAMPLES::
sage: P.<x,y,z,w> = PolynomialRing(ZZ)
sage: P.completion('w')
Power Series Ring in w over Multivariate Polynomial Ring in
x, y, z over Integer Ring
x, y, z over Integer Ring
sage: P.completion((w,x,y))
Multivariate Power Series Ring in w, x, y over Univariate
Polynomial Ring in z over Integer Ring
Multivariate Power Series Ring in w, x, y over
Univariate Polynomial Ring in z over Integer Ring
sage: Q.<w,x,y,z> = P.completion(); Q
Multivariate Power Series Ring in w, x, y, z over Integer Ring
sage: H = PolynomialRing(PolynomialRing(ZZ,3,'z'),4,'f'); H
Multivariate Polynomial Ring in f0, f1, f2, f3 over
Multivariate Polynomial Ring in z0, z1, z2 over Integer Ring
Multivariate Polynomial Ring in z0, z1, z2 over Integer Ring
sage: H.completion(H.gens())
Multivariate Power Series Ring in f0, f1, f2, f3 over
Multivariate Polynomial Ring in z0, z1, z2 over Integer Ring
Multivariate Polynomial Ring in z0, z1, z2 over Integer Ring
sage: H.completion(H.gens()[2])
Power Series Ring in f2 over
Multivariate Polynomial Ring in f0, f1, f3 over
Multivariate Polynomial Ring in z0, z1, z2 over Integer Ring
Multivariate Polynomial Ring in f0, f1, f3 over
Multivariate Polynomial Ring in z0, z1, z2 over Integer Ring
sage: P.<x,y,z,w> = PolynomialRing(ZZ)
sage: P.completion(prec=oo)
Multivariate Lazy Taylor Series Ring in x, y, z, w over Integer Ring
sage: P.completion((w,x,y), prec=oo)
Multivariate Lazy Taylor Series Ring in w, x, y over
Univariate Polynomial Ring in z over Integer Ring
TESTS::
Expand All @@ -246,7 +254,9 @@ cdef class MPolynomialRing_base(sage.rings.ring.CommutativeRing):
ValueError: q is not a variable of Multivariate Polynomial Ring
in x, y over Integer Ring
"""
if not isinstance(names, (list, tuple)):
if names is None:
names = self.variable_names()
elif not isinstance(names, (list, tuple)):
names = [names] # Single variable
elif not names:
return self # 0 variables => completion is self
Expand All @@ -265,9 +275,12 @@ cdef class MPolynomialRing_base(sage.rings.ring.CommutativeRing):
raise ValueError(f"{v} is not a variable of {self}")
vars.append(v)

from sage.rings.power_series_ring import PowerSeriesRing
new_base = self.remove_var(*vars)
return PowerSeriesRing(new_base, names=vars, default_prec=prec, **extras)
if prec == float('inf'):
from sage.rings.lazy_series_ring import LazyPowerSeriesRing
return LazyPowerSeriesRing(new_base, names=vars, **extras, **kwds)
from sage.rings.power_series_ring import PowerSeriesRing
return PowerSeriesRing(new_base, names=vars, default_prec=prec, **extras, **kwds)

def remove_var(self, *var, order=None):
"""
Expand Down
Loading

0 comments on commit 3884add

Please sign in to comment.