ContinuedFraction.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. import org.apache.commons.numbers.fraction.GeneralizedContinuedFraction.Coefficient;

  20. /**
  21.  * Provides a generic means to evaluate
  22.  * <a href="https://mathworld.wolfram.com/ContinuedFraction.html">continued fractions</a>.
  23.  *
  24.  * <p>The continued fraction uses the following form for the numerator ({@code a}) and
  25.  * denominator ({@code b}) coefficients:
  26.  * <pre>
  27.  *              a1
  28.  * b0 + ------------------
  29.  *      b1 +      a2
  30.  *           -------------
  31.  *           b2 +    a3
  32.  *                --------
  33.  *                b3 + ...
  34.  * </pre>
  35.  *
  36.  * <p>Subclasses must provide the {@link #getA(int,double) a} and {@link #getB(int,double) b}
  37.  * coefficients to evaluate the continued fraction.
  38.  *
  39.  * <p>This class allows evaluation of the fraction for a specified evaluation point {@code x};
  40.  * the point can be used to express the values of the coefficients.
  41.  * Evaluation of a continued fraction from a generator of the coefficients can be performed using
  42.  * {@link GeneralizedContinuedFraction}. This may be preferred if the coefficients can be computed
  43.  * with updates to the previous coefficients.
  44.  */
  45. public abstract class ContinuedFraction {
  46.     /** Create an instance. */
  47.     public ContinuedFraction() {}

  48.     /**
  49.      * Defines the <a href="https://mathworld.wolfram.com/ContinuedFraction.html">
  50.      * {@code n}-th "a" coefficient</a> of the continued fraction.
  51.      *
  52.      * @param n Index of the coefficient to retrieve.
  53.      * @param x Evaluation point.
  54.      * @return the coefficient <code>a<sub>n</sub></code>.
  55.      */
  56.     protected abstract double getA(int n, double x);

  57.     /**
  58.      * Defines the <a href="https://mathworld.wolfram.com/ContinuedFraction.html">
  59.      * {@code n}-th "b" coefficient</a> of the continued fraction.
  60.      *
  61.      * @param n Index of the coefficient to retrieve.
  62.      * @param x Evaluation point.
  63.      * @return the coefficient <code>b<sub>n</sub></code>.
  64.      */
  65.     protected abstract double getB(int n, double x);

  66.     /**
  67.      * Evaluates the continued fraction.
  68.      *
  69.      * @param x the evaluation point.
  70.      * @param epsilon Maximum relative error allowed.
  71.      * @return the value of the continued fraction evaluated at {@code x}.
  72.      * @throws ArithmeticException if the algorithm fails to converge.
  73.      * @throws ArithmeticException if the maximal number of iterations is reached
  74.      * before the expected convergence is achieved.
  75.      *
  76.      * @see #evaluate(double,double,int)
  77.      */
  78.     public double evaluate(double x, double epsilon) {
  79.         return evaluate(x, epsilon, GeneralizedContinuedFraction.DEFAULT_ITERATIONS);
  80.     }

  81.     /**
  82.      * Evaluates the continued fraction.
  83.      * <p>
  84.      * The implementation of this method is based on the modified Lentz algorithm as described
  85.      * on page 508 in:
  86.      * </p>
  87.      *
  88.      * <ul>
  89.      *   <li>
  90.      *   I. J. Thompson,  A. R. Barnett (1986).
  91.      *   "Coulomb and Bessel Functions of Complex Arguments and Order."
  92.      *   Journal of Computational Physics 64, 490-509.
  93.      *   <a target="_blank" href="https://www.fresco.org.uk/papers/Thompson-JCP64p490.pdf">
  94.      *   https://www.fresco.org.uk/papers/Thompson-JCP64p490.pdf</a>
  95.      *   </li>
  96.      * </ul>
  97.      *
  98.      * @param x Point at which to evaluate the continued fraction.
  99.      * @param epsilon Maximum relative error allowed.
  100.      * @param maxIterations Maximum number of iterations.
  101.      * @return the value of the continued fraction evaluated at {@code x}.
  102.      * @throws ArithmeticException if the algorithm fails to converge.
  103.      * @throws ArithmeticException if the maximal number of iterations is reached
  104.      * before the expected convergence is achieved.
  105.      */
  106.     public double evaluate(double x, double epsilon, int maxIterations) {
  107.         // Delegate to GeneralizedContinuedFraction

  108.         // Get the first coefficient
  109.         final double b0 = getB(0, x);

  110.         // Generate coefficients from (a1,b1)
  111.         final Supplier<Coefficient> gen = new Supplier<Coefficient>() {
  112.             /** Coefficient index. */
  113.             private int n;
  114.             @Override
  115.             public Coefficient get() {
  116.                 n++;
  117.                 final double a = getA(n, x);
  118.                 final double b = getB(n, x);
  119.                 return Coefficient.of(a, b);
  120.             }
  121.         };

  122.         // Invoke appropriate method based on magnitude of first term.

  123.         // If b0 is too small or zero it is set to a non-zero small number to allow
  124.         // magnitude updates. Avoid this by adding b0 at the end if b0 is small.
  125.         //
  126.         // This handles the use case of a negligible initial term. If b1 is also small
  127.         // then the evaluation starting at b0 or b1 may converge poorly.
  128.         // One solution is to manually compute the convergent until it is not small
  129.         // and then evaluate the fraction from the next term:
  130.         // h1 = b0 + a1 / b1
  131.         // h2 = b0 + a1 / (b1 + a2 / b2)
  132.         // ...
  133.         // hn not 'small', start generator at (n+1):
  134.         // value = GeneralizedContinuedFraction.value(hn, gen)
  135.         // This solution is not implemented to avoid recursive complexity.

  136.         if (Math.abs(b0) < GeneralizedContinuedFraction.SMALL) {
  137.             // Updates from initial convergent b1 and computes:
  138.             // b0 + a1 / [  b1 + a2 / (b2 + ... ) ]
  139.             return GeneralizedContinuedFraction.value(b0, gen, epsilon, maxIterations);
  140.         }

  141.         // Use the package-private evaluate method.
  142.         // Calling GeneralizedContinuedFraction.value(gen, epsilon, maxIterations)
  143.         // requires the generator to start from (a0,b0) and repeats computation of b0
  144.         // and wastes computation of a0.

  145.         // Updates from initial convergent b0:
  146.         // b0 + a1 / (b1 + ... )
  147.         return GeneralizedContinuedFraction.evaluate(b0, gen, epsilon, maxIterations);
  148.     }
  149. }