View Javadoc
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    *      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  
18  /*
19   * This is not the original file distributed by the Apache Software Foundation
20   * It has been modified by the Hipparchus project
21   */
22  package org.hipparchus.analysis.solvers;
23  
24  import org.hipparchus.analysis.FunctionUtils;
25  import org.hipparchus.analysis.MonitoredFunction;
26  import org.hipparchus.analysis.QuinticFunction;
27  import org.hipparchus.analysis.UnivariateFunction;
28  import org.hipparchus.analysis.differentiation.DSFactory;
29  import org.hipparchus.analysis.differentiation.UnivariateDifferentiableFunction;
30  import org.hipparchus.analysis.function.Constant;
31  import org.hipparchus.analysis.function.Inverse;
32  import org.hipparchus.analysis.function.Sin;
33  import org.hipparchus.analysis.function.Sqrt;
34  import org.hipparchus.exception.MathIllegalArgumentException;
35  import org.hipparchus.exception.MathIllegalStateException;
36  import org.hipparchus.util.FastMath;
37  import org.junit.jupiter.api.Test;
38  
39  import static org.junit.jupiter.api.Assertions.assertEquals;
40  import static org.junit.jupiter.api.Assertions.assertTrue;
41  import static org.junit.jupiter.api.Assertions.fail;
42  
43  /**
44   * Test case for {@link BrentSolver Brent} solver.
45   * Because Brent-Dekker is guaranteed to converge in less than the default
46   * maximum iteration count due to bisection fallback, it is quite hard to
47   * debug. I include measured iteration counts plus one in order to detect
48   * regressions. On average Brent-Dekker should use 4..5 iterations for the
49   * default absolute accuracy of 10E-8 for sinus and the quintic function around
50   * zero, and 5..10 iterations for the other zeros.
51   *
52   */
53  final class BrentSolverTest {
54      @Test
55      void testSinZero() {
56          // The sinus function is behaved well around the root at pi. The second
57          // order derivative is zero, which means linar approximating methods will
58          // still converge quadratically.
59          UnivariateFunction f = new Sin();
60          double result;
61          UnivariateSolver solver = new BrentSolver();
62          // Somewhat benign interval. The function is monotone.
63          result = solver.solve(100, f, 3, 4);
64          // System.out.println(
65          //    "Root: " + result + " Evaluations: " + solver.getEvaluations());
66          assertEquals(FastMath.PI, result, solver.getAbsoluteAccuracy());
67          assertTrue(solver.getEvaluations() <= 7);
68          // Larger and somewhat less benign interval. The function is grows first.
69          result = solver.solve(100, f, 1, 4);
70          // System.out.println(
71          //    "Root: " + result + " Evaluations: " + solver.getEvaluations());
72          assertEquals(FastMath.PI, result, solver.getAbsoluteAccuracy());
73          assertTrue(solver.getEvaluations() <= 8);
74      }
75  
76      @Test
77      void testQuinticZero() {
78          // The quintic function has zeros at 0, +-0.5 and +-1.
79          // Around the root of 0 the function is well behaved, with a second derivative
80          // of zero a 0.
81          // The other roots are less well to find, in particular the root at 1, because
82          // the function grows fast for x>1.
83          // The function has extrema (first derivative is zero) at 0.27195613 and 0.82221643,
84          // intervals containing these values are harder for the solvers.
85          UnivariateFunction f = new QuinticFunction();
86          double result;
87          // Brent-Dekker solver.
88          UnivariateSolver solver = new BrentSolver();
89          // Symmetric bracket around 0. Test whether solvers can handle hitting
90          // the root in the first iteration.
91          result = solver.solve(100, f, -0.2, 0.2);
92          //System.out.println(
93          //    "Root: " + result + " Evaluations: " + solver.getEvaluations());
94          assertEquals(0, result, solver.getAbsoluteAccuracy());
95          assertTrue(solver.getEvaluations() <= 3);
96          // 1 iterations on i586 JDK 1.4.1.
97          // Asymmetric bracket around 0, just for fun. Contains extremum.
98          result = solver.solve(100, f, -0.1, 0.3);
99          //System.out.println(
100         //    "Root: " + result + " Evaluations: " + solver.getEvaluations());
101         assertEquals(0, result, solver.getAbsoluteAccuracy());
102         // 5 iterations on i586 JDK 1.4.1.
103         assertTrue(solver.getEvaluations() <= 7);
104         // Large bracket around 0. Contains two extrema.
105         result = solver.solve(100, f, -0.3, 0.45);
106         //System.out.println(
107         //    "Root: " + result + " Evaluations: " + solver.getEvaluations());
108         assertEquals(0, result, solver.getAbsoluteAccuracy());
109         // 6 iterations on i586 JDK 1.4.1.
110         assertTrue(solver.getEvaluations() <= 8);
111         // Benign bracket around 0.5, function is monotonous.
112         result = solver.solve(100, f, 0.3, 0.7);
113         //System.out.println(
114         //    "Root: " + result + " Evaluations: " + solver.getEvaluations());
115         assertEquals(0.5, result, solver.getAbsoluteAccuracy());
116         // 6 iterations on i586 JDK 1.4.1.
117         assertTrue(solver.getEvaluations() <= 9);
118         // Less benign bracket around 0.5, contains one extremum.
119         result = solver.solve(100, f, 0.2, 0.6);
120         // System.out.println(
121         //    "Root: " + result + " Evaluations: " + solver.getEvaluations());
122         assertEquals(0.5, result, solver.getAbsoluteAccuracy());
123         assertTrue(solver.getEvaluations() <= 10);
124         // Large, less benign bracket around 0.5, contains both extrema.
125         result = solver.solve(100, f, 0.05, 0.95);
126         //System.out.println(
127         //    "Root: " + result + " Evaluations: " + solver.getEvaluations());
128         assertEquals(0.5, result, solver.getAbsoluteAccuracy());
129         assertTrue(solver.getEvaluations() <= 11);
130         // Relatively benign bracket around 1, function is monotonous. Fast growth for x>1
131         // is still a problem.
132         result = solver.solve(100, f, 0.85, 1.25);
133         //System.out.println(
134         //    "Root: " + result + " Evaluations: " + solver.getEvaluations());
135         assertEquals(1.0, result, solver.getAbsoluteAccuracy());
136         assertTrue(solver.getEvaluations() <= 11);
137         // Less benign bracket around 1 with extremum.
138         result = solver.solve(100, f, 0.8, 1.2);
139         //System.out.println(
140         //    "Root: " + result + " Evaluations: " + solver.getEvaluations());
141         assertEquals(1.0, result, solver.getAbsoluteAccuracy());
142         assertTrue(solver.getEvaluations() <= 11);
143         // Large bracket around 1. Monotonous.
144         result = solver.solve(100, f, 0.85, 1.75);
145         //System.out.println(
146         //    "Root: " + result + " Evaluations: " + solver.getEvaluations());
147         assertEquals(1.0, result, solver.getAbsoluteAccuracy());
148         assertTrue(solver.getEvaluations() <= 13);
149         // Large bracket around 1. Interval contains extremum.
150         result = solver.solve(100, f, 0.55, 1.45);
151         //System.out.println(
152         //    "Root: " + result + " Evaluations: " + solver.getEvaluations());
153         assertEquals(1.0, result, solver.getAbsoluteAccuracy());
154         assertTrue(solver.getEvaluations() <= 10);
155         // Very large bracket around 1 for testing fast growth behaviour.
156         result = solver.solve(100, f, 0.85, 5);
157         //System.out.println(
158        //     "Root: " + result + " Evaluations: " + solver.getEvaluations());
159         assertEquals(1.0, result, solver.getAbsoluteAccuracy());
160         assertTrue(solver.getEvaluations() <= 15);
161 
162         try {
163             result = solver.solve(5, f, 0.85, 5);
164             fail("Expected MathIllegalStateException");
165         } catch (MathIllegalStateException e) {
166             // Expected.
167         }
168     }
169 
170     @Test
171     void testRootEndpoints() {
172         UnivariateFunction f = new Sin();
173         BrentSolver solver = new BrentSolver();
174 
175         // endpoint is root
176         double result = solver.solve(100, f, FastMath.PI, 4);
177         assertEquals(FastMath.PI, result, solver.getAbsoluteAccuracy());
178 
179         result = solver.solve(100, f, 3, FastMath.PI);
180         assertEquals(FastMath.PI, result, solver.getAbsoluteAccuracy());
181 
182         result = solver.solve(100, f, FastMath.PI, 4, 3.5);
183         assertEquals(FastMath.PI, result, solver.getAbsoluteAccuracy());
184 
185         result = solver.solve(100, f, 3, FastMath.PI, 3.07);
186         assertEquals(FastMath.PI, result, solver.getAbsoluteAccuracy());
187     }
188 
189     @Test
190     void testBadEndpoints() {
191         UnivariateFunction f = new Sin();
192         BrentSolver solver = new BrentSolver();
193         try {  // bad interval
194             solver.solve(100, f, 1, -1);
195             fail("Expecting MathIllegalArgumentException - bad interval");
196         } catch (MathIllegalArgumentException ex) {
197             // expected
198         }
199         try {  // no bracket
200             solver.solve(100, f, 1, 1.5);
201             fail("Expecting MathIllegalArgumentException - non-bracketing");
202         } catch (MathIllegalArgumentException ex) {
203             // expected
204         }
205         try {  // no bracket
206             solver.solve(100, f, 1, 1.5, 1.2);
207             fail("Expecting MathIllegalArgumentException - non-bracketing");
208         } catch (MathIllegalArgumentException ex) {
209             // expected
210         }
211     }
212 
213     @Test
214     void testInitialGuess() {
215         MonitoredFunction f = new MonitoredFunction(new QuinticFunction());
216         BrentSolver solver = new BrentSolver();
217         double result;
218 
219         // no guess
220         result = solver.solve(100, f, 0.6, 7.0);
221         assertEquals(1.0, result, solver.getAbsoluteAccuracy());
222         int referenceCallsCount = f.getCallsCount();
223         assertTrue(referenceCallsCount >= 13);
224 
225         // invalid guess (it *is* a root, but outside of the range)
226         try {
227           result = solver.solve(100, f, 0.6, 7.0, 0.0);
228           fail("a MathIllegalArgumentException was expected");
229         } catch (MathIllegalArgumentException iae) {
230             // expected behaviour
231         }
232 
233         // bad guess
234         f.setCallsCount(0);
235         result = solver.solve(100, f, 0.6, 7.0, 0.61);
236         assertEquals(1.0, result, solver.getAbsoluteAccuracy());
237         assertTrue(f.getCallsCount() > referenceCallsCount);
238 
239         // good guess
240         f.setCallsCount(0);
241         result = solver.solve(100, f, 0.6, 7.0, 0.999999);
242         assertEquals(1.0, result, solver.getAbsoluteAccuracy());
243         assertTrue(f.getCallsCount() < referenceCallsCount);
244 
245         // perfect guess
246         f.setCallsCount(0);
247         result = solver.solve(100, f, 0.6, 7.0, 1.0);
248         assertEquals(1.0, result, solver.getAbsoluteAccuracy());
249         assertEquals(1, solver.getEvaluations());
250         assertEquals(1, f.getCallsCount());
251     }
252 
253     @Test
254     void testMath832() {
255         final UnivariateFunction f = new UnivariateFunction() {
256                 private final UnivariateDifferentiableFunction sqrt = new Sqrt();
257                 private final UnivariateDifferentiableFunction inv = new Inverse();
258                 private final UnivariateDifferentiableFunction func
259                     = FunctionUtils.add(FunctionUtils.multiply(new Constant(1e2), sqrt),
260                                         FunctionUtils.multiply(new Constant(1e6), inv),
261                                         FunctionUtils.multiply(new Constant(1e4),
262                                                                FunctionUtils.compose(inv, sqrt)));
263                 private final DSFactory factory = new DSFactory(1, 1);
264 
265                 public double value(double x) {
266                     return func.value(factory.variable(0, x)).getPartialDerivative(1);
267                 }
268 
269             };
270 
271         BrentSolver solver = new BrentSolver();
272         final double result = solver.solve(99, f, 1, 1e30, 1 + 1e-10);
273         assertEquals(804.93558250, result, 1e-8);
274     }
275 }