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
23 package org.hipparchus.optim.nonlinear.scalar.gradient;
24
25 import org.hipparchus.exception.LocalizedCoreFormats;
26 import org.hipparchus.exception.MathIllegalStateException;
27 import org.hipparchus.exception.MathRuntimeException;
28 import org.hipparchus.optim.ConvergenceChecker;
29 import org.hipparchus.optim.OptimizationData;
30 import org.hipparchus.optim.PointValuePair;
31 import org.hipparchus.optim.nonlinear.scalar.GoalType;
32 import org.hipparchus.optim.nonlinear.scalar.GradientMultivariateOptimizer;
33 import org.hipparchus.optim.nonlinear.scalar.LineSearch;
34
35
36 /**
37 * Non-linear conjugate gradient optimizer.
38 * <br>
39 * This class supports both the Fletcher-Reeves and the Polak-Ribière
40 * update formulas for the conjugate search directions.
41 * It also supports optional preconditioning.
42 * <br>
43 * Constraints are not supported: the call to
44 * {@link #optimize(OptimizationData[]) optimize} will throw
45 * {@link MathRuntimeException} if bounds are passed to it.
46 *
47 */
48 public class NonLinearConjugateGradientOptimizer
49 extends GradientMultivariateOptimizer {
50 /** Update formula for the beta parameter. */
51 private final Formula updateFormula;
52 /** Preconditioner (may be null). */
53 private final Preconditioner preconditioner;
54 /** Line search algorithm. */
55 private final LineSearch line;
56
57 /**
58 * Available choices of update formulas for the updating the parameter
59 * that is used to compute the successive conjugate search directions.
60 * For non-linear conjugate gradients, there are
61 * two formulas:
62 * <ul>
63 * <li>Fletcher-Reeves formula</li>
64 * <li>Polak-Ribière formula</li>
65 * </ul>
66 *
67 * On the one hand, the Fletcher-Reeves formula is guaranteed to converge
68 * if the start point is close enough of the optimum whether the
69 * Polak-Ribière formula may not converge in rare cases. On the
70 * other hand, the Polak-Ribière formula is often faster when it
71 * does converge. Polak-Ribière is often used.
72 *
73 */
74 public enum Formula {
75 /** Fletcher-Reeves formula. */
76 FLETCHER_REEVES,
77 /** Polak-Ribière formula. */
78 POLAK_RIBIERE
79 }
80
81 /**
82 * Constructor with default tolerances for the line search (1e-8) and
83 * {@link IdentityPreconditioner preconditioner}.
84 *
85 * @param updateFormula formula to use for updating the β parameter,
86 * must be one of {@link Formula#FLETCHER_REEVES} or
87 * {@link Formula#POLAK_RIBIERE}.
88 * @param checker Convergence checker.
89 */
90 public NonLinearConjugateGradientOptimizer(final Formula updateFormula,
91 ConvergenceChecker<PointValuePair> checker) {
92 this(updateFormula,
93 checker,
94 1e-8,
95 1e-8,
96 1e-8,
97 new IdentityPreconditioner());
98 }
99
100 /**
101 * Constructor with default {@link IdentityPreconditioner preconditioner}.
102 *
103 * @param updateFormula formula to use for updating the β parameter,
104 * must be one of {@link Formula#FLETCHER_REEVES} or
105 * {@link Formula#POLAK_RIBIERE}.
106 * @param checker Convergence checker.
107 * @param relativeTolerance Relative threshold for line search.
108 * @param absoluteTolerance Absolute threshold for line search.
109 * @param initialBracketingRange Extent of the initial interval used to
110 * find an interval that brackets the optimum in order to perform the
111 * line search.
112 *
113 * @see LineSearch#LineSearch(org.hipparchus.optim.nonlinear.scalar.MultivariateOptimizer,double,double,double)
114 */
115 public NonLinearConjugateGradientOptimizer(final Formula updateFormula,
116 ConvergenceChecker<PointValuePair> checker,
117 double relativeTolerance,
118 double absoluteTolerance,
119 double initialBracketingRange) {
120 this(updateFormula,
121 checker,
122 relativeTolerance,
123 absoluteTolerance,
124 initialBracketingRange,
125 new IdentityPreconditioner());
126 }
127
128 /** Simple constructor.
129 * @param updateFormula formula to use for updating the β parameter,
130 * must be one of {@link Formula#FLETCHER_REEVES} or
131 * {@link Formula#POLAK_RIBIERE}.
132 * @param checker Convergence checker.
133 * @param preconditioner Preconditioner.
134 * @param relativeTolerance Relative threshold for line search.
135 * @param absoluteTolerance Absolute threshold for line search.
136 * @param initialBracketingRange Extent of the initial interval used to
137 * find an interval that brackets the optimum in order to perform the
138 * line search.
139 *
140 * @see LineSearch#LineSearch(org.hipparchus.optim.nonlinear.scalar.MultivariateOptimizer,double,double,double)
141 */
142 public NonLinearConjugateGradientOptimizer(final Formula updateFormula,
143 ConvergenceChecker<PointValuePair> checker,
144 double relativeTolerance,
145 double absoluteTolerance,
146 double initialBracketingRange,
147 final Preconditioner preconditioner) {
148 super(checker);
149
150 this.updateFormula = updateFormula;
151 this.preconditioner = preconditioner;
152 line = new LineSearch(this,
153 relativeTolerance,
154 absoluteTolerance,
155 initialBracketingRange);
156 }
157
158 /**
159 * {@inheritDoc}
160 */
161 @Override
162 public PointValuePair optimize(OptimizationData... optData)
163 throws MathIllegalStateException {
164 // Set up base class and perform computation.
165 return super.optimize(optData);
166 }
167
168 /** {@inheritDoc} */
169 @Override
170 protected PointValuePair doOptimize() {
171 final ConvergenceChecker<PointValuePair> checker = getConvergenceChecker();
172 final double[] point = getStartPoint();
173 final GoalType goal = getGoalType();
174 final int n = point.length;
175 double[] r = computeObjectiveGradient(point);
176 if (goal == GoalType.MINIMIZE) {
177 for (int i = 0; i < n; i++) {
178 r[i] = -r[i];
179 }
180 }
181
182 // Initial search direction.
183 double[] steepestDescent = preconditioner.precondition(point, r);
184 double[] searchDirection = steepestDescent.clone();
185
186 double delta = 0;
187 for (int i = 0; i < n; ++i) {
188 delta += r[i] * searchDirection[i];
189 }
190
191 PointValuePair current = null;
192 while (true) {
193 incrementIterationCount();
194
195 final double objective = computeObjectiveValue(point);
196 PointValuePair previous = current;
197 current = new PointValuePair(point, objective);
198 if (previous != null && checker.converged(getIterations(), previous, current)) {
199 // We have found an optimum.
200 return current;
201 }
202
203 final double step = line.search(point, searchDirection).getPoint();
204
205 // Validate new point.
206 for (int i = 0; i < point.length; ++i) {
207 point[i] += step * searchDirection[i];
208 }
209
210 r = computeObjectiveGradient(point);
211 if (goal == GoalType.MINIMIZE) {
212 for (int i = 0; i < n; ++i) {
213 r[i] = -r[i];
214 }
215 }
216
217 // Compute beta.
218 final double deltaOld = delta;
219 final double[] newSteepestDescent = preconditioner.precondition(point, r);
220 delta = 0;
221 for (int i = 0; i < n; ++i) {
222 delta += r[i] * newSteepestDescent[i];
223 }
224
225 final double beta;
226 switch (updateFormula) {
227 case FLETCHER_REEVES:
228 beta = delta / deltaOld;
229 break;
230 case POLAK_RIBIERE:
231 double deltaMid = 0;
232 for (int i = 0; i < r.length; ++i) {
233 deltaMid += r[i] * steepestDescent[i];
234 }
235 beta = (delta - deltaMid) / deltaOld;
236 break;
237 default:
238 // Should never happen.
239 throw MathRuntimeException.createInternalError();
240 }
241 steepestDescent = newSteepestDescent;
242
243 // Compute conjugate search direction.
244 if (getIterations() % n == 0 ||
245 beta < 0) {
246 // Break conjugation: reset search direction.
247 searchDirection = steepestDescent.clone();
248 } else {
249 // Compute new conjugate search direction.
250 for (int i = 0; i < n; ++i) {
251 searchDirection[i] = steepestDescent[i] + beta * searchDirection[i];
252 }
253 }
254 }
255 }
256
257 /**
258 * {@inheritDoc}
259 */
260 @Override
261 protected void parseOptimizationData(OptimizationData... optData) {
262 // Allow base class to register its own data.
263 super.parseOptimizationData(optData);
264
265 checkParameters();
266 }
267
268 /** Default identity preconditioner. */
269 public static class IdentityPreconditioner implements Preconditioner {
270
271 /** Empty constructor.
272 * <p>
273 * This constructor is not strictly necessary, but it prevents spurious
274 * javadoc warnings with JDK 18 and later.
275 * </p>
276 * @since 3.0
277 */
278 public IdentityPreconditioner() { // NOPMD - unnecessary constructor added intentionally to make javadoc happy
279 // nothing to do
280 }
281
282 /** {@inheritDoc} */
283 @Override
284 public double[] precondition(double[] variables, double[] r) {
285 return r.clone();
286 }
287
288 }
289
290 // Class is not used anymore (cf. MATH-1092). However, it might
291 // be interesting to create a class similar to "LineSearch", but
292 // that will take advantage that the model's gradient is available.
293 // /**
294 // * Internal class for line search.
295 // * <p>
296 // * The function represented by this class is the dot product of
297 // * the objective function gradient and the search direction. Its
298 // * value is zero when the gradient is orthogonal to the search
299 // * direction, i.e. when the objective function value is a local
300 // * extremum along the search direction.
301 // * </p>
302 // */
303 // private class LineSearchFunction implements UnivariateFunction {
304 // /** Current point. */
305 // private final double[] currentPoint;
306 // /** Search direction. */
307 // private final double[] searchDirection;
308
309 // /**
310 // * @param point Current point.
311 // * @param direction Search direction.
312 // */
313 // public LineSearchFunction(double[] point,
314 // double[] direction) {
315 // currentPoint = point.clone();
316 // searchDirection = direction.clone();
317 // }
318
319 // /** {@inheritDoc} */
320 // public double value(double x) {
321 // // current point in the search direction
322 // final double[] shiftedPoint = currentPoint.clone();
323 // for (int i = 0; i < shiftedPoint.length; ++i) {
324 // shiftedPoint[i] += x * searchDirection[i];
325 // }
326
327 // // gradient of the objective function
328 // final double[] gradient = computeObjectiveGradient(shiftedPoint);
329
330 // // dot product with the search direction
331 // double dotProduct = 0;
332 // for (int i = 0; i < gradient.length; ++i) {
333 // dotProduct += gradient[i] * searchDirection[i];
334 // }
335
336 // return dotProduct;
337 // }
338 // }
339
340 /**
341 * @throws MathRuntimeException if bounds were passed to the
342 * {@link #optimize(OptimizationData[]) optimize} method.
343 */
344 private void checkParameters() {
345 if (getLowerBound() != null ||
346 getUpperBound() != null) {
347 throw new MathRuntimeException(LocalizedCoreFormats.CONSTRAINT);
348 }
349 }
350 }