Skip to content
This repository has been archived by the owner on Jan 30, 2023. It is now read-only.

Commit

Permalink
Implement Gosper algorithm for homographic action on continued fractions
Browse files Browse the repository at this point in the history
  • Loading branch information
SAGitbot authored and fchapoton committed May 12, 2019
1 parent baff1c4 commit 3308c92
Show file tree
Hide file tree
Showing 2 changed files with 321 additions and 14 deletions.
122 changes: 108 additions & 14 deletions src/sage/rings/continued_fraction.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,13 +174,6 @@
.. TODO::
- Gosper's algorithm to compute the continued fraction of (ax + b)/(cx + d)
knowing the one of x (see Gosper (1972,
http://www.inwap.com/pdp10/hbaker/hakmem/cf.html), Knuth (1998, TAOCP vol
2, Exercise 4.5.3.15), Fowler (1999). See also Liardet, P. and Stambul, P.
"Algebraic Computation with Continued Fractions." J. Number Th. 73,
92-121, 1998.
- Improve numerical approximation (the method
:meth:`~ContinuedFraction_base._mpfr_` is quite slow compared to the
same method for an element of a number field)
Expand All @@ -206,6 +199,7 @@
from sage.structure.richcmp import richcmp_method, rich_to_bool
from .integer import Integer
from .infinity import Infinity
from .continued_fraction_gosper import gosper_iterator

ZZ_0 = Integer(0)
ZZ_1 = Integer(1)
Expand Down Expand Up @@ -1136,6 +1130,68 @@ def numerical_approx(self, prec=None, digits=None, algorithm=None):

n = numerical_approx

def apply_homography(self, a, b, c, d):
"""
Return a new continued fraction (ax + b)/(cx + d).
This is computed using Gosper's algorithm.
- Gosper's algorithm to compute the continued fraction of (ax
+ b)/(cx + d) knowing the one of x (
INPUT:
- ``a, b, c, d`` -- integer coefficients
EXAMPLES::
sage: a = Integer(randint(-10,10)); b = Integer(randint(-10,10));
sage: c = Integer(randint(-10,10)); d = Integer(randint(-10,10));
sage: vals = [pi, sqrt(2), 541/227];
sage: for val in vals:
....: x = continued_fraction(val)
....: y = continued_fraction((a*val+b)/(c*val+d))
....: z = x.apply_homography(a,b,c,d)
....: y == z
....:
True
True
True
sage: x = continued_fraction(([1,2,3],[4,5]))
sage: val = x.value()
sage: y = continued_fraction((a*val+b)/(c*val+d))
sage: z = x.apply_homography(a,b,c,d)
sage: y == z
True
REFERENCES:
- Gosper (1972, http://www.inwap.com/pdp10/hbaker/hakmem/cf.html),
- Knuth (1998, TAOCP vol 2, Exercise 4.5.3.15),
- Fowler (1999),
- Liardet, P. and Stambul, P. "Algebraic Computation
with Continued Fractions." J. Number Th. 73, 92-121, 1998.
"""
from .rational_field import QQ
from sage.rings.number_field.number_field_element_quadratic import NumberFieldElement_quadratic

if not all(isinstance(x, Integer) for x in (a, b, c, d)):
raise AttributeError("coefficients a, b, c, d must be integers")

x = self.value()
z = (a * x + b) / (c * x + d)
_i = iter(gosper_iterator(a, b, c, d, self))

if z in QQ or isinstance(z, NumberFieldElement_quadratic):
l = list(_i)
preperiod_length = _i.output_preperiod_length
preperiod = l[:preperiod_length]
period = l[preperiod_length:]
return continued_fraction((preperiod, period), z)
else:
from sage.misc.lazy_list import lazy_list
return continued_fraction(lazy_list(_i), z)


class ContinuedFraction_periodic(ContinuedFraction_base):
r"""
Expand Down Expand Up @@ -1266,6 +1322,36 @@ def length(self):
return Infinity
return Integer(len(self._x1))

def preperiod_length(self):
r"""
Returns the number of partial quotients of the preperiodic part of ``self``.
EXAMPLES::
sage: continued_fraction(2/5).preperiod_length()
3
sage: cf = continued_fraction([(0,1),(2,)]); cf
[0; 1, (2)*]
sage: cf.preperiod_length()
2
"""
return Integer(len(self._x1))

def period_length(self):
r"""
Return the number of partial quotients of the preperiodic part of ``self``.
EXAMPLES::
sage: continued_fraction(2/5).period_length()
1
sage: cf = continued_fraction([(0,1),(2,)]); cf
[0; 1, (2)*]
sage: cf.period_length()
1
"""
return Integer(len(self._x2))

def __richcmp__(self, other, op):
r"""
Rich comparison.
Expand Down Expand Up @@ -1683,12 +1769,6 @@ def __richcmp__(self, other, op):
False
"""
try:
# The following is crazy and prevent us from using cmp(self.value(),
# other.value()). On sage-5.10.beta2:
# sage: cmp(pi, 4)
# -1
# sage: cmp(pi, pi+4)
# 1
if self.value() == other.value():
return rich_to_bool(op, 0)
if self.value() - other.value() > 0:
Expand Down Expand Up @@ -1942,7 +2022,7 @@ def _repr_(self):
sage: # TODO
"""
return "[" + str(self._w[0]) + "; " + ", ".join(map(str,self._w[1:20])) + "...]"
return "[" + str(self._w[0]) + "; " + ", ".join(map(str,self._w[1:20])) + ", ...]"

def length(self):
r"""
Expand Down Expand Up @@ -2036,8 +2116,22 @@ def value(self):
return self._value
else:
from sage.rings.real_lazy import RLF
if self._w[0] < 0:
return -RLF(-self)
return RLF(self)

def __neg__(self):
"""
Return the opposite of ``self``.
"""
from sage.combinat.words.word import Word
_w = self._w
if _w[1] == 1:
_w = Word((-_w[0]-1, _w[2]+1)).concatenate(_w[3:])
else:
_w = Word((-_w[0]-1, ZZ_1, _w[1]-1)).concatenate(_w[2:])
return self.__class__(_w)


def check_and_reduce_pair(x1, x2=None):
r"""
Expand Down
213 changes: 213 additions & 0 deletions src/sage/rings/continued_fraction_gosper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
"""
Gosper iterator
A class which serves as a stateful iterable for computing the terms of the continued fraction of `(a*x+b)/(c*x+d)`,
where `a, b, c, d` are integers, and `x` is a continued fraction.
EXAMPLES::
sage: from sage.rings.continued_fraction_gosper import gosper_iterator
sage: x = continued_fraction(pi)
sage: it = iter(gosper_iterator(3,2,3,1,x))
sage: Word(it, length='infinite')
word: 1,10,2,2,1,4,1,1,1,97,4,1,2,1,2,45,6,4,9,1,27,2,6,1,4,2,3,1,3,1,15,2,1,1,2,1,1,2,32,1,...
"""
from sage.rings.infinity import Infinity
from sage.rings.integer import Integer
from sage.rings.real_mpfr import RR


class gosper_iterator(object):

def __init__(self, a, b, c, d, x):
"""
Construct the class.
INPUT:
- ``a, b, c, d`` -- Integer coefficients of the transformation.
- ``x`` -- An instance of a continued fraction.
OUTPUT:
- The instance of gosper_iterator class.
TESTS::
sage: a = Integer(randint(-10,10)); b = Integer(randint(-10,10));
sage: c = Integer(randint(-10,10)); d = Integer(randint(-10,10));
sage: from sage.rings.continued_fraction_gosper import gosper_iterator
sage: x = continued_fraction(([1,2],[3,4])); i = iter(gosper_iterator(a,b,c,d,x))
sage: l = list(i)
sage: preperiod_length = i.output_preperiod_length
sage: preperiod = l[:preperiod_length]
sage: period = l[preperiod_length:]
sage: continued_fraction((preperiod, period), x.value()) == continued_fraction((a*x.value()+b)/(c*x.value()+d))
True
"""
from sage.rings.continued_fraction import ContinuedFraction_periodic
self.a = a
self.b = b
self.c = c
self.d = d

self.x = iter(x)

self.states = set()
self.states_to_currently_emitted = dict()

self.currently_emitted = 0
self.currently_read = 0

# Rational or quadratic case
if isinstance(x, ContinuedFraction_periodic):
self.input_preperiod_length = x.preperiod_length()
self.input_period_length = x.period_length()
# Infinite case
else:
self.input_preperiod_length = +Infinity
self.input_period_length = 0

self.output_preperiod_length = 0

def __iter__(self):
"""
Return the iterable instance of the class.
Is called upon `iter(gosper_iterator(a,b,c,d,x))`.
TESTS::
sage: a = Integer(randint(-100,100)); b = Integer(randint(-100,100));
sage: c = Integer(randint(-100,100)); d = Integer(randint(-100,100));
sage: from sage.rings.continued_fraction_gosper import gosper_iterator
sage: ig = iter(gosper_iterator(a,b,c,d,continued_fraction(pi))); icf = iter(continued_fraction((a*pi+b)/(c*pi+d)));
sage: lg = [next(ig) for _ in range(10)]; lcf = [next(icf) for _ in range(10)];
sage: lg == lcf
True
"""
return self

def __next__(self):
"""
Return the next term of the transformation.
TESTS::
sage: a = Integer(randint(-100,100)); b = Integer(randint(-100,100));
sage: c = Integer(randint(-100,100)); d = Integer(randint(-100,100));
sage: from sage.rings.continued_fraction_gosper import gosper_iterator
sage: ig = iter(gosper_iterator(a,b,c,d,continued_fraction(pi))); icf = iter(continued_fraction((a*pi+b)/(c*pi+d)));
sage: for i in range(10):
....: assert next(ig) == next(icf)
"""
limit = 100
while True:
if self.currently_read >= self.input_preperiod_length:
current_state = (
('a', self.a),
('b', self.b),
('c', self.c),
('d', self.d),
('index', (self.currently_read - self.input_preperiod_length) % self.input_period_length)
)
# for state in self.states:
# if self.compare_dicts(state, current_state, ['currently_emitted']):
# self.output_preperiod_length = state['currently_emitted']
# raise StopIteration
if current_state in self.states:
self.output_preperiod_length = self.states_to_currently_emitted[current_state]
raise StopIteration
self.states.add(current_state)
self.states_to_currently_emitted[current_state] = self.currently_emitted
if len(self.states) > 100:
print("ERROR: Stopping iteration, danger of memory overflow.")
raise StopIteration

if (self.c == 0 and self.d == 0):
raise StopIteration

ub = self.bound(self.a, self.c)
lb = self.bound(self.a + self.b, self.c + self.d)
s = -self.bound(self.c, self.d)

if ub == lb and s < 1:
self.emit(ub)
return Integer(ub)
else:
self.ingest()

limit -= 1
if limit < 1:
print("ERROR: Next loop iteration ran too many times.")
raise StopIteration

def emit(self, q):
"""
Change the state of the iterator, emitting the term `q`.
TESTS::
sage: a = Integer(randint(-100,100)); b = Integer(randint(-100,100));
sage: c = Integer(randint(-100,100)); d = Integer(randint(-100,100));
sage: from sage.rings.continued_fraction_gosper import gosper_iterator
sage: gi = gosper_iterator(a,b,c,d,continued_fraction(pi))
sage: for i in range(10):
....: gi.emit(i)
sage: gi.currently_emitted
10
"""
self.currently_emitted += 1
# This is being computed for the case when no states are being saved (still reading preperiod).
if self.currently_read <= self.input_preperiod_length:
self.output_preperiod_length = self.currently_emitted
a = self.a
b = self.b
self.a = self.c
self.b = self.d
self.c = a - q * self.c
self.d = b - q * self.d

def ingest(self):
"""
Change the state of the iterator, ingesting another term from the input continued fraction.
TESTS::
sage: a = Integer(randint(-100,100)); b = Integer(randint(-100,100));
sage: c = Integer(randint(-100,100)); d = Integer(randint(-100,100));
sage: from sage.rings.continued_fraction_gosper import gosper_iterator
sage: gi = gosper_iterator(a,b,c,d,continued_fraction(pi))
sage: for i in range(10):
....: gi.ingest()
sage: gi.currently_read
10
"""
try:
p = next(self.x)
self.currently_read += 1
a = self.a
c = self.c
self.a = a * p + self.b
self.b = a
self.c = c * p + self.d
self.d = c
except StopIteration:
self.b = self.a
self.d = self.c

@staticmethod
def bound(n, d):
"""
Helper function for division. Return infinity if denominator is zero.
TESTS::
sage: from sage.rings.continued_fraction_gosper import gosper_iterator
sage: gosper_iterator.bound(1,0)
+Infinity
"""
if d == 0:
return +Infinity
else:
return (n / d).floor()

0 comments on commit 3308c92

Please sign in to comment.