DerivativeLineMultiOptimizer.java
/*
* Copyright (C) 2012 Alberto Irurueta Carro (alberto@irurueta.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.irurueta.numerical.optimization;
import com.irurueta.numerical.DirectionalDerivativeEvaluator;
import com.irurueta.numerical.GradientFunctionEvaluatorListener;
import com.irurueta.numerical.LockedException;
import com.irurueta.numerical.MultiDimensionFunctionEvaluatorListener;
import com.irurueta.numerical.NotAvailableException;
import com.irurueta.numerical.NumericalException;
/**
* Class to find a minimum on a multidimensional function along a given line
* of input values. The difference between this abstract class and
* LineMultiOptimizer is that subclasses of this class will use the function
* gradient information. By using gradient information, typically convergence is
* faster, although, if gradient does not have a closed expression, then a
* GradientEstimator will be needed in the gradient listener provided to this
* class. Notice that a GradientEstimator might estimate gradients with a
* certain error, so depending on the function topology, LineMultiOptimizer
* subclasses might obtain greater accuracy than subclasses of this class.
*/
public abstract class DerivativeLineMultiOptimizer extends MultiOptimizer {
/**
* n-dimensional point containing a minimum in a given line.
*/
protected double[] p;
/**
* Direction to make the search.
*/
protected double[] xi;
/**
* Number of dimensions on function being evaluated.
*/
private int n;
/**
* Listener to evaluate the function's gradient. If the function's
* gradient is not know (e.g. does not have a closed expression), then
* a GradientEstimator might be used inside the listener implementation.
*/
protected GradientFunctionEvaluatorListener gradientListener;
/**
* Class in charge of evaluating a function through a given line.
*/
private DirectionalDerivativeEvaluator evaluator;
/**
* Internal optimizer to find a minimum of a function along a line of
* input values. Hence, input is converted to a single dimension using a
* DirectionalEvaluator.
*/
private DerivativeBrentSingleOptimizer dbrent;
/**
* Empty constructor.
*/
protected DerivativeLineMultiOptimizer() {
super();
p = xi = null;
n = 0;
gradientListener = null;
evaluator = null;
dbrent = null;
}
/**
* Constructor.
*
* @param listener Listener to evaluate a multi-dimension function.
* @param gradientListener Listener to evaluate the function's gradient.
* @param point Start point where algorithm will be started. Start point
* should be close to the local minimum to be found. Provided array must
* have a length equal to the number of dimensions of the function being
* evaluated, otherwise and exception will be raised when searching for the
* minimum.
* @param direction Direction to start looking for a minimum. Provided array
* must have the same length as the number of dimensions of the function
* being evaluated. Provided direction is considered as a vector pointing
* to the minimum to be found.
* @throws IllegalArgumentException Raised if provided point and direction
* don't have the same length.
*/
protected DerivativeLineMultiOptimizer(
final MultiDimensionFunctionEvaluatorListener listener,
final GradientFunctionEvaluatorListener gradientListener, final double[] point, final double[] direction) {
super(listener);
internalSetStartPointAndDirection(point, direction);
n = 0;
this.gradientListener = gradientListener;
evaluator = null;
dbrent = null;
}
/**
* Returns boolean indicating whether start point has already been provided
* and is ready for retrieval.
*
* @return True if available, false otherwise.
*/
public boolean isStartPointAvailable() {
return p != null;
}
/**
* Returns start point where algorithm will be started. Start point should
* be close to the local minimum to be found.
*
* @return Start point where algorithm will be started.
* @throws NotAvailableException Raised if start point has not yet been
* provided and is not available.
*/
public double[] getStartPoint() throws NotAvailableException {
if (!isStartPointAvailable()) {
throw new NotAvailableException();
}
return p;
}
/**
* Returns boolean indicating whether direction has already been provided
* and is ready for retrieval.
*
* @return True if available, false otherwise.
*/
public boolean isDirectionAvailable() {
return xi != null;
}
/**
* Returns direction to start looking for a minimum. Provided array must
* have the same length as the number of dimensions of the function being
* evaluated. Provided direction is considered as a vector pointing to the
* minimum to be found.
*
* @return Direction to start looking for a minimum.
* @throws NotAvailableException Raised if direction has not yet been
* provided and is not available.
*/
public double[] getDirection() throws NotAvailableException {
if (!isDirectionAvailable()) {
throw new NotAvailableException();
}
return xi;
}
/**
* Internal method to set start point and direction to start the search for
* a local minimum.
* This method does not check whether this instance is locked.
*
* @param point Start point where algorithm will be started. Start point
* should be close to the local minimum to be found. Provided array must
* have a length equal to the number of dimensions of the function being
* evaluated, otherwise and exception will be raised when searching for the
* minimum.
* @param direction Direction to start looking for a minimum. Provided array
* must have the same length as the number of dimensions of the function
* being evaluated. Provided direction is considered as a vector pointing
* to the minimum to be found.
* @throws IllegalArgumentException Raised if provided point and direction
* don't have the same length.
*/
private void internalSetStartPointAndDirection(final double[] point, final double[] direction) {
if (point.length != direction.length) {
throw new IllegalArgumentException();
}
p = point;
xi = direction;
}
/**
* Internal method to set start point and direction to start the search for
* a local minimum.
*
* @param point Start point where algorithm will be started. Start point
* should be close to the local minimum to be found. Provided array must
* have a length equal to the number of dimensions of the function being
* evaluated, otherwise and exception will be raised when searching for the
* minimum.
* @param direction Direction to start looking for a minimum. Provided array
* must have the same length as the number of dimensions of the function
* being evaluated. Provided direction is considered as a vector pointing
* to the minimum to be found.
* @throws LockedException Raised if this instance is locked.
* @throws IllegalArgumentException Raised if provided point and direction
* don't have the same length.
*/
public void setStartPointAndDirection(final double[] point, final double[] direction) throws LockedException {
if (isLocked()) {
throw new LockedException();
}
internalSetStartPointAndDirection(point, direction);
}
/**
* Returns boolean indicating whether this instance is considered to be
* ready to start the estimation of a minimum.
* This instance is considered to be ready once a listener, gradient
* listener, start point and direction are provided.
*
* @return True if this instance is ready, false otherwise.
*/
@Override
public boolean isReady() {
return isListenerAvailable() && isGradientListenerAvailable() && isStartPointAvailable()
&& isDirectionAvailable();
}
/**
* Returns gradient listener.
* The gradient listener is used to evaluate the function's gradient. If the
* function's gradient is not know (e.g. does not have a closed expression),
* then a GradientEstimator might be used inside the listener
* implementation.
*
* @return Gradient listener.
* @throws NotAvailableException Raised if gradient listener is not
* available for retrieval.
*/
public GradientFunctionEvaluatorListener getGradientListener() throws NotAvailableException {
if (!isGradientListenerAvailable()) {
throw new NotAvailableException();
}
return gradientListener;
}
/**
* Sets gradient listener.
* The gradient listener is used to evaluate the function's gradient. If the
* function's gradient is not know (e.g. does not have a closed expression),
* then a GradientEstimator might be used inside the listener
* implementation.
*
* @param gradientListener Gradient listener.
* @throws LockedException Raised if this instance is locked.
*/
public void setGradientListener(final GradientFunctionEvaluatorListener gradientListener) throws LockedException {
if (isLocked()) {
throw new LockedException();
}
this.gradientListener = gradientListener;
}
/**
* Returns boolean indicating whether the gradient listener has been
* provided and is available for retrieval.
*
* @return True if available, false otherwise.
*/
public boolean isGradientListenerAvailable() {
return gradientListener != null;
}
/**
* Searches for a minimum along a given line of input values.
* The line being searched is obtained by using a start point and direction.
*
* @return Returns function evaluation at minimum that has been found.
*/
@SuppressWarnings("Duplicates")
protected double linmin() {
final double ax;
final double xx;
final double linxmin;
n = p.length;
if (evaluator == null) {
//attempt to reuse evaluator
evaluator = new DirectionalDerivativeEvaluator(listener, gradientListener, p, xi);
}
if (evaluator.getListener() != listener) {
//update listener
evaluator.setListener(listener);
}
if (evaluator.getGradientListener() != gradientListener) {
//update gradient listener
evaluator.setGradientListener(gradientListener);
}
if (evaluator.getPoint() != p || evaluator.getDirection() != xi) {
evaluator.setPointAndDirection(p, xi);
}
ax = 0.0;
xx = 1.0;
try {
if (dbrent == null) {
// attempt to reuse brent single optimizer
dbrent = new DerivativeBrentSingleOptimizer(
point -> evaluator.evaluateAt(point),
point -> evaluator.differentiateAt(point),
BracketedSingleOptimizer.DEFAULT_MIN_EVAL_POINT,
BracketedSingleOptimizer.DEFAULT_MIDDLE_EVAL_POINT,
BracketedSingleOptimizer.DEFAULT_MAX_EVAL_POINT,
DerivativeBrentSingleOptimizer.DEFAULT_TOLERANCE);
}
dbrent.computeBracket(ax, xx);
dbrent.minimize();
linxmin = dbrent.getResult();
for (var j = 0; j < n; j++) {
xi[j] *= linxmin;
p[j] += xi[j];
}
return dbrent.getEvaluationAtResult();
} catch (final NumericalException t) {
//if minimization fails we assume that obtained result is the worst
//possible one
return Double.MAX_VALUE;
}
}
}