View Javadoc
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  
19  import java.util.Arrays;
20  import java.util.SortedMap;
21  import java.util.TreeMap;
22  
23  import org.hipparchus.CalculusFieldElement;
24  import org.hipparchus.Field;
25  import org.hipparchus.analysis.CalculusFieldUnivariateFunction;
26  import org.hipparchus.exception.LocalizedCoreFormats;
27  import org.hipparchus.exception.MathIllegalArgumentException;
28  import org.hipparchus.util.FastMath;
29  import org.hipparchus.util.Incrementor;
30  import org.hipparchus.util.MathArrays;
31  import org.hipparchus.util.Pair;
32  
33  /**
34   * Base class for rules that determines the integration nodes and their
35   * weights.
36   * Subclasses must implement the {@link #computeRule(int) computeRule} method.
37   *
38   * @param <T> Type of the number used to represent the points and weights of
39   * the quadrature rules.
40   * @since 2.0
41   */
42  public abstract class FieldAbstractRuleFactory<T extends CalculusFieldElement<T>> implements FieldRuleFactory<T> {
43  
44      /** Field to which rule coefficients belong. */
45      private final Field<T> field;
46  
47      /** List of points and weights, indexed by the order of the rule. */
48      private final SortedMap<Integer, Pair<T[], T[]>> pointsAndWeights;
49  
50      /** Simple constructor
51       * @param field field to which rule coefficients belong
52       */
53      public FieldAbstractRuleFactory(final Field<T> field) {
54          this.field            = field;
55          this.pointsAndWeights = new TreeMap<>();
56      }
57  
58      /** Get the field to which rule coefficients belong.
59       * @return field to which rule coefficients belong
60       */
61      public Field<T> getField() {
62          return field;
63      }
64  
65      /** {@inheritDoc} */
66      @Override
67      public Pair<T[], T[]> getRule(int numberOfPoints)
68          throws MathIllegalArgumentException {
69  
70          if (numberOfPoints <= 0) {
71              throw new MathIllegalArgumentException(LocalizedCoreFormats.NUMBER_OF_POINTS,
72                                                     numberOfPoints);
73          }
74          if (numberOfPoints > 1000) {
75              throw new MathIllegalArgumentException(LocalizedCoreFormats.NUMBER_TOO_LARGE,
76                                                     numberOfPoints, 1000);
77          }
78  
79          Pair<T[], T[]> rule;
80          synchronized (pointsAndWeights) {
81              // Try to obtain the rule from the cache.
82              rule = pointsAndWeights.get(numberOfPoints);
83  
84              if (rule == null) {
85                  // Rule not computed yet.
86  
87                  // Compute the rule.
88                  rule = computeRule(numberOfPoints);
89  
90                  // Cache it.
91                  pointsAndWeights.put(numberOfPoints, rule);
92              }
93          }
94  
95          // Return a copy.
96          return new Pair<>(rule.getFirst().clone(), rule.getSecond().clone());
97  
98      }
99  
100     /**
101      * Computes the rule for the given order.
102      *
103      * @param numberOfPoints Order of the rule to be computed.
104      * @return the computed rule.
105      * @throws MathIllegalArgumentException if the elements of the pair do not
106      * have the same length.
107      */
108     protected abstract Pair<T[], T[]> computeRule(int numberOfPoints)
109         throws MathIllegalArgumentException;
110 
111     /** Computes roots of the associated orthogonal polynomials.
112      * <p>
113      * The roots are found using the <a href="https://en.wikipedia.org/wiki/Aberth_method">Aberth method</a>.
114      * The guess points for initializing search for degree n are fixed for degrees 1 and 2 and are
115      * selected from n-1 roots of rule n-1 (the two extreme roots are used, plus the n-1 intermediate
116      * points between all roots).
117      * </p>
118      * @param n number of roots to search for
119      * @param ratioEvaluator function evaluating the ratio Pₙ(x)/Pₙ'(x)
120      * @return sorted array of roots
121      */
122     protected T[] findRoots(final int n, final CalculusFieldUnivariateFunction<T> ratioEvaluator) {
123 
124         final T[] roots  = MathArrays.buildArray(field, n);
125 
126         // set up initial guess
127         if (n == 1) {
128             // arbitrary guess
129             roots[0] = field.getZero();
130         } else if (n == 2) {
131             // arbitrary guess
132             roots[0] = field.getOne().negate();
133             roots[1] = field.getOne();
134         } else {
135 
136             // get roots from previous rule.
137             // If it has not been computed yet it will trigger a recursive call
138             final T[] previousPoints = getRule(n - 1).getFirst();
139 
140             // first guess at previous first root
141             roots[0] = previousPoints[0];
142 
143             // intermediate guesses between previous roots
144             for (int i = 1; i < n - 1; ++i) {
145                 roots[i] = previousPoints[i - 1].add(previousPoints[i]).multiply(0.5);
146             }
147 
148             // last guess at previous last root
149             roots[n - 1] = previousPoints[n - 2];
150 
151         }
152 
153         // use Aberth method to find all roots simultaneously
154         final T[]         ratio       = MathArrays.buildArray(field, n);
155         final Incrementor incrementor = new Incrementor(1000);
156         double            tol;
157         double            maxOffset;
158         do {
159 
160             // safety check that triggers an exception if too much iterations are made
161             incrementor.increment();
162 
163             // find the ratio P(xᵢ)/P'(xᵢ) for all current roots approximations
164             for (int i = 0; i < n; ++i) {
165                 ratio[i] = ratioEvaluator.value(roots[i]);
166             }
167 
168             // move roots approximations all at once, using Aberth method
169             maxOffset = 0;
170             for (int i = 0; i < n; ++i) {
171                 T sum = field.getZero();
172                 for (int j = 0; j < n; ++j) {
173                     if (j != i) {
174                         sum = sum.add(roots[i].subtract(roots[j]).reciprocal());
175                     }
176                 }
177                 final T offset = ratio[i].divide(sum.multiply(ratio[i]).negate().add(1));
178                 maxOffset = FastMath.max(maxOffset, FastMath.abs(offset).getReal());
179                 roots[i] = roots[i].subtract(offset);
180             }
181 
182             // we set tolerance to 1 ulp of the largest root
183             tol = 0;
184             for (final T r : roots) {
185                 tol = FastMath.max(tol, FastMath.ulp(r.getReal()));
186             }
187 
188         } while (maxOffset > tol);
189 
190         // sort the roots
191         Arrays.sort(roots, (r1, r2) -> Double.compare(r1.getReal(), r2.getReal()));
192 
193         return roots;
194 
195     }
196 
197     /** Enforce symmetry of roots.
198      * @param roots roots to process in place
199      */
200     protected void enforceSymmetry(final T[] roots) {
201 
202         final int n = roots.length;
203 
204         // enforce symmetry
205         for (int i = 0; i < n / 2; ++i) {
206             final int idx = n - i - 1;
207             final T c = roots[i].subtract(roots[idx]).multiply(0.5);
208             roots[i]   = c;
209             roots[idx] = c.negate();
210         }
211 
212         // If n is odd, 0 is a root.
213         // Note: as written, the test for oddness will work for negative
214         // integers too (although it is not necessary here), preventing
215         // a FindBugs warning.
216         if (n % 2 != 0) {
217             roots[n / 2] = field.getZero();
218         }
219 
220     }
221 
222 }