AbstractRuleFactory.java

  1. /*
  2.  * Licensed to the Hipparchus project 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 Hipparchus project 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.  *      https://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.hipparchus.analysis.integration.gauss;

  18. import java.util.Arrays;
  19. import java.util.SortedMap;
  20. import java.util.TreeMap;

  21. import org.hipparchus.analysis.UnivariateFunction;
  22. import org.hipparchus.exception.LocalizedCoreFormats;
  23. import org.hipparchus.exception.MathIllegalArgumentException;
  24. import org.hipparchus.util.FastMath;
  25. import org.hipparchus.util.Incrementor;
  26. import org.hipparchus.util.Pair;

  27. /**
  28.  * Base class for rules that determines the integration nodes and their
  29.  * weights.
  30.  * Subclasses must implement the {@link #computeRule(int) computeRule} method.
  31.  *
  32.  * @since 2.0
  33.  */
  34. public abstract class AbstractRuleFactory implements RuleFactory {

  35.     /** List of points and weights, indexed by the order of the rule. */
  36.     private final SortedMap<Integer, Pair<double[], double[]>> pointsAndWeights = new TreeMap<>();

  37.     /** Empty constructor.
  38.      * <p>
  39.      * This constructor is not strictly necessary, but it prevents spurious
  40.      * javadoc warnings with JDK 18 and later.
  41.      * </p>
  42.      * @since 3.0
  43.      */
  44.     protected AbstractRuleFactory() { // NOPMD - unnecessary constructor added intentionally to make javadoc happy
  45.         // nothing to do
  46.     }

  47.     /** {@inheritDoc} */
  48.     @Override
  49.     public Pair<double[], double[]> getRule(int numberOfPoints)
  50.         throws MathIllegalArgumentException {

  51.         if (numberOfPoints <= 0) {
  52.             throw new MathIllegalArgumentException(LocalizedCoreFormats.NUMBER_OF_POINTS,
  53.                                                    numberOfPoints);
  54.         }
  55.         if (numberOfPoints > 1000) {
  56.             throw new MathIllegalArgumentException(LocalizedCoreFormats.NUMBER_TOO_LARGE,
  57.                                                    numberOfPoints, 1000);
  58.         }

  59.         Pair<double[], double[]> rule;
  60.         synchronized (pointsAndWeights) {
  61.             // Try to obtain the rule from the cache.
  62.             rule = pointsAndWeights.get(numberOfPoints);

  63.             if (rule == null) {
  64.                 // Rule not computed yet.

  65.                 // Compute the rule.
  66.                 rule = computeRule(numberOfPoints);

  67.                 // Cache it.
  68.                 pointsAndWeights.put(numberOfPoints, rule);
  69.             }
  70.         }

  71.         // Return a copy.
  72.         return new Pair<>(rule.getFirst().clone(), rule.getSecond().clone());

  73.     }

  74.     /**
  75.      * Computes the rule for the given order.
  76.      *
  77.      * @param numberOfPoints Order of the rule to be computed.
  78.      * @return the computed rule.
  79.      * @throws MathIllegalArgumentException if the elements of the pair do not
  80.      * have the same length.
  81.      */
  82.     protected abstract Pair<double[], double[]> computeRule(int numberOfPoints)
  83.         throws MathIllegalArgumentException;

  84.     /** Computes roots of the associated orthogonal polynomials.
  85.      * <p>
  86.      * The roots are found using the <a href="https://en.wikipedia.org/wiki/Aberth_method">Aberth method</a>.
  87.      * The guess points for initializing search for degree n are fixed for degrees 1 and 2 and are
  88.      * selected from n-1 roots of rule n-1 (the two extreme roots are used, plus the n-1 intermediate
  89.      * points between all roots).
  90.      * </p>
  91.      * @param n number of roots to search for
  92.      * @param ratioEvaluator function evaluating the ratio Pₙ(x)/Pₙ'(x)
  93.      * @return sorted array of roots
  94.      */
  95.     protected double[] findRoots(final int n, final UnivariateFunction ratioEvaluator) {

  96.         final double[] roots  = new double[n];

  97.         // set up initial guess
  98.         if (n == 1) {
  99.             // arbitrary guess
  100.             roots[0] = 0;
  101.         } else if (n == 2) {
  102.             // arbitrary guess
  103.             roots[0] = -1;
  104.             roots[1] = +1;
  105.         } else {

  106.             // get roots from previous rule.
  107.             // If it has not been computed yet it will trigger a recursive call
  108.             final double[] previousPoints = getRule(n - 1).getFirst();

  109.             // first guess at previous first root
  110.             roots[0] = previousPoints[0];

  111.             // intermediate guesses between previous roots
  112.             for (int i = 1; i < n - 1; ++i) {
  113.                 roots[i] = (previousPoints[i - 1] + previousPoints[i]) * 0.5;
  114.             }

  115.             // last guess at previous last root
  116.             roots[n - 1] = previousPoints[n - 2];

  117.         }

  118.         // use Aberth method to find all roots simultaneously
  119.         final double[]    ratio       = new double[n];
  120.         final Incrementor incrementor = new Incrementor(1000);
  121.         double            tol;
  122.         double            maxOffset;
  123.         do {

  124.             // safety check that triggers an exception if too much iterations are made
  125.             incrementor.increment();

  126.             // find the ratio P(xᵢ)/P'(xᵢ) for all current roots approximations
  127.             for (int i = 0; i < n; ++i) {
  128.                 ratio[i] = ratioEvaluator.value(roots[i]);
  129.             }

  130.             // move roots approximations all at once, using Aberth method
  131.             maxOffset = 0;
  132.             for (int i = 0; i < n; ++i) {
  133.                 double sum = 0;
  134.                 for (int j = 0; j < n; ++j) {
  135.                     if (j != i) {
  136.                         sum += 1 / (roots[i] - roots[j]);
  137.                     }
  138.                 }
  139.                 final double offset = ratio[i] / (1 - ratio[i] * sum);
  140.                 maxOffset = FastMath.max(maxOffset, FastMath.abs(offset));
  141.                 roots[i] -= offset;
  142.             }

  143.             // we set tolerance to 1 ulp of the largest root
  144.             tol = 0;
  145.             for (final double r : roots) {
  146.                 tol = FastMath.max(tol, FastMath.ulp(r));
  147.             }

  148.         } while (maxOffset > tol);

  149.         // sort the roots
  150.         Arrays.sort(roots);

  151.         return roots;

  152.     }

  153.     /** Enforce symmetry of roots.
  154.      * @param roots roots to process in place
  155.      */
  156.     protected void enforceSymmetry(final double[] roots) {

  157.         final int n = roots.length;

  158.         // enforce symmetry
  159.         for (int i = 0; i < n / 2; ++i) {
  160.             final int idx = n - i - 1;
  161.             final double c = (roots[i] - roots[idx]) * 0.5;
  162.             roots[i]   = +c;
  163.             roots[idx] = -c;
  164.         }

  165.         // If n is odd, 0 is a root.
  166.         // Note: as written, the test for oddness will work for negative
  167.         // integers too (although it is not necessary here), preventing
  168.         // a FindBugs warning.
  169.         if (n % 2 != 0) {
  170.             roots[n / 2] = 0;
  171.         }

  172.     }

  173. }