Skip to content

Commit

Permalink
Release v0.15.0 (#102)
Browse files Browse the repository at this point in the history
* chore: bump version refs to `0.15.0`

* feat: add `ContinuedFraction` instance method to compute semiconvergents + add tests and update Sphinx docs

* docs: various fixes and tidying up in the Sphinx docs and README
  • Loading branch information
sr-murthy committed Jun 14, 2024
1 parent 298278f commit 8458b87
Show file tree
Hide file tree
Showing 9 changed files with 217 additions and 30 deletions.
4 changes: 2 additions & 2 deletions CITATION.cff
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,6 @@ keywords:
- real numbers

license: MPL-2.0
version: 0.14.2
date-released: 2024-06-11
version: 0.15.0
date-released: 2024-06-13

10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,18 +28,18 @@ See the [project docs](https://continuedfractions.readthedocs.io/en/latest/) for

[Continued fractions](https://en.wikipedia.org/wiki/Continued_fraction) are beautiful and interesting mathematical objects, with many connections in [number theory](https://en.wikipedia.org/wiki/Number_theory) and also very useful practical applications, including the [rational approximation of real numbers](https://en.wikipedia.org/wiki/Continued_fraction#Best_rational_approximations).

The `continuedfractions` package is designed for:
The `continuedfractions` package is designed for users interested in:

* working with (finite) continued fractions as Python objects, in an object-oriented way
* exploring their key properties, such as elements/coefficients, convergents, segments, remainders, and others
* working with (finite) continued fractions as Python objects, in an intuitive object-oriented way
* exploring their key properties, such as elements/coefficients, convergents, semiconvergents, remainders, and others
* operating on them as rationals and instances of the standard library [`fractions.Fraction`](https://docs.python.org/3/library/fractions.html#fractions.Fraction) class
* supporting approximations of and experimental computations for irrational numbers
* making approximations of and experimental computations for irrational numbers
* exploring other related objects, such as mediants, and special sequences of rational numbers such as Farey sequences

Currently, it does **not** support the following features:

* infinite and generalised continued fractions
* symbolic computations
* symbolic representations of or operations with continued fractions

These are [planned](https://github.com/sr-murthy/continuedfractions/issues) for future releases.

Expand Down
16 changes: 7 additions & 9 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,20 @@ continuedfractions

A simple extension of the Python :py:mod:`fractions` standard library for working with `continued fractions <https://en.wikipedia.org/wiki/Continued_fraction>`_ as Python objects.

The package is designed for:
The package is designed for users interested in:

- working with continued fractions as Python objects, in an object-oriented way
- exploring their key continued fractions properties, such as elements/coefficients,
convergents, remainders, and other numerical properties etc.
- operating on them fully as rational numbers and instances of the standard library
:py:class:`fractions.Fraction` class
- supporting approximations of and experimental computations for irrational numbers
- exploring other related objects, such as mediants, and special sequences of rational numbers such as Farey sequences
- working with (finite) continued fractions as Python objects, in an intuitive object-oriented way
- exploring their key properties, such as elements/coefficients, convergents, semiconvergents, remainders, and others
- operating on them as rationals and instances of the standard library :py:class:`fractions.Fraction` class
- making approximations of and experimental computations for irrational numbers
- exploring other related objects, such as mediants, and special sequences of rational numbers such as Farey sequences

.. note::

Currently, it does **not** support the following features:

* infinite and generalised continued fractions
* symbolic computations
* symbolic representations of or operations with continued fractions

These are `planned <https://github.com/sr-murthy/continuedfractions/issues>`_ for future releases.

Expand Down
2 changes: 1 addition & 1 deletion docs/sources/contributing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ The CI/CD pipelines are defined in the `CI YML <https://github.com/sr-murthy/con
Versioning and Releases :fas:`upload`
=====================================

The `PyPI package <https://pypi.org/project/continuedfractions/>`_ is currently at version ``0.14.2`` - the goal is to use `semantic versioning <https://semver.org/>`_ consistently for all future releases, but some earlier releases do not comply with strict semantic versioning.
The `PyPI package <https://pypi.org/project/continuedfractions/>`_ is currently at version ``0.15.0`` - the goal is to use `semantic versioning <https://semver.org/>`_ consistently for all future releases, but some earlier releases do not comply with strict semantic versioning.

There is currently no dedicated pipeline for releases - both `GitHub releases <https://github.com/sr-murthy/continuedfractions/releases>`_ and `PyPI packages <https://pypi.org/project/continuedfractions>`_ are published manually, but both have the same version tag.

Expand Down
91 changes: 82 additions & 9 deletions docs/sources/exploring-continued-fractions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ The **elements** (or coefficients) of a (possibly infinite), simple continued fr
>>> cf.elements
(3, 4, 12, 4)
The **order** of a continued fraction is defined to be number of its elements **after** the first. Thus, for ``ContinuedFraction(649, 200)`` the order is ``3``:
The **order** of a continued fraction is defined to be number of its tail elements, i.e. the elements defining the fractional part of the number represented by the continued fraction. Thus, for ``ContinuedFraction(649, 200)`` the order is ``3``:

.. code:: python
Expand Down Expand Up @@ -125,7 +125,7 @@ Unlike the :py:meth:`~continuedfractions.continuedfraction.ContinuedFraction.con
Even- and Odd-order Convergents
-------------------------------

It is known that even- and odd-order convergents behave differently: the even-order convergents :math:`C_0,C_2,C_4,\ldots` strictly increase, while the odd-order convergents :math:`C_1,C_3,C_5,\ldots` strictly decrease, both at a decreasing rate. This is captured by the formula:
It is known that even- and odd-order convergents behave differently: the even-order convergents :math:`C_0,C_2,C_4,\ldots` strictly increase, while the odd-order convergents :math:`C_1,C_3,C_5,\ldots` strictly decrease, both at a decreasing rate. This is captured by the formula for the difference between consecutive convergents:

.. math::
Expand All @@ -142,14 +142,14 @@ The :py:class:`~continuedfractions.continuedfraction.ContinuedFraction` class pr
As with :py:attr:`~continuedfractions.continuedfraction.ContinuedFraction.convergents` the results are :py:class:`types.MappingProxyType` objects, and are keyed by convergent order.

The different behaviour of even- and odd-order convergents can be illustrated by looking at them for a ``ContinuedFraction`` approximation of :math:`\sqrt{2}` with :math:`100` 2s in the tail:
The different behaviour of even- and odd-order convergents can be illustrated by looking at them for a ``ContinuedFraction`` approximation of :math:`\sqrt{2}` with one hundred 2s in the tail:

.. code:: python
# Increase the current context precision to 100 digits
>>> decimal.getcontext().prec = 100
#
# Construct an approximation for the square root of 2, with 100 2s in the tail
# Construct an approximation for the square root of 2, with one hundred 2s in the tail
>>> cf = ContinuedFraction.from_elements(1, *([2] * 100))
>>> cf
>>> ContinuedFraction(228725309250740208744750893347264645481, 161733217200188571081311986634082331709)
Expand Down Expand Up @@ -178,6 +178,81 @@ The different behaviour of even- and odd-order convergents can be illustrated by
>>> cf.odd_order_convergents[9] - cf.odd_order_convergents[7]
>>> ContinuedFraction(-1, 485112)
.. _exploring-continued-fractions.semiconvergents:

Semiconvergents
---------------

`Semiconvergents <https://en.wikipedia.org/wiki/Continued_fraction#Semiconvergents>`_ are :ref:`mediants <sequences.mediants>` of consecutive convergents of continued fractions. More precisely, if :math:`\frac{p_{k - 1}}{ q_{k - 1}}` and :math:`\frac{p_k}{q_k}` are consecutive convergents of a (possibly infinite) continued fraction :math:`[a_0;a_1,a_2,\ldots,a_k, a_{k + 1}, \ldots]`, and :math:`m` is any positive integer, then the fraction:

.. math::
\frac{p_{k - 1} + mp_k}{q_{k - 1} + mq_k}
is called a **semiconvergent** of :math:`\frac{p_{k - 1}}{q_{k - 1}}` and :math:`\frac{p_k}{q_k}`. This is also the :ref:`right-mediant <sequences.mediants.generalised>` of order :math:`m` of the two (consecutive) convergents, and is an intermediate fraction between them.

.. 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 :py:meth:`~continuedfractions.continuedfraction.ContinuedFraction.semiconvergent` method. A few examples are given below for the continued fraction :math:`[-5; 1, 1, 6, 7]` for :math:`-\frac{415}{93}`.

.. code:: python
>>> cf = ContinuedFraction(-415, 93)
>>> cf.elements
(-5, 1, 1, 6, 7)
>>> cf.convergents
mappingproxy({0: ContinuedFraction(-5, 1), 1: ContinuedFraction(-4, 1), 2: ContinuedFraction(-9, 2), 3: ContinuedFraction(-58, 13), 4: ContinuedFraction(-415, 93)})
>>> cf.semiconvergent(3, 1)
ContinuedFraction(-67, 15)
>>> cf.semiconvergent(3, 2)
ContinuedFraction(-125, 28)
>>> cf.semiconvergent(3, 3)
ContinuedFraction(-183, 41)
>>> cf.semiconvergent(3, 4)
ContinuedFraction(-241, 54)
>>> cf.semiconvergent(3, 5)
ContinuedFraction(-299, 67)
>>> cf.semiconvergent(3, 6)
ContinuedFraction(-357, 80)
>>> 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 semiconvergent of the :math:`(m - 1)`-st semiconvergent :math:`\frac{p_{k - 1} + (m - 1)p_k}{q_{k - 1} + (m - 1)q_k}` and the 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`, upper-bounded by :math:`\frac{p_k}{q_k}`, and thus 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:

.. math::
\begin{align}
\frac{p_{k - 1} + mp_k}{q_{k - 1} + mq_k} - \frac{p_{k - 1} + (m - 1)p_k}{q_{k - 1} + (m - 1)q_k} &=
\frac{p_kq_{k - 1} - p_{k - 1}q_k}{q_{k - 1}^2 + (2m - 1)q_kq_{k - 1} + m(m - 1)q_k^2} \\ &=
\frac{(-1)^{k + 1}}{q_{k - 1}^2 + (2m - 1)q_kq_{k - 1} + m(m - 1)q_k^2}
\end{align}
This can be illustrated again using the continued fraction for :math:`-\frac{415}{93}`:

.. code:: python
>>> cf = ContinuedFraction(-415, 93)
>>> cf.elements
(-5, 1, 1, 6, 7)
>>> cf.convergents
mappingproxy({0: ContinuedFraction(-5, 1), 1: ContinuedFraction(-4, 1), 2: ContinuedFraction(-9, 2), 3: ContinuedFraction(-58, 13), 4: ContinuedFraction(-415, 93)})
>>> cf.semiconvergent(2, 1) - cf.semiconvergent(1, 1)
ContinuedFraction(1, 6)
>>> cf.semiconvergent(3, 1) - cf.semiconvergent(2, 1)
ContinuedFraction(-2, 15)
>>> cf.semiconvergent(4, 1) - cf.semiconvergent(3, 1)
ContinuedFraction(7, 1590)
.. note::

When calling :py:meth:`~continuedfractions.continuedfraction.ContinuedFraction.semiconvergent` note that values of :math:`k`, which determines the :math:`k`-th convergent of a continued fraction, cannot exceed the order of the continued fraction.

.. _exploring-continued-fractions.rational-approximation:

Rational Approximation
Expand Down Expand Up @@ -235,11 +310,11 @@ We can illustrate rational approximation with the :py:meth:`~continuedfractions.
With the 10th convergent of :math:`\sqrt{2}` we have obtained an approximation that is accurate to :math:`6` decimal places in the fractional part. We'd ideally like to have as few elements as possible in our :py:class:`~continuedfractions.continuedfraction.ContinuedFraction` approximation of :math:`\sqrt{2}` for a desired level of accuracy, but this partly depends on how fast the partial, finite continued fractions represented by the chosen sequences of elements in our approximations are converging to the true value of :math:`\sqrt{2}` - these partial, finite continued fractions in a given continued fraction are called :ref:`convergents <exploring-continued-fractions.convergents-and-rational-approximations>`, and will be discussed in more detail later on.

If we use the 100th convergent (with :math:`101` elements consisting of the integer part :math:`1`, plus a tail of 100 twos), we get more accurate results:
If we use the 100th convergent (with :math:`101` elements consisting of the integer part :math:`1`, plus a tail of one hundred 2s), we get more accurate results:

.. code:: python
# Create a `ContinuedFraction` from the sequence 1, 2, 2, 2, ..., 2, with 100 2s in the tail
# Create a `ContinuedFraction` from the sequence 1, 2, 2, 2, ..., 2, with one hundred 2s in the tail
>>> sqrt2_100 = ContinuedFraction.from_elements(1, *[2] * 100)
ContinuedFraction(228725309250740208744750893347264645481, 161733217200188571081311986634082331709)
>>> sqrt2_100.elements
Expand All @@ -263,9 +338,7 @@ The decimal value of ``ContinuedFraction.from_elements(1, *[2] * 100)`` in this
>>> sqrt2_100.as_decimal()
Decimal('1.414213562373095048801688724209698078569671875376948073176679737990732478462093522589829309077750929')
Now, the decimal value of ``ContinuedFraction.from_elements(1, *[2] * 100)`` is accurate up to 75 digits in the fractional part, but deviates from the `true value <https://apod.nasa.gov/htmltest/gifcity/sqrt2.1mil>`_ after 76th digit onwards.

This example also highlights the fact that "almost all" square roots of positive integers are irrational, even though the set of positive integers which are perfect squares and the set of positive integers which are not perfect squares are both countably infinite - the former is an infinitely sparser subset of the integers.
Now, the decimal value of ``ContinuedFraction.from_elements(1, *[2] * 100)`` is accurate up to 75 digits in the fractional part, but deviates from the `true value <https://apod.nasa.gov/htmltest/gifcity/sqrt2.1mil>`_ after the 76th digit onwards.

.. _exploring-continued-fractions.remainders:

Expand Down
8 changes: 5 additions & 3 deletions docs/sources/sequences.rst
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ and right-mediants:
>>> cf1.right_mediant(cf2, k=100).as_decimal()
Decimal('0.5996015936254980079681274900')
As :math:`k \longrightarrow \infty` the left- and right-mediants form different, strictly monotonic, sequences
As :math:`k \longrightarrow \infty` the sequences of left- and right-mediants separate into two, strictly monotonic, sequences
converging to opposite limits: the left-mediants form a strictly decreasing sequence lower-bounded by :math:`\frac{a}{b}`:

.. math::
Expand All @@ -158,7 +158,7 @@ thus converging to :math:`\frac{c}{d}`:
\lim_{k \to \infty} \frac{a + kc}{b + kd} = \lim_{k \to \infty} \frac{\frac{a}{k} + c}{\frac{b}{k} + d} = \frac{c}{d}
We can see with the ``ContinuedFraction(1, 2)`` and ``ContinuedFraction(3, 5)`` instances used in the examples above, starting with the left-mediants:
We can see this with the ``ContinuedFraction(1, 2)`` and ``ContinuedFraction(3, 5)`` instances used in the examples above, starting with the left-mediants:

.. code:: python
Expand Down Expand Up @@ -194,6 +194,8 @@ And then the right-mediants:
>>> cf1.right_mediant(cf2, k=10 ** 6).as_decimal()
Decimal('0.5999999600000159999936000026')
A particular class of right-mediants are known as `semiconvergents <https://en.wikipedia.org/wiki/Continued_fraction#Semiconvergents>`_, and are described in more detail :ref:`here <exploring-continued-fractions.semiconvergents>`.

.. _sequences.coprime-integers:

Coprime Integers
Expand Down Expand Up @@ -498,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 ternary trees the worst case time complexity of search, for either tree, is given by :math:`O(3^d)`, where :math:`3` is the (constant) branching factor, and :math:`d` is the depth to which the search is performed. Theoretically, the space complexity is :math:`O(3d)`, but the pruning of nodes and backtracking ensures that for almost all of the search for any given :math:`n` only some fraction of :math:`d` nodes, along a single branch, are ever stored all at once.
As the KSRM trees are (infinite) ternary trees the worst case time complexity of search for a given :math:`n`, for either tree, is given by :math:`O(3^d)`, where :math:`3` is the (constant) branching factor, and :math:`d` is the depth to which the search is performed. Theoretically, the space complexity is :math:`O(3d)`, but the pruning of nodes and backtracking ensures that for almost all of the search for any given :math:`n` only some fraction of :math:`d` nodes, along a single branch, are ever stored all at once.

.. _sequences.farey-sequences:

Expand Down
Loading

0 comments on commit 8458b87

Please sign in to comment.