FieldAbstractRuleFactory.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.CalculusFieldElement;
  22. import org.hipparchus.Field;
  23. import org.hipparchus.analysis.CalculusFieldUnivariateFunction;
  24. import org.hipparchus.exception.LocalizedCoreFormats;
  25. import org.hipparchus.exception.MathIllegalArgumentException;
  26. import org.hipparchus.util.FastMath;
  27. import org.hipparchus.util.Incrementor;
  28. import org.hipparchus.util.MathArrays;
  29. import org.hipparchus.util.Pair;

  30. /**
  31.  * Base class for rules that determines the integration nodes and their
  32.  * weights.
  33.  * Subclasses must implement the {@link #computeRule(int) computeRule} method.
  34.  *
  35.  * @param <T> Type of the number used to represent the points and weights of
  36.  * the quadrature rules.
  37.  * @since 2.0
  38.  */
  39. public abstract class FieldAbstractRuleFactory<T extends CalculusFieldElement<T>> implements FieldRuleFactory<T> {

  40.     /** Field to which rule coefficients belong. */
  41.     private final Field<T> field;

  42.     /** List of points and weights, indexed by the order of the rule. */
  43.     private final SortedMap<Integer, Pair<T[], T[]>> pointsAndWeights;

  44.     /** Simple constructor
  45.      * @param field field to which rule coefficients belong
  46.      */
  47.     protected FieldAbstractRuleFactory(final Field<T> field) {
  48.         this.field            = field;
  49.         this.pointsAndWeights = new TreeMap<>();
  50.     }

  51.     /** Get the field to which rule coefficients belong.
  52.      * @return field to which rule coefficients belong
  53.      */
  54.     public Field<T> getField() {
  55.         return field;
  56.     }

  57.     /** {@inheritDoc} */
  58.     @Override
  59.     public Pair<T[], T[]> getRule(int numberOfPoints)
  60.         throws MathIllegalArgumentException {

  61.         if (numberOfPoints <= 0) {
  62.             throw new MathIllegalArgumentException(LocalizedCoreFormats.NUMBER_OF_POINTS,
  63.                                                    numberOfPoints);
  64.         }
  65.         if (numberOfPoints > 1000) {
  66.             throw new MathIllegalArgumentException(LocalizedCoreFormats.NUMBER_TOO_LARGE,
  67.                                                    numberOfPoints, 1000);
  68.         }

  69.         Pair<T[], T[]> rule;
  70.         synchronized (pointsAndWeights) {
  71.             // Try to obtain the rule from the cache.
  72.             rule = pointsAndWeights.get(numberOfPoints);

  73.             if (rule == null) {
  74.                 // Rule not computed yet.

  75.                 // Compute the rule.
  76.                 rule = computeRule(numberOfPoints);

  77.                 // Cache it.
  78.                 pointsAndWeights.put(numberOfPoints, rule);
  79.             }
  80.         }

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

  83.     }

  84.     /**
  85.      * Computes the rule for the given order.
  86.      *
  87.      * @param numberOfPoints Order of the rule to be computed.
  88.      * @return the computed rule.
  89.      * @throws MathIllegalArgumentException if the elements of the pair do not
  90.      * have the same length.
  91.      */
  92.     protected abstract Pair<T[], T[]> computeRule(int numberOfPoints)
  93.         throws MathIllegalArgumentException;

  94.     /** Computes roots of the associated orthogonal polynomials.
  95.      * <p>
  96.      * The roots are found using the <a href="https://en.wikipedia.org/wiki/Aberth_method">Aberth method</a>.
  97.      * The guess points for initializing search for degree n are fixed for degrees 1 and 2 and are
  98.      * selected from n-1 roots of rule n-1 (the two extreme roots are used, plus the n-1 intermediate
  99.      * points between all roots).
  100.      * </p>
  101.      * @param n number of roots to search for
  102.      * @param ratioEvaluator function evaluating the ratio Pₙ(x)/Pₙ'(x)
  103.      * @return sorted array of roots
  104.      */
  105.     protected T[] findRoots(final int n, final CalculusFieldUnivariateFunction<T> ratioEvaluator) {

  106.         final T[] roots  = MathArrays.buildArray(field, n);

  107.         // set up initial guess
  108.         if (n == 1) {
  109.             // arbitrary guess
  110.             roots[0] = field.getZero();
  111.         } else if (n == 2) {
  112.             // arbitrary guess
  113.             roots[0] = field.getOne().negate();
  114.             roots[1] = field.getOne();
  115.         } else {

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

  119.             // first guess at previous first root
  120.             roots[0] = previousPoints[0];

  121.             // intermediate guesses between previous roots
  122.             for (int i = 1; i < n - 1; ++i) {
  123.                 roots[i] = previousPoints[i - 1].add(previousPoints[i]).multiply(0.5);
  124.             }

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

  127.         }

  128.         // use Aberth method to find all roots simultaneously
  129.         final T[]         ratio       = MathArrays.buildArray(field, n);
  130.         final Incrementor incrementor = new Incrementor(1000);
  131.         double            tol;
  132.         double            maxOffset;
  133.         do {

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

  136.             // find the ratio P(xᵢ)/P'(xᵢ) for all current roots approximations
  137.             for (int i = 0; i < n; ++i) {
  138.                 ratio[i] = ratioEvaluator.value(roots[i]);
  139.             }

  140.             // move roots approximations all at once, using Aberth method
  141.             maxOffset = 0;
  142.             for (int i = 0; i < n; ++i) {
  143.                 T sum = field.getZero();
  144.                 for (int j = 0; j < n; ++j) {
  145.                     if (j != i) {
  146.                         sum = sum.add(roots[i].subtract(roots[j]).reciprocal());
  147.                     }
  148.                 }
  149.                 final T offset = ratio[i].divide(sum.multiply(ratio[i]).negate().add(1));
  150.                 maxOffset = FastMath.max(maxOffset, FastMath.abs(offset).getReal());
  151.                 roots[i] = roots[i].subtract(offset);
  152.             }

  153.             // we set tolerance to 1 ulp of the largest root
  154.             tol = 0;
  155.             for (final T r : roots) {
  156.                 tol = FastMath.max(tol, FastMath.ulp(r.getReal()));
  157.             }

  158.         } while (maxOffset > tol);

  159.         // sort the roots
  160.         Arrays.sort(roots, (r1, r2) -> Double.compare(r1.getReal(), r2.getReal()));

  161.         return roots;

  162.     }

  163.     /** Enforce symmetry of roots.
  164.      * @param roots roots to process in place
  165.      */
  166.     protected void enforceSymmetry(final T[] roots) {

  167.         final int n = roots.length;

  168.         // enforce symmetry
  169.         for (int i = 0; i < n / 2; ++i) {
  170.             final int idx = n - i - 1;
  171.             final T c = roots[i].subtract(roots[idx]).multiply(0.5);
  172.             roots[i]   = c;
  173.             roots[idx] = c.negate();
  174.         }

  175.         // If n is odd, 0 is a root.
  176.         // Note: as written, the test for oddness will work for negative
  177.         // integers too (although it is not necessary here), preventing
  178.         // a FindBugs warning.
  179.         if (n % 2 != 0) {
  180.             roots[n / 2] = field.getZero();
  181.         }

  182.     }

  183. }