Skip to content

Commit 04afcf7

Browse files
author
Release Manager
committed
gh-39485: Implement conversion from laurent series to rational function field As in the title. I think this behavior makes sense because `QQ(RR(…))` currently uses `.simplest_rational()` instead of like exact value (like `x / 2^53` for some integer `x`), so it makes sense for similar conversions to compute a good approximation too. I cannot prove that this round-trips, however. Note that currently conversion from power series `QQ[[x]]` to polynomial ring `QQ[x]` truncates, because of implementation detail this leads to conversion from `QQ[[x]]` to `Frac(QQ[x])` also truncates. I think this is undesirable behavior because identical-looking elements in `QQ[[x]]` versus `Frac(QQ[[x]]) = LaurentSeriesRing(QQ, "x")` has different conversion behavior. Elements which are already Laurent polynomial are preserved. Side note: `.one()` is faster because it's cached ```sage sage: R.<x> = QQ[] sage: S = Frac(R) sage: %timeit S.one() # fast because it's cached 90.6 ns ± 1.27 ns per loop (mean ± std. dev. of 7 runs, 10,000,000 loops each) sage: ring_one = S.ring().one() sage: %timeit S._element_class(S, ring_one, ring_one, coerce=False, reduce=False) 3.42 μs ± 75 ns per loop (mean ± std. dev. of 7 runs, 100,000 loops each) ``` Maybe this doesn't need to be explicitly written in the documentation (users trying to do the conversion will just see the result) There exists also <code>:meth:\`.PowerSeries_poly.pade\`</code> which should probably be mentioned in the documentation. Reverse direction (that one is a morphism): #39365 Possible caveat: ``` sage: S(Frac(V)(1/(x+1))/(x^10)) 1/(x^11 + x^10) sage: S(Frac(V)(1/(x+1))/(x^30)) (-x^19 + x^18 - x^17 + x^16 - x^15 + x^14 - x^13 + x^12 - x^11 + x^10 - x^9 + x^8 - x^7 + x^6 - x^5 + x^4 - x^3 + x^2 - x + 1)/x^30 ``` It could be argued the latter would be simpler as `1/(x^31+x^30)`, but then that way gives higher denominator degree. ### 📝 Checklist <!-- Put an `x` in all the boxes that apply. --> - [x] The title is concise and informative. - [x] The description explains in detail what this PR is about. - [x] I have linked a relevant issue or discussion. - [x] I have created tests covering the changes. - [ ] I have updated the documentation and checked the documentation preview. ### ⌛ Dependencies <!-- List all open PRs that this PR logically depends on. For example, --> <!-- - #12345: short description why this is a dependency --> <!-- - #34567: ... --> URL: #39485 Reported by: user202729 Reviewer(s): Travis Scrimshaw, user202729
2 parents ae15866 + 7471239 commit 04afcf7

File tree

1 file changed

+111
-2
lines changed

1 file changed

+111
-2
lines changed

src/sage/rings/fraction_field.py

Lines changed: 111 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -557,6 +557,40 @@ def is_exact(self):
557557
"""
558558
return self.ring().is_exact()
559559

560+
def _convert_from_finite_precision_laurent_series(self, x):
561+
"""
562+
Construct an element of this fraction field approximating a Laurent series.
563+
564+
INPUT:
565+
566+
- ``x`` -- a Laurent series, must have finite precision
567+
568+
OUTPUT: Element of ``self``
569+
570+
This internal method should not be used directly, use :meth:`__call__` instead,
571+
which will delegates to :meth:`_element_constructor_`. There are some tests there.
572+
573+
.. NOTE::
574+
575+
Uses the algorithm described in `<https://mathoverflow.net/a/14874>`_.
576+
This may be changed to use Berlekamp--Massey algorithm or something else
577+
to compute Padé approximant in the future.
578+
579+
TESTS::
580+
581+
sage: F = QQ['x'].fraction_field()
582+
sage: R = LaurentSeriesRing(QQ, 'x')
583+
sage: f = ~R(x^2 + x + 3); f
584+
1/3 - 1/9*x - 2/27*x^2 + 5/81*x^3 + ... + O(x^20)
585+
sage: F._convert_from_finite_precision_laurent_series(f)
586+
1/(x^2 + x + 3)
587+
"""
588+
integral_part = self(x.truncate(1))
589+
fractional_part = x.truncate_neg(1)
590+
if fractional_part.is_zero():
591+
return integral_part
592+
return integral_part + ~self._convert_from_finite_precision_laurent_series(~fractional_part)
593+
560594
def _element_constructor_(self, x, y=None, coerce=True):
561595
"""
562596
Construct an element of this fraction field.
@@ -653,19 +687,94 @@ def _element_constructor_(self, x, y=None, coerce=True):
653687
sage: x = FF(elt)
654688
sage: F(x)
655689
-1/2/(a^2 + a)
690+
691+
Conversion from power series to rational function field gives an approximation::
692+
693+
sage: F.<x> = Frac(QQ['x'])
694+
sage: R.<x> = QQ[[]]
695+
sage: f = 1/(x+1)
696+
sage: f.parent()
697+
Power Series Ring in x over Rational Field
698+
sage: F(f)
699+
doctest:warning...
700+
DeprecationWarning: Previously conversion from power series to rational function field truncates
701+
instead of gives an approximation. Use .truncate() to recover the old behavior
702+
See https://github.com/sagemath/sage/issues/39485 for details.
703+
1/(x + 1)
704+
705+
Previously, the power series was truncated. To recover the old behavior, use
706+
:meth:`~sage.rings.power_series_ring_element.PowerSeries.truncate`::
707+
708+
sage: F(f.truncate())
709+
-x^19 + x^18 - x^17 + x^16 - x^15 + x^14 - x^13 + x^12 - x^11 + x^10 - x^9 + x^8 - x^7 + x^6 - x^5 + x^4 - x^3 + x^2 - x + 1
710+
711+
Conversion from Laurent series to rational function field gives an approximation::
712+
713+
sage: F.<x> = Frac(QQ['x'])
714+
sage: R.<x> = QQ[[]]
715+
sage: f = Frac(R)(1/(x+1))
716+
sage: f.parent()
717+
Laurent Series Ring in x over Rational Field
718+
sage: F(f)
719+
1/(x + 1)
720+
sage: f = f.truncate(20); f # infinite precision
721+
1 - 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
722+
sage: f.parent()
723+
Laurent Series Ring in x over Rational Field
724+
sage: F(f)
725+
-x^19 + x^18 - x^17 + x^16 - x^15 + x^14 - x^13 + x^12 - x^11 + x^10 - x^9 + x^8 - x^7 + x^6 - x^5 + x^4 - x^3 + x^2 - x + 1
726+
sage: f = 1/(x*(x+1))
727+
sage: f.parent()
728+
Laurent Series Ring in x over Rational Field
729+
sage: F(f)
730+
1/(x^2 + x)
731+
732+
::
733+
734+
sage: K.<x> = FunctionField(QQ)
735+
sage: R.<x> = QQ[[]]
736+
sage: f = 1/(x+1); f.parent()
737+
Power Series Ring in x over Rational Field
738+
sage: K(f)
739+
doctest:warning...
740+
DeprecationWarning: Previously conversion from power series to rational function field truncates
741+
instead of gives an approximation. Use .truncate() to recover the old behavior
742+
See https://github.com/sagemath/sage/issues/39485 for details.
743+
1/(x + 1)
744+
sage: f = Frac(R)(1/(x+1))
745+
sage: K(f)
746+
1/(x + 1)
747+
sage: f = 1/(x*(x+1))
748+
sage: K(f)
749+
1/(x^2 + x)
656750
"""
657751
if isinstance(x, (list, tuple)) and len(x) == 1:
658752
x = x[0]
659753
if y is None:
660754
if parent(x) is self:
661755
return x
756+
from sage.rings.polynomial.polynomial_ring import PolynomialRing_generic
757+
if isinstance(self.ring(), PolynomialRing_generic):
758+
from sage.rings.power_series_ring_element import PowerSeries
759+
from sage.rings.laurent_series_ring_element import LaurentSeries
760+
if isinstance(x, PowerSeries):
761+
from sage.misc.superseded import deprecation
762+
deprecation(
763+
39485,
764+
"Previously conversion from power series to rational function field truncates "
765+
"instead of gives an approximation. Use .truncate() to recover the old behavior")
766+
x = x.laurent_series()
767+
if isinstance(x, LaurentSeries):
768+
from sage.rings.infinity import infinity
769+
if x.prec() == infinity:
770+
return self(x.laurent_polynomial())
771+
return self._convert_from_finite_precision_laurent_series(x)
662772
ring_one = self.ring().one()
663773
try:
664774
return self._element_class(self, x, ring_one, coerce=coerce)
665775
except (TypeError, ValueError):
666776
pass
667-
y = self._element_class(self, ring_one, ring_one,
668-
coerce=False, reduce=False)
777+
y = self.one()
669778
else:
670779
if parent(x) is self:
671780
y = self(y)

0 commit comments

Comments
 (0)