GeneralizedContinuedFraction.java

  1. /*
  2.  * Licensed to the Apache Software Foundation (ASF) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * The ASF licenses this file to You under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  You may obtain a copy of the License at
  8.  *
  9.  *      http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */
  17. package org.apache.commons.numbers.fraction;

  18. import java.util.function.Supplier;

  19. /**
  20.  * Provides a means to evaluate
  21.  * <a href="https://mathworld.wolfram.com/GeneralizedContinuedFraction.html">generalized continued fractions</a>.
  22.  *
  23.  * <p>The continued fraction uses the following form for the numerator ({@code a}) and
  24.  * denominator ({@code b}) coefficients:
  25.  * <pre>
  26.  *              a1
  27.  * b0 + ------------------
  28.  *      b1 +      a2
  29.  *           -------------
  30.  *           b2 +    a3
  31.  *                --------
  32.  *                b3 + ...
  33.  * </pre>
  34.  *
  35.  * <p>A generator of the coefficients must be provided to evaluate the continued fraction.
  36.  *
  37.  * <p>The implementation of the fraction evaluation is based on the modified Lentz algorithm
  38.  * as described on page 508 in:
  39.  *
  40.  * <ul>
  41.  *   <li>
  42.  *   I. J. Thompson,  A. R. Barnett (1986).
  43.  *   "Coulomb and Bessel Functions of Complex Arguments and Order."
  44.  *   Journal of Computational Physics 64, 490-509.
  45.  *   <a target="_blank" href="https://www.fresco.org.uk/papers/Thompson-JCP64p490.pdf">
  46.  *   https://www.fresco.org.uk/papers/Thompson-JCP64p490.pdf</a>
  47.  *   </li>
  48.  * </ul>
  49.  *
  50.  * @see <a href="https://mathworld.wolfram.com/GeneralizedContinuedFraction.html">Wikipedia: Generalized continued fraction</a>
  51.  * @see <a href="https://en.wikipedia.org/wiki/Generalized_continued_fraction">MathWorld: Generalized continued fraction</a>
  52.  * @since 1.1
  53.  */
  54. public final class GeneralizedContinuedFraction {
  55.     /**
  56.      * The value for any number close to zero.
  57.      *
  58.      * <p>"The parameter small should be some non-zero number less than typical values of
  59.      * eps * |b_n|, e.g., 1e-50".
  60.      */
  61.     static final double SMALL = 1e-50;
  62.     /** Default maximum number of iterations. */
  63.     static final int DEFAULT_ITERATIONS = Integer.MAX_VALUE;
  64.     /**
  65.      * Minimum relative error epsilon. Equal to 1 - Math.nextDown(1.0), or 2^-53.
  66.      *
  67.      * <p>The epsilon is used to compare the change in the magnitude of the fraction
  68.      * convergent to 1.0. In theory eps can be 2^-53 reflecting the smallest reduction in
  69.      * magnitude possible i.e. {@code next = previous * Math.nextDown(1.0)}, or zero
  70.      * reflecting exact convergence.
  71.      *
  72.      * <p>If set to zero then the algorithm requires exact convergence which may not be possible
  73.      * due to floating point error in the algorithm. For example the golden ratio will not
  74.      * converge.
  75.      *
  76.      * <p>The minimum value will stop the recursive evaluation at the smallest possible
  77.      * increase or decrease in the convergent.
  78.      */
  79.     private static final double MIN_EPSILON = 0x1.0p-53;
  80.     /** Maximum relative error epsilon. This is configured to prevent incorrect usage. Values
  81.      * higher than 1.0 invalidate the relative error lower bound of {@code (1 - eps) / 1}.
  82.      * Set to 0.5 which is a very weak relative error tolerance. */
  83.     private static final double MAX_EPSILON = 0.5;
  84.     /** Default low threshold for change in magnitude. Precomputed using MIN_EPSILON.
  85.      * Equal to 1 - 2^-53. */
  86.     private static final double DEFAULT_LOW = 1 - MIN_EPSILON;
  87.     /** Default absolute difference threshold for change in magnitude. Precomputed using MIN_EPSILON.
  88.      * Equal to {@code 1 / (1 - 2^-53) = 2^-52}. */
  89.     private static final double DEFAULT_EPS = 0x1.0p-52;

  90.     /**
  91.      * Defines the <a href="https://mathworld.wolfram.com/GeneralizedContinuedFraction.html">
  92.      * {@code n}-th "a" and "b" coefficients</a> of the continued fraction.
  93.      *
  94.      * @since 1.1
  95.      */
  96.     public static final class Coefficient {
  97.         /** "a" coefficient. */
  98.         private final double a;
  99.         /** "b" coefficient. */
  100.         private final double b;

  101.         /**
  102.          * @param a "a" coefficient
  103.          * @param b "b" coefficient
  104.          */
  105.         private Coefficient(double a, double b) {
  106.             this.a = a;
  107.             this.b = b;
  108.         }

  109.         /**
  110.          * Returns the {@code n}-th "a" coefficient of the continued fraction.
  111.          *
  112.          * @return the coefficient <code>a<sub>n</sub></code>.
  113.          */
  114.         public double getA() {
  115.             return a;
  116.         }

  117.         /**
  118.          * Returns the {@code n}-th "b" coefficient of the continued fraction.
  119.          *
  120.          * @return the coefficient <code>b<sub>n</sub></code>.
  121.          */
  122.         public double getB() {
  123.             return b;
  124.         }

  125.         /**
  126.          * Create a new coefficient.
  127.          *
  128.          * @param a "a" coefficient
  129.          * @param b "b" coefficient
  130.          * @return the coefficient
  131.          */
  132.         public static Coefficient of(double a, double b) {
  133.             return new Coefficient(a, b);
  134.         }
  135.     }

  136.     /** No instances. */
  137.     private GeneralizedContinuedFraction() {}

  138.     /**
  139.      * Evaluates the continued fraction.
  140.      *
  141.      * <p>Note: The first generated partial numerator a<sub>0</sub> is discarded.
  142.      *
  143.      * @param gen Generator of coefficients.
  144.      * @return the value of the continued fraction.
  145.      * @throws ArithmeticException if the algorithm fails to converge or if the maximal number of
  146.      * iterations is reached before the expected convergence is achieved.
  147.      * @see #value(Supplier,double,int)
  148.      */
  149.     public static double value(Supplier<Coefficient> gen) {
  150.         return value(gen, MIN_EPSILON, DEFAULT_ITERATIONS);
  151.     }

  152.     /**
  153.      * Evaluates the continued fraction.
  154.      *
  155.      * <p>Note: The first generated partial numerator a<sub>0</sub> is discarded.
  156.      *
  157.      * @param gen Generator of coefficients.
  158.      * @param epsilon Maximum relative error allowed.
  159.      * @return the value of the continued fraction.
  160.      * @throws ArithmeticException if the algorithm fails to converge or if the maximal number of
  161.      * iterations is reached before the expected convergence is achieved.
  162.      * @see #value(Supplier,double,int)
  163.      */
  164.     public static double value(Supplier<Coefficient> gen, double epsilon) {
  165.         return value(gen, epsilon, DEFAULT_ITERATIONS);
  166.     }

  167.     /**
  168.      * Evaluates the continued fraction.
  169.      * <pre>
  170.      *              a1
  171.      * b0 + ------------------
  172.      *      b1 +      a2
  173.      *           -------------
  174.      *           b2 +    a3
  175.      *                --------
  176.      *                b3 + ...
  177.      * </pre>
  178.      *
  179.      * <p>Setting coefficient a<sub>n</sub> to zero will signal the end of the recursive evaluation.
  180.      *
  181.      * <p>Note: The first generated partial numerator a<sub>0</sub> is discarded.
  182.      *
  183.      * <p><b>Usage Note</b>
  184.      *
  185.      * <p>This method is not functionally identical to calling
  186.      * {@link #value(double, Supplier, double, int)} with the generator configured to
  187.      * provide coefficients from n=1 and supplying b<sub>0</sub> separately. In some cases
  188.      * the computed result from the two variations may be different by more than the
  189.      * provided epsilon. The other method should be used if b<sub>0</sub> is zero or very
  190.      * small. See the corresponding javadoc for details.
  191.      *
  192.      * @param gen Generator of coefficients.
  193.      * @param epsilon Maximum relative error allowed.
  194.      * @param maxIterations Maximum number of iterations.
  195.      * @return the value of the continued fraction.
  196.      * @throws ArithmeticException if the algorithm fails to converge or if the maximal number of
  197.      * iterations is reached before the expected convergence is achieved.
  198.      * @see #value(double, Supplier, double, int)
  199.      */
  200.     public static double value(Supplier<Coefficient> gen, double epsilon, int maxIterations) {
  201.         // Use the first b coefficient to seed the evaluation of the fraction.
  202.         // Coefficient a is discarded.
  203.         final Coefficient c = gen.get();
  204.         return evaluate(c.getB(), gen, epsilon, maxIterations);
  205.     }

  206.     /**
  207.      * Evaluates the continued fraction.
  208.      *
  209.      * <p>Note: The initial term b<sub>0</sub> is supplied as an argument.
  210.      * Both of the first generated terms a and b are used. This fraction evaluation
  211.      * can be used when:
  212.      * <ul>
  213.      *  <li>b<sub>0</sub> is not part of a regular series
  214.      *  <li>b<sub>0</sub> is zero and the result will evaluate only the continued fraction component
  215.      *  <li>b<sub>0</sub> is very small and the result is expected to approach zero
  216.      * </ul>
  217.      *
  218.      * @param b0 Coefficient b<sub>0</sub>.
  219.      * @param gen Generator of coefficients.
  220.      * @return the value of the continued fraction.
  221.      * @throws ArithmeticException if the algorithm fails to converge or if the maximal number
  222.      * of iterations is reached before the expected convergence is achieved.
  223.      * @see #value(double,Supplier,double,int)
  224.      */
  225.     public static double value(double b0, Supplier<Coefficient> gen) {
  226.         return value(b0, gen, MIN_EPSILON, DEFAULT_ITERATIONS);
  227.     }

  228.     /**
  229.      * Evaluates the continued fraction.
  230.      *
  231.      * <p>Note: The initial term b<sub>0</sub> is supplied as an argument.
  232.      * Both of the first generated terms a and b are used. This fraction evaluation
  233.      * can be used when:
  234.      * <ul>
  235.      *  <li>b<sub>0</sub> is not part of a regular series
  236.      *  <li>b<sub>0</sub> is zero and the result will evaluate only the continued fraction component
  237.      *  <li>b<sub>0</sub> is very small and the result is expected to approach zero
  238.      * </ul>
  239.      *
  240.      * @param b0 Coefficient b<sub>0</sub>.
  241.      * @param gen Generator of coefficients.
  242.      * @param epsilon Maximum relative error allowed.
  243.      * @return the value of the continued fraction.
  244.      * @throws ArithmeticException if the algorithm fails to converge or if the maximal number
  245.      * of iterations is reached before the expected convergence is achieved.
  246.      * @see #value(double,Supplier,double,int)
  247.      */
  248.     public static double value(double b0, Supplier<Coefficient> gen, double epsilon) {
  249.         return value(b0, gen, epsilon, DEFAULT_ITERATIONS);
  250.     }

  251.     /**
  252.      * Evaluates the continued fraction.
  253.      * <pre>
  254.      *              a1
  255.      * b0 + ------------------
  256.      *      b1 +      a2
  257.      *           -------------
  258.      *           b2 +    a3
  259.      *                --------
  260.      *                b3 + ...
  261.      * </pre>
  262.      *
  263.      * <p>Setting coefficient a<sub>n</sub> to zero will signal the end of the recursive evaluation.
  264.      *
  265.      * <p>Note: The initial term b<sub>0</sub> is supplied as an argument.
  266.      * Both of the first generated terms a and b are used. This fraction evaluation
  267.      * can be used when:
  268.      * <ul>
  269.      *  <li>b<sub>0</sub> is not part of a regular series
  270.      *  <li>b<sub>0</sub> is zero and the result will evaluate only the continued fraction component
  271.      *  <li>b<sub>0</sub> is very small and the result is expected to approach zero
  272.      * </ul>
  273.      *
  274.      * <p><b>Usage Note</b>
  275.      *
  276.      * <p>This method is not functionally identical to calling
  277.      * {@link #value(Supplier, double, int)} with the generator configured to provide term
  278.      * "b<sub>0</sub>" in the first coefficient. In some cases the computed result from
  279.      * the two variations may be different by more than the provided epsilon. The
  280.      * convergence of the continued fraction algorithm relies on computing an update
  281.      * multiplier applied to the current value. Convergence is faster if the initial value
  282.      * is close to the final value. The {@link #value(Supplier, double, int)} method will
  283.      * initialise the current value using b<sub>0</sub> and evaluate the continued
  284.      * fraction using updates computed from the generated coefficients. This method
  285.      * initialises the algorithm using b1 to evaluate part of the continued fraction and
  286.      * computes the result as:
  287.      *
  288.      * <pre>
  289.      *        a1
  290.      * b0 + ------
  291.      *       part
  292.      * </pre>
  293.      *
  294.      * <p>This is preferred if b<sub>0</sub> is smaller in magnitude than the continued
  295.      * fraction component. In particular the evaluation algorithm sets a bound on the
  296.      * minimum initial value as {@code 1e-50}. If b<sub>0</sub> is smaller than this value
  297.      * then using this method is the preferred evaluation.
  298.      *
  299.      * @param b0 Coefficient b<sub>0</sub>.
  300.      * @param gen Generator of coefficients.
  301.      * @param epsilon Maximum relative error allowed.
  302.      * @param maxIterations Maximum number of iterations.
  303.      * @return the value of the continued fraction.
  304.      * @throws ArithmeticException if the algorithm fails to converge or if the maximal number
  305.      * of iterations is reached before the expected convergence is achieved.
  306.      * @see #value(Supplier,double,int)
  307.      */
  308.     public static double value(double b0, Supplier<Coefficient> gen, double epsilon, int maxIterations) {
  309.         // Use the first b coefficient to seed the evaluation of the fraction.
  310.         // Coefficient a is used to compute the final result as the numerator term a1.
  311.         // The supplied b0 is added to the result.
  312.         final Coefficient c = gen.get();
  313.         return b0 + c.getA() / evaluate(c.getB(), gen, epsilon, maxIterations);
  314.     }

  315.     /**
  316.      * Evaluates the continued fraction using the modified Lentz algorithm described in
  317.      * Thompson and Barnett (1986) Journal of Computational Physics 64, 490-509.
  318.      * <pre>
  319.      *              a1
  320.      * b0 + ------------------
  321.      *      b1 +      a2
  322.      *           -------------
  323.      *           b2 +    a3
  324.      *                --------
  325.      *                b3 + ...
  326.      * </pre>
  327.      *
  328.      * <p>Note: The initial term b<sub>0</sub> is supplied as an argument.
  329.      * Both of the first generated terms a and b are used.
  330.      *
  331.      * <p><b>Implementation Note</b>
  332.      *
  333.      * <p>This method is private and functionally different from
  334.      * {@link #value(double, Supplier, double, int)}. The convergence of the algorithm relies on
  335.      * computing an update multiplier applied to the current value, initialised as b0. Accuracy
  336.      * of the evaluation can be effected if the magnitude of b0 is very different from later
  337.      * terms. In particular if initialised as 0 the algorithm will not function and so must
  338.      * set b0 to a small non-zero number. The public methods with the leading b0 term
  339.      * provide evaluation of the fraction if the term b0 is zero.
  340.      *
  341.      * @param b0 Coefficient b<sub>0</sub>.
  342.      * @param gen Generator of coefficients.
  343.      * @param epsilon Maximum relative error allowed.
  344.      * @param maxIterations Maximum number of iterations.
  345.      * @return the value of the continued fraction.
  346.      * @throws ArithmeticException if the algorithm fails to converge or if the maximal number
  347.      * of iterations is reached before the expected convergence is achieved.
  348.      */
  349.     static double evaluate(double b0, Supplier<Coefficient> gen, double epsilon, int maxIterations) {
  350.         // Relative error epsilon should not be zero to prevent drift in the event
  351.         // that the update ratio never achieves 1.0.

  352.         // Epsilon is the relative change allowed from 1. Configure the absolute limits so
  353.         // convergence requires: low <= deltaN <= high
  354.         // low = 1 - eps
  355.         // high = 1 / (1 - eps)
  356.         // High is always further from 1 than low in absolute distance. Do not store high
  357.         // but store the maximum absolute deviation from 1 for convergence = high - 1.
  358.         // If this is achieved a second check is made against low.
  359.         double low;
  360.         double eps;
  361.         if (epsilon > MIN_EPSILON && epsilon <= MAX_EPSILON) {
  362.             low = 1 - epsilon;
  363.             eps = 1 / low - 1;
  364.         } else {
  365.             // Precomputed defaults. Used when epsilon <= MIN_EPSILON
  366.             low = DEFAULT_LOW;
  367.             eps = DEFAULT_EPS;
  368.         }

  369.         double hPrev = updateIfCloseToZero(b0);

  370.         // Notes from Thompson and Barnett:
  371.         //
  372.         // Fraction convergent: hn = An / Bn
  373.         // A(-1) = 1, A0 = b0, B(-1) = 0, B0 = 1

  374.         // Compute the ratios:
  375.         // Dn = B(n-1) / Bn  = 1 / (an * D(n-1) + bn)
  376.         // Cn = An / A(n-1)  = an / C(n-1) + bn
  377.         //
  378.         // Ratio of successive convergents:
  379.         // delta n = hn / h(n-1)
  380.         //         = Cn / Dn

  381.         // Avoid divisors being zero (less than machine precision) by shifting them to e.g. 1e-50.

  382.         double dPrev = 0.0;
  383.         double cPrev = hPrev;

  384.         for (int n = maxIterations; n > 0; n--) {
  385.             final Coefficient c = gen.get();
  386.             final double a = c.getA();
  387.             final double b = c.getB();

  388.             double dN = updateIfCloseToZero(b + a * dPrev);
  389.             final double cN = updateIfCloseToZero(b + a / cPrev);

  390.             dN = 1 / dN;
  391.             final double deltaN = cN * dN;
  392.             final double hN = hPrev * deltaN;

  393.             // If the fraction is convergent then deltaN -> 1.
  394.             // Computation of deltaN = 0 or deltaN = big will result in zero or overflow.
  395.             // Directly check for overflow on hN (this ensures the result is finite).

  396.             if (!Double.isFinite(hN)) {
  397.                 throw new FractionException("Continued fraction diverged to " + hN);
  398.             }

  399.             // Check for underflow on deltaN. This allows fractions to compute zero
  400.             // if this is the convergent limit.
  401.             // Note: deltaN is only zero if dN > 1e-50 / min_value, or 2.02e273.
  402.             // Since dN is the ratio of convergent denominators this magnitude of
  403.             // ratio is a presumed to be an error.
  404.             if (deltaN == 0) {
  405.                 throw new FractionException("Ratio of successive convergents is zero");
  406.             }

  407.             // Update from Thompson and Barnett to use <= eps in place of < eps.
  408.             // eps = high - 1
  409.             // A second check is made to ensure:
  410.             // low <= deltaN <= high
  411.             if (Math.abs(deltaN - 1) <= eps && deltaN >= low) {
  412.                 return hN;
  413.             }

  414.             dPrev = dN;
  415.             cPrev = cN;
  416.             hPrev = hN;
  417.         }

  418.         throw new FractionException("Maximum iterations (%d) exceeded", maxIterations);
  419.     }

  420.     /**
  421.      * Returns the value, or if close to zero returns a small epsilon of the same sign.
  422.      *
  423.      * <p>This method is used in Thompson &amp; Barnett to monitor both the numerator and denominator
  424.      * ratios for approaches to zero.
  425.      *
  426.      * @param value the value
  427.      * @return the value (or small epsilon)
  428.      */
  429.     private static double updateIfCloseToZero(double value) {
  430.         return Math.abs(value) < SMALL ? Math.copySign(SMALL, value) : value;
  431.     }
  432. }