Skip to content

Commit

Permalink
docs: further fixes and refinements in the docs for convergents and s…
Browse files Browse the repository at this point in the history
…equences
  • Loading branch information
sr-murthy committed Jul 12, 2024
1 parent 7548f57 commit 7ec55bb
Show file tree
Hide file tree
Showing 2 changed files with 16 additions and 12 deletions.
26 changes: 15 additions & 11 deletions docs/sources/exploring-continued-fractions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ If we use the 100th convergent (with :math:`101` elements consisting of the inte
>>> sqrt2_100.as_decimal()
Decimal('1.414213562373095048801688724')
The decimal value of ``ContinuedFraction.from_elements(1, *[2] * 100)`` in this construction is now accurate up to 27 digits in the fractional part, but the decimal representation stops there. Why 27? Because the :py:mod:`decimal` library uses a default `contextual precision <https://docs.python.org/3/library/decimal.html#decimal.DefaultContext>`_ of 28 digits, including the integer part. The :py:mod:`decimal` precision can be increased, and the accuracy of the "longer" approximation above can be compared, as follows:
The decimal value of ``ContinuedFraction.from_elements(1, *[2] * 100)`` in this construction is now accurate up to 27 digits in the fractional part, but the decimal representation stops there. This is because the :py:mod:`decimal` library uses a default `contextual precision <https://docs.python.org/3/library/decimal.html#decimal.DefaultContext>`_ of 28 digits, including the integer part. The :py:mod:`decimal` precision can be increased, and the accuracy of the "longer" approximation above can be compared, as follows:

.. code:: python
Expand Down Expand Up @@ -265,7 +265,7 @@ The :py:class:`~continuedfractions.continuedfraction.ContinuedFraction` class pr
>>> dict(ContinuedFraction(649, 200).odd_convergents)
{1: ContinuedFraction(13, 4), 3: ContinuedFraction(649, 200)}
As with :py:attr:`~continuedfractions.continuedfraction.ContinuedFraction.convergents` the results are generators of enumerated sequences of :py:class:`~continuedfractions.continuedfraction.ContinuedFraction` instances, where the enumeration is by convergent index.
As with the :py:attr:`~continuedfractions.continuedfraction.ContinuedFraction.convergents` property the result is a generator of enumerated sequence of :py:class:`~continuedfractions.continuedfraction.ContinuedFraction` instances, where the enumeration is by convergent index.

The different behaviour of even- and odd-indexed convergents can be illustrated by a :py:class:`~continuedfractions.continuedfraction.ContinuedFraction` approximation of :math:`\sqrt{2}` with one hundred 2s in the tail, using dictionaries to store the even- and odd-indexed convergents:

Expand Down Expand Up @@ -324,10 +324,6 @@ is called a **semiconvergent** of :math:`\frac{p_{k - 1}}{q_{k - 1}}` and :math:
If on the other hand :math:`\frac{p_{k - 1}}{q_{k - 1}} \geq \frac{p_k}{q_k}` the inequality above would be reversed.

.. note::

If the number represented by a continued fraction is an integer it has only one convergent - itself - and thus no semiconvergents.

Some definitions of semiconvergents are more restricted: one such definition is the same as above, except that :math:`m` is required to be an integer in the range :math:`0..a_{k + 1}`, i.e. :math:`0 \leq m \leq a_{k + 1}`, where the corner cases are :math:`m = 0` in which case the semiconvergent is equal to :math:`\frac{p_{k - 1}}{q_{k - 1}}`, and :math:`m = a_{n + 1}` (if this is defined) in which the case the semiconvergent is equal to :math:`\frac{p_{k + 1}}{q_{k + 1}}`. Another restrictive definition is also the same as the first definition above except that :math:`m` is required to be an integer in the range :math:`1..a_{k + 1} - 1`, i.e. :math:`0 < m < a_{k + 1}`. In this latter definition, the two corner cases listed above are excluded.

The first, more general definition is used here, and has been implemented in the :py:class:`~continuedfractions.continuedfraction.ContinuedFraction` class as the (cached) :py:meth:`~continuedfractions.continuedfraction.ContinuedFraction.semiconvergent` method. This takes two arguments: (1) a positive integer :math:`k` determining two consecutive convergents :math:`\frac{p_{k - 1}}{q_{k - 1}}, \frac{p_k}{q_k}` for which to take a semiconvergent, and (2) a positive integer :math:`m` for the index of the semiconvergent (see the definition of :ref:`"right-mediant" <sequences.mediants.generalised>`).
Expand Down Expand Up @@ -356,7 +352,17 @@ A few examples are given below for the continued fraction :math:`[-5; 1, 1, 6, 7
>>> cf.semiconvergent(3, 7)
ContinuedFraction(-415, 93)
The :math:`m`-th semiconvergent :math:`\frac{p_{k - 1} + mp_k}{q_{k - 1} + mq_k}` of the convergents :math:`\frac{p_{k - 1}}{q_{k - 1}}` and :math:`\frac{p_k}{q_k}` is the mediant of their :math:`(m - 1)`-st semiconvergent :math:`\frac{p_{k - 1} + (m - 1)p_k}{q_{k - 1} + (m - 1)q_k}` and the :math:`k`-th convergent :math:`\frac{p_k}{q_k}`. The semiconvergent sequence :math:`\left( \frac{p_{k - 1} + mp_k}{q_{k - 1} + mq_k} \right)` is monotonic in :math:`m`, bounded on one side by :math:`\frac{p_k}{q_k}` (the side depends on whether :math:`k` is odd or even), and has the limit :math:`\frac{p_k}{q_k}` as :math:`m \to \infty`. This can be seen in the example above.
.. note::

The continued fraction of an integer is of zero order, and thus has only one convergent - itself - and no semiconvergents. Attempting to call :py:meth:`~continuedfractions.continuedfraction.ContinuedFraction.semiconvergent` on any integer-valued :py:class:`~continuedfractions.continuedfraction.ContinuedFraction` instance, for any value of :math:`k` and :math:`m`, produces a :py:class:`ValueError`.

.. code:: python
>>> ContinuedFraction(1).semiconvergent(0, 1)
...
ValueError: `k` and `m` must be positive integers and `k` must be an integer in the range `1..n` where `n` is the order of the continued fraction

In relation to consecutive convergents :math:`\frac{p_{k - 1}}{q_{k - 1}}` and :math:`\frac{p_k}{q_k}` the :math:`m`-th semiconvergent :math:`\frac{p_{k - 1} + mp_k}{q_{k - 1} + mq_k}` (of the ) is the mediant of their :math:`(m - 1)`-st semiconvergent :math:`\frac{p_{k - 1} + (m - 1)p_k}{q_{k - 1} + (m - 1)q_k}` and the :math:`k`-th convergent :math:`\frac{p_k}{q_k}`. The semiconvergent sequence :math:`\left( \frac{p_{k - 1} + mp_k}{q_{k - 1} + mq_k} \right)` is monotonic in :math:`m`, bounded on one side by :math:`\frac{p_k}{q_k}` (the side depends on whether :math:`k` is odd or even), and has the limit :math:`\frac{p_k}{q_k}` as :math:`m \to \infty`. This can be seen in the example above.

The semiconvergents have the same alternating behaviour in :math:`k` as the convergents: the difference between the :math:`m`-th semiconvergent :math:`\frac{p_{k - 1} + mp_k}{q_{k - 1} + mq_k}` and the :math:`(m - 1)`-st semiconvergent :math:`\frac{p_{k - 1} + (m - 1)p_k}{q_{k - 1} + (m - 1)q_k}` is given by:

Expand Down Expand Up @@ -409,7 +415,7 @@ The :math:`k`-th remainder :math:`R_k` of a (simple) continued fraction :math:`[
R_k = a_k + \cfrac{1}{a_{k + 1} + \cfrac{1}{a_{k + 2} \ddots }}
where :math:`R_0 = x`.
where :math:`R_0 = x`. As with convergents, we can also use :math:`R_k` to denote the number represented by the associated continued fraction :math:`[a_k;a_{k + 1},\ldots]`, and this number is rational if and only if the continued fraction is of finite order.

If :math:`[a_0; a_1,\ldots]` is of finite order :math:`n` then :math:`R_k` is of order :math:`(n - k)`. The remainders of :py:class:`~continuedfractions.continuedfraction.ContinuedFraction` instances can be obtained via the :py:meth:`~continuedfractions.continuedfraction.ContinuedFraction.remainder` method, which takes a non-negative integer not exceeding the order of the original.

Expand All @@ -425,8 +431,6 @@ It is also possible to get all of the remainders at once using the :py:attr:`~co
>>> dict(ContinuedFraction('3.245').remainders)
{3: ContinuedFraction(4, 1), 2: ContinuedFraction(49, 4), 1: ContinuedFraction(200, 49), 0: ContinuedFraction(649, 200)}
As with convergents the result is a generator of an enumerated sequence of :py:class:`~continuedfractions.continuedfraction.ContinuedFraction` instances.

Using the simple continued fraction of :math:`\frac{649}{200}` we can verify that these remainders are mathematically correct.

.. math::
Expand All @@ -451,7 +455,7 @@ where :math:`\frac{1}{R_k}` denotes the inverted simple continued fraction :math
R_{k - 1} = \frac{s_{k - 1}}{t_{k - 1}} = \frac{a_{k - 1}s_k + t_k}{s_k}, \hskip{3em} k \geq 1
This is used to implement the core remainders library function :py:func:`~continuedfractions.lib.remainders`, which is called by the :py:class:`~continuedfractions.continuedfraction.ContinuedFraction` instance method :py:meth:`~continuedfractions.continuedfraction.ContinuedFraction.remainders`.
If the continued fraction :math:`[a_0; a_1, \ldots]` is finite of order :math:`n` we can use this formula to compute successive remainders starting from :math:`R_n = [a_n;]` and working backwards to :math:`R_0 = [a_0; a_1, \ldots, a_n]`. This has been implemented in the remainders library function :py:func:`~continuedfractions.lib.remainders`, which is then called by the :py:class:`~continuedfractions.continuedfraction.ContinuedFraction` :py:attr:`~continuedfractions.continuedfraction.ContinuedFraction.remainders` property.

Khinchin Means & Khinchin's Constant
====================================
Expand Down
2 changes: 1 addition & 1 deletion docs/sources/sequences.rst
Original file line number Diff line number Diff line change
Expand Up @@ -500,7 +500,7 @@ The result for a given :math:`n \geq 1` is a generator of coprime pairs, yielded

The implementation of :py:meth:`~continuedfractions.sequences.KSRMTree.search_root` is guaranteed to terminate for any given :math:`n`, as there is always a finite subset of nodes :math:`(a, b)` satisfying the conditions :math:`1 \leq b < a \leq n` and :math:`(a, b) = 1`, and nodes that don't satisfy these conditions are discarded (pruned).

As the KSRM trees are infinite ternary trees the worst-case time and space complexity of a standard DFS, for a given :math:`n`, on either tree, are determined by the (variable) search depth :math:`d`, and the (constant) branching factor of :math:`3`. The current implementation of :py:meth:`~continuedfractions.sequences.KSRMTree.search_root`, which uses DFS, has a worst-case time complexity which is exponential (:math:`O(3^d)`) - optimising it would require a different approach where the search is not based on branch-by-branch traversal, but on a precalculated sequence of the exact nodes which meet the search criteria for a given :math:`n`. There is a `repository issue/ticket <https://github.com/sr-murthy/continuedfractions/issues/109>`_ for this optimisation. For space complexity the combination of backtracking and pruning "failed" nodes in the search ensures that for any given :math:`n` the smallest fraction of nodes are stored in memory at any given time - see the :py:meth:`~continuedfractions.sequences.KSRMTree._backtrack` and :py:meth:`~continuedfractions.sequences.KSRMTree.search_root` methods for more details.
As the KSRM trees are infinite ternary trees the worst-case time and space complexity of a standard DFS, for a given :math:`n`, on either tree, are determined by the (variable) search depth :math:`d`, and the (constant) branching factor of :math:`3`. The current implementation of :py:meth:`~continuedfractions.sequences.KSRMTree.search_root`, which uses DFS, has a worst-case time complexity which is exponential (:math:`O(3^d)`) - optimising it would require a different approach where the search is based not on the standard DFS traversal and bounds test of nodes, but on calculating the sequence of the exact nodes which meet the search criteria for a given :math:`n`. There is a `repository issue/ticket <https://github.com/sr-murthy/continuedfractions/issues/109>`_ for this optimisation. For space complexity the combination of backtracking and pruning "failed" nodes in the search ensures that for any given :math:`n` the smallest fraction of nodes are stored in memory at any given time - see the :py:meth:`~continuedfractions.sequences.KSRMTree._backtrack` and :py:meth:`~continuedfractions.sequences.KSRMTree.search_root` methods for more details.

.. _sequences.farey-sequences:

Expand Down

0 comments on commit 7ec55bb

Please sign in to comment.