/** * Copyright (C) 2009 - present by OpenGamma Inc. and the OpenGamma group of companies * * Please see distribution for license. */ package com.opengamma.analytics.math.minimization; import static com.opengamma.analytics.math.matrix.MatrixAlgebraFactory.OG_ALGEBRA; import org.apache.commons.lang.Validate; import com.opengamma.analytics.math.MathException; import com.opengamma.analytics.math.differentiation.ScalarFieldFirstOrderDifferentiator; import com.opengamma.analytics.math.function.Function1D; import com.opengamma.analytics.math.matrix.DoubleMatrix1D; /** * This implementation of the conjugate gradient method is taken from <i>"An Introduction to the Conjugate Gradient Method Without the Agonizing Pain", Shewchuk</i>. */ public class ConjugateGradientVectorMinimizer implements MinimizerWithGradient<Function1D<DoubleMatrix1D, Double>, Function1D<DoubleMatrix1D, DoubleMatrix1D>, DoubleMatrix1D> { private static final double SMALL = 1e-25; private static final double DEFAULT_TOL = 1e-8; private static final int DEFAULT_MAX_STEPS = 100; private final double _relTol; private final double _absTol; private final int _maxIterations; private final LineSearch _lineSearch; /** * Constructs the object with default values for relative and absolute tolerance (10<sup>-8</sup>) and the number of iterations (100) * @param minimizer The minimizer, not null */ public ConjugateGradientVectorMinimizer(final ScalarMinimizer minimizer) { this(minimizer, DEFAULT_TOL, DEFAULT_MAX_STEPS); } /** * Constructs the object with equal relative and absolute values of tolerance. * @param minimizer The minimizer, not null * @param tolerance The tolerance, greater than 10<sup>-25</sup> and less than one * @param maxInterations The number of iterations, greater than one */ public ConjugateGradientVectorMinimizer(final ScalarMinimizer minimizer, final double tolerance, final int maxInterations) { this(minimizer, tolerance, tolerance, maxInterations); } /** * @param minimizer The minimizer, not null * @param relativeTolerance The relative tolerance, greater than zero and less than one * @param absoluteTolerance The absolute tolerance, greater than 10<sup>-25</sup> * @param maxIterations The number of iterations, greater than one */ public ConjugateGradientVectorMinimizer(final ScalarMinimizer minimizer, final double relativeTolerance, final double absoluteTolerance, final int maxIterations) { Validate.notNull(minimizer, "minimizer"); Validate.isTrue(relativeTolerance > 0.0 || relativeTolerance < 1.0, "relative tolerance must be greater than 0.0 and less than 1.0"); Validate.isTrue(absoluteTolerance > SMALL, "absolute tolerance must be greater than " + SMALL); Validate.isTrue(maxIterations >= 1, "Need at least one iteration"); _lineSearch = new LineSearch(minimizer); _relTol = relativeTolerance; _absTol = absoluteTolerance; _maxIterations = maxIterations; } /** * {@inheritDoc} */ @Override public DoubleMatrix1D minimize(final Function1D<DoubleMatrix1D, Double> function, final DoubleMatrix1D startPosition) { Validate.notNull(function, "function"); Validate.notNull(startPosition, "start position"); final ScalarFieldFirstOrderDifferentiator diff = new ScalarFieldFirstOrderDifferentiator(); final Function1D<DoubleMatrix1D, DoubleMatrix1D> grad = diff.differentiate(function); return minimize(function, grad, startPosition); } /** * {@inheritDoc} */ @Override public DoubleMatrix1D minimize(final Function1D<DoubleMatrix1D, Double> function, final Function1D<DoubleMatrix1D, DoubleMatrix1D> grad, final DoubleMatrix1D startPosition) { Validate.notNull(function, "function"); Validate.notNull(grad, "grad"); Validate.notNull(startPosition, "start position"); final int n = startPosition.getNumberOfElements(); DoubleMatrix1D x = startPosition; DoubleMatrix1D deltaX; DoubleMatrix1D g = grad.evaluate(x); DoubleMatrix1D d = (DoubleMatrix1D) OG_ALGEBRA.scale(g, -1.0); final double delta0 = -OG_ALGEBRA.getInnerProduct(g, d); double deltaOld; double deltaNew = delta0; double lambda = 0.0; int resetCount = 0; for (int count = 0; count < _maxIterations; count++, resetCount++) { lambda = _lineSearch.minimise(function, d, x); deltaX = (DoubleMatrix1D) OG_ALGEBRA.scale(d, lambda); x = (DoubleMatrix1D) OG_ALGEBRA.add(x, deltaX); final DoubleMatrix1D gNew = grad.evaluate(x); final double deltaMid = OG_ALGEBRA.getInnerProduct(g, gNew); g = gNew; deltaOld = deltaNew; deltaNew = OG_ALGEBRA.getInnerProduct(g, g); if (Math.sqrt(deltaNew) < _relTol * delta0 + _absTol // in practice may never get exactly zero gradient (especially if using finite difference to find it), so it shouldn't be the critical stopping criterion && OG_ALGEBRA.getNorm2(deltaX) < _relTol * OG_ALGEBRA.getNorm2(x) + _absTol) { boolean flag = true; for (int i = 0; i < n; i++) { if (Math.abs(deltaX.getEntry(i)) > _relTol * Math.abs(x.getEntry(i)) + _absTol) { flag = false; break; } } if (flag) { return x; } } final double beta = (deltaNew - deltaMid) / deltaOld; if (beta < 0 || resetCount == n) { d = (DoubleMatrix1D) OG_ALGEBRA.scale(g, -1.0); resetCount = 0; } else { d = (DoubleMatrix1D) OG_ALGEBRA.subtract(OG_ALGEBRA.scale(d, beta), g); final double sanity = OG_ALGEBRA.getInnerProduct(d, g); if (sanity > 0) { d = (DoubleMatrix1D) OG_ALGEBRA.scale(g, -1.0); resetCount = 0; } } } final double value = function.evaluate(x); throw new MathException("ConjugateGradient failed to converge after " + _maxIterations + " iterations, with a tolerance of " + _relTol + ". Final value: " + value + ". Final position reached was " + x.toString()); } }