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.math4.legacy.analysis.solvers;
18
19
20 import org.apache.commons.math4.legacy.analysis.UnivariateFunction;
21 import org.apache.commons.math4.legacy.exception.MathInternalError;
22 import org.apache.commons.math4.legacy.exception.NoBracketingException;
23 import org.apache.commons.math4.legacy.exception.NumberIsTooLargeException;
24 import org.apache.commons.math4.legacy.exception.NumberIsTooSmallException;
25 import org.apache.commons.math4.legacy.exception.TooManyEvaluationsException;
26 import org.apache.commons.math4.core.jdkmath.JdkMath;
27 import org.apache.commons.numbers.core.Precision;
28
29 /**
30 * This class implements a modification of the <a
31 * href="http://mathworld.wolfram.com/BrentsMethod.html"> Brent algorithm</a>.
32 * <p>
33 * The changes with respect to the original Brent algorithm are:
34 * <ul>
35 * <li>the returned value is chosen in the current interval according
36 * to user specified {@link AllowedSolution},</li>
37 * <li>the maximal order for the invert polynomial root search is
38 * user-specified instead of being invert quadratic only</li>
39 * </ul><p>
40 * The given interval must bracket the root.</p>
41 *
42 */
43 public class BracketingNthOrderBrentSolver
44 extends AbstractUnivariateSolver
45 implements BracketedUnivariateSolver<UnivariateFunction> {
46
47 /** Default absolute accuracy. */
48 private static final double DEFAULT_ABSOLUTE_ACCURACY = 1e-6;
49
50 /** Default maximal order. */
51 private static final int DEFAULT_MAXIMAL_ORDER = 5;
52
53 /** Maximal aging triggering an attempt to balance the bracketing interval. */
54 private static final int MAXIMAL_AGING = 2;
55
56 /** Reduction factor for attempts to balance the bracketing interval. */
57 private static final double REDUCTION_FACTOR = 1.0 / 16.0;
58
59 /** Maximal order. */
60 private final int maximalOrder;
61
62 /** The kinds of solutions that the algorithm may accept. */
63 private AllowedSolution allowed;
64
65 /**
66 * Construct a solver with default accuracy and maximal order (1e-6 and 5 respectively).
67 */
68 public BracketingNthOrderBrentSolver() {
69 this(DEFAULT_ABSOLUTE_ACCURACY, DEFAULT_MAXIMAL_ORDER);
70 }
71
72 /**
73 * Construct a solver.
74 *
75 * @param absoluteAccuracy Absolute accuracy.
76 * @param maximalOrder maximal order.
77 * @exception NumberIsTooSmallException if maximal order is lower than 2
78 */
79 public BracketingNthOrderBrentSolver(final double absoluteAccuracy,
80 final int maximalOrder)
81 throws NumberIsTooSmallException {
82 super(absoluteAccuracy);
83 if (maximalOrder < 2) {
84 throw new NumberIsTooSmallException(maximalOrder, 2, true);
85 }
86 this.maximalOrder = maximalOrder;
87 this.allowed = AllowedSolution.ANY_SIDE;
88 }
89
90 /**
91 * Construct a solver.
92 *
93 * @param relativeAccuracy Relative accuracy.
94 * @param absoluteAccuracy Absolute accuracy.
95 * @param maximalOrder maximal order.
96 * @exception NumberIsTooSmallException if maximal order is lower than 2
97 */
98 public BracketingNthOrderBrentSolver(final double relativeAccuracy,
99 final double absoluteAccuracy,
100 final int maximalOrder)
101 throws NumberIsTooSmallException {
102 super(relativeAccuracy, absoluteAccuracy);
103 if (maximalOrder < 2) {
104 throw new NumberIsTooSmallException(maximalOrder, 2, true);
105 }
106 this.maximalOrder = maximalOrder;
107 this.allowed = AllowedSolution.ANY_SIDE;
108 }
109
110 /**
111 * Construct a solver.
112 *
113 * @param relativeAccuracy Relative accuracy.
114 * @param absoluteAccuracy Absolute accuracy.
115 * @param functionValueAccuracy Function value accuracy.
116 * @param maximalOrder maximal order.
117 * @exception NumberIsTooSmallException if maximal order is lower than 2
118 */
119 public BracketingNthOrderBrentSolver(final double relativeAccuracy,
120 final double absoluteAccuracy,
121 final double functionValueAccuracy,
122 final int maximalOrder)
123 throws NumberIsTooSmallException {
124 super(relativeAccuracy, absoluteAccuracy, functionValueAccuracy);
125 if (maximalOrder < 2) {
126 throw new NumberIsTooSmallException(maximalOrder, 2, true);
127 }
128 this.maximalOrder = maximalOrder;
129 this.allowed = AllowedSolution.ANY_SIDE;
130 }
131
132 /** Get the maximal order.
133 * @return maximal order
134 */
135 public int getMaximalOrder() {
136 return maximalOrder;
137 }
138
139 /**
140 * {@inheritDoc}
141 */
142 @Override
143 protected double doSolve()
144 throws TooManyEvaluationsException,
145 NumberIsTooLargeException,
146 NoBracketingException {
147 // prepare arrays with the first points
148 final double[] x = new double[maximalOrder + 1];
149 final double[] y = new double[maximalOrder + 1];
150 x[0] = getMin();
151 x[1] = getStartValue();
152 x[2] = getMax();
153 verifySequence(x[0], x[1], x[2]);
154
155 // evaluate initial guess
156 y[1] = computeObjectiveValue(x[1]);
157 if (Precision.equals(y[1], 0.0, 1)) {
158 // return the initial guess if it is a perfect root.
159 return x[1];
160 }
161
162 // evaluate first endpoint
163 y[0] = computeObjectiveValue(x[0]);
164 if (Precision.equals(y[0], 0.0, 1)) {
165 // return the first endpoint if it is a perfect root.
166 return x[0];
167 }
168
169 int nbPoints;
170 int signChangeIndex;
171 if (y[0] * y[1] < 0) {
172
173 // reduce interval if it brackets the root
174 nbPoints = 2;
175 signChangeIndex = 1;
176 } else {
177
178 // evaluate second endpoint
179 y[2] = computeObjectiveValue(x[2]);
180 if (Precision.equals(y[2], 0.0, 1)) {
181 // return the second endpoint if it is a perfect root.
182 return x[2];
183 }
184
185 if (y[1] * y[2] < 0) {
186 // use all computed point as a start sampling array for solving
187 nbPoints = 3;
188 signChangeIndex = 2;
189 } else {
190 throw new NoBracketingException(x[0], x[2], y[0], y[2]);
191 }
192 }
193
194 // prepare a work array for inverse polynomial interpolation
195 final double[] tmpX = new double[x.length];
196
197 // current tightest bracketing of the root
198 double xA = x[signChangeIndex - 1];
199 double yA = y[signChangeIndex - 1];
200 double absYA = JdkMath.abs(yA);
201 int agingA = 0;
202 double xB = x[signChangeIndex];
203 double yB = y[signChangeIndex];
204 double absYB = JdkMath.abs(yB);
205 int agingB = 0;
206
207 // search loop
208 while (true) {
209
210 // check convergence of bracketing interval
211 final double xTol = getAbsoluteAccuracy() +
212 getRelativeAccuracy() * JdkMath.max(JdkMath.abs(xA), JdkMath.abs(xB));
213 if (xB - xA <= xTol || JdkMath.max(absYA, absYB) < getFunctionValueAccuracy()) {
214 switch (allowed) {
215 case ANY_SIDE :
216 return absYA < absYB ? xA : xB;
217 case LEFT_SIDE :
218 return xA;
219 case RIGHT_SIDE :
220 return xB;
221 case BELOW_SIDE :
222 return (yA <= 0) ? xA : xB;
223 case ABOVE_SIDE :
224 return (yA < 0) ? xB : xA;
225 default :
226 // this should never happen
227 throw new MathInternalError();
228 }
229 }
230
231 // target for the next evaluation point
232 double targetY;
233 if (agingA >= MAXIMAL_AGING) {
234 // we keep updating the high bracket, try to compensate this
235 final int p = agingA - MAXIMAL_AGING;
236 final double weightA = (1 << p) - 1;
237 final double weightB = p + 1;
238 targetY = (weightA * yA - weightB * REDUCTION_FACTOR * yB) / (weightA + weightB);
239 } else if (agingB >= MAXIMAL_AGING) {
240 // we keep updating the low bracket, try to compensate this
241 final int p = agingB - MAXIMAL_AGING;
242 final double weightA = p + 1;
243 final double weightB = (1 << p) - 1;
244 targetY = (weightB * yB - weightA * REDUCTION_FACTOR * yA) / (weightA + weightB);
245 } else {
246 // bracketing is balanced, try to find the root itself
247 targetY = 0;
248 }
249
250 // make a few attempts to guess a root,
251 double nextX;
252 int start = 0;
253 int end = nbPoints;
254 do {
255
256 // guess a value for current target, using inverse polynomial interpolation
257 System.arraycopy(x, start, tmpX, start, end - start);
258 nextX = guessX(targetY, tmpX, y, start, end);
259
260 if (!(nextX > xA && nextX < xB)) {
261 // the guessed root is not strictly inside of the tightest bracketing interval
262
263 // the guessed root is either not strictly inside the interval or it
264 // is a NaN (which occurs when some sampling points share the same y)
265 // we try again with a lower interpolation order
266 if (signChangeIndex - start >= end - signChangeIndex) {
267 // we have more points before the sign change, drop the lowest point
268 ++start;
269 } else {
270 // we have more points after sign change, drop the highest point
271 --end;
272 }
273
274 // we need to do one more attempt
275 nextX = Double.NaN;
276 }
277 } while (Double.isNaN(nextX) && end - start > 1);
278
279 if (Double.isNaN(nextX)) {
280 // fall back to bisection
281 nextX = xA + 0.5 * (xB - xA);
282 start = signChangeIndex - 1;
283 end = signChangeIndex;
284 }
285
286 // evaluate the function at the guessed root
287 final double nextY = computeObjectiveValue(nextX);
288 if (Precision.equals(nextY, 0.0, 1)) {
289 // we have found an exact root, since it is not an approximation
290 // we don't need to bother about the allowed solutions setting
291 return nextX;
292 }
293
294 if (nbPoints > 2 && end - start != nbPoints) {
295
296 // we have been forced to ignore some points to keep bracketing,
297 // they are probably too far from the root, drop them from now on
298 nbPoints = end - start;
299 System.arraycopy(x, start, x, 0, nbPoints);
300 System.arraycopy(y, start, y, 0, nbPoints);
301 signChangeIndex -= start;
302 } else if (nbPoints == x.length) {
303
304 // we have to drop one point in order to insert the new one
305 nbPoints--;
306
307 // keep the tightest bracketing interval as centered as possible
308 if (signChangeIndex >= (x.length + 1) / 2) {
309 // we drop the lowest point, we have to shift the arrays and the index
310 System.arraycopy(x, 1, x, 0, nbPoints);
311 System.arraycopy(y, 1, y, 0, nbPoints);
312 --signChangeIndex;
313 }
314 }
315
316 // insert the last computed point
317 //(by construction, we know it lies inside the tightest bracketing interval)
318 System.arraycopy(x, signChangeIndex, x, signChangeIndex + 1, nbPoints - signChangeIndex);
319 x[signChangeIndex] = nextX;
320 System.arraycopy(y, signChangeIndex, y, signChangeIndex + 1, nbPoints - signChangeIndex);
321 y[signChangeIndex] = nextY;
322 ++nbPoints;
323
324 // update the bracketing interval
325 if (nextY * yA <= 0) {
326 // the sign change occurs before the inserted point
327 xB = nextX;
328 yB = nextY;
329 absYB = JdkMath.abs(yB);
330 ++agingA;
331 agingB = 0;
332 } else {
333 // the sign change occurs after the inserted point
334 xA = nextX;
335 yA = nextY;
336 absYA = JdkMath.abs(yA);
337 agingA = 0;
338 ++agingB;
339
340 // update the sign change index
341 signChangeIndex++;
342 }
343 }
344 }
345
346 /** Guess an x value by n<sup>th</sup> order inverse polynomial interpolation.
347 * <p>
348 * The x value is guessed by evaluating polynomial Q(y) at y = targetY, where Q
349 * is built such that for all considered points (x<sub>i</sub>, y<sub>i</sub>),
350 * Q(y<sub>i</sub>) = x<sub>i</sub>.
351 * </p>
352 * @param targetY target value for y
353 * @param x reference points abscissas for interpolation,
354 * note that this array <em>is</em> modified during computation
355 * @param y reference points ordinates for interpolation
356 * @param start start index of the points to consider (inclusive)
357 * @param end end index of the points to consider (exclusive)
358 * @return guessed root (will be a NaN if two points share the same y)
359 */
360 private double guessX(final double targetY, final double[] x, final double[] y,
361 final int start, final int end) {
362
363 // compute Q Newton coefficients by divided differences
364 for (int i = start; i < end - 1; ++i) {
365 final int delta = i + 1 - start;
366 for (int j = end - 1; j > i; --j) {
367 x[j] = (x[j] - x[j-1]) / (y[j] - y[j - delta]);
368 }
369 }
370
371 // evaluate Q(targetY)
372 double x0 = 0;
373 for (int j = end - 1; j >= start; --j) {
374 x0 = x[j] + x0 * (targetY - y[j]);
375 }
376
377 return x0;
378 }
379
380 /** {@inheritDoc} */
381 @Override
382 public double solve(int maxEval, UnivariateFunction f, double min,
383 double max, AllowedSolution allowedSolution)
384 throws TooManyEvaluationsException,
385 NumberIsTooLargeException,
386 NoBracketingException {
387 this.allowed = allowedSolution;
388 return super.solve(maxEval, f, min, max);
389 }
390
391 /** {@inheritDoc} */
392 @Override
393 public double solve(int maxEval, UnivariateFunction f, double min,
394 double max, double startValue,
395 AllowedSolution allowedSolution)
396 throws TooManyEvaluationsException,
397 NumberIsTooLargeException,
398 NoBracketingException {
399 this.allowed = allowedSolution;
400 return super.solve(maxEval, f, min, max, startValue);
401 }
402 }