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 protected 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 }