//
// Math.NET Numerics, part of the Math.NET Project
// http://numerics.mathdotnet.com
// http://github.com/mathnet/mathnet-numerics
//
// Copyright (c) 2009-2017 Math.NET
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
//
using System;
using System.Collections.Generic;
using System.Linq;
using IStation.Numerics.LinearAlgebra;
using IStation.Numerics.Optimization.LineSearch;
namespace IStation.Numerics.Optimization
{
///
/// Limited Memory version of Broyden–Fletcher–Goldfarb–Shanno (BFGS) algorithm
///
public class LimitedMemoryBfgsMinimizer : MinimizerBase, IUnconstrainedMinimizer
{
public int Memory { get; set; }
///
///
/// Creates L-BFGS minimizer
///
/// Numbers of gradients and steps to store.
public LimitedMemoryBfgsMinimizer(double gradientTolerance, double parameterTolerance, double functionProgressTolerance, int memory, int maximumIterations=1000) : base(gradientTolerance, parameterTolerance, functionProgressTolerance, maximumIterations)
{
Memory = memory;
}
///
/// Find the minimum of the objective function given lower and upper bounds
///
/// The objective function, must support a gradient
/// The initial guess
/// The MinimizationResult which contains the minimum and the ExitCondition
public MinimizationResult FindMinimum(IObjectiveFunction objective, Vector initialGuess)
{
if (!objective.IsGradientSupported)
throw new IncompatibleObjectiveException("Gradient not supported in objective function, but required for L-BFGS minimization.");
objective.EvaluateAt(initialGuess);
ValidateGradientAndObjective(objective);
// Check that we're not already done
ExitCondition currentExitCondition = ExitCriteriaSatisfied(objective, null, 0);
if (currentExitCondition != ExitCondition.None)
return new MinimizationResult(objective, 0, currentExitCondition);
// Set up line search algorithm
var lineSearcher = new WeakWolfeLineSearch(1e-4, 0.9, Math.Max(ParameterTolerance, 1e-10), 1000);
// First step
var lineSearchDirection = -objective.Gradient;
var stepSize = 100 * GradientTolerance / (lineSearchDirection * lineSearchDirection);
var previousPoint = objective;
LineSearchResult lineSearchResult;
try
{
lineSearchResult = lineSearcher.FindConformingStep(objective, lineSearchDirection, stepSize);
}
catch (OptimizationException e)
{
throw new InnerOptimizationException("Line search failed.", e);
}
catch (ArgumentException e)
{
throw new InnerOptimizationException("Line search failed.", e);
}
var candidate = lineSearchResult.FunctionInfoAtMinimum;
ValidateGradientAndObjective(candidate);
var gradient = candidate.Gradient;
var step = candidate.Point - initialGuess;
var yk = candidate.Gradient - previousPoint.Gradient;
var ykhistory = new List>() {yk};
var skhistory = new List>() {step};
var rhokhistory = new List() {1.0/yk.DotProduct(step)};
// Subsequent steps
int iterations = 1;
int totalLineSearchSteps = lineSearchResult.Iterations;
int iterationsWithNontrivialLineSearch = lineSearchResult.Iterations > 0 ? 0 : 1;
previousPoint = candidate;
while (iterations++ < MaximumIterations && previousPoint.Gradient.Norm(2) >= GradientTolerance)
{
lineSearchDirection = -ApplyLbfgsUpdate(previousPoint, ykhistory, skhistory, rhokhistory);
var directionalDerivative = previousPoint.Gradient.DotProduct(lineSearchDirection);
if (directionalDerivative > 0)
throw new InnerOptimizationException("Direction is not a descent direction.");
try
{
lineSearchResult = lineSearcher.FindConformingStep(previousPoint, lineSearchDirection, 1.0);
}
catch (OptimizationException e)
{
throw new InnerOptimizationException("Line search failed.", e);
}
catch (ArgumentException e)
{
throw new InnerOptimizationException("Line search failed.", e);
}
iterationsWithNontrivialLineSearch += lineSearchResult.Iterations > 0 ? 1 : 0;
totalLineSearchSteps += lineSearchResult.Iterations;
candidate = lineSearchResult.FunctionInfoAtMinimum;
currentExitCondition = ExitCriteriaSatisfied(candidate, previousPoint, iterations);
if (currentExitCondition != ExitCondition.None)
break;
step = candidate.Point - previousPoint.Point;
yk = candidate.Gradient - previousPoint.Gradient;
ykhistory.Add(yk);
skhistory.Add(step);
rhokhistory.Add(1.0/yk.DotProduct(step));
previousPoint = candidate;
if (ykhistory.Count > Memory)
{
ykhistory.RemoveAt(0);
skhistory.RemoveAt(0);
rhokhistory.RemoveAt(0);
}
}
if (iterations == MaximumIterations && currentExitCondition == ExitCondition.None)
throw new MaximumIterationsException(FormattableString.Invariant($"Maximum iterations ({MaximumIterations}) reached."));
return new MinimizationWithLineSearchResult(candidate, iterations, ExitCondition.AbsoluteGradient, totalLineSearchSteps, iterationsWithNontrivialLineSearch);
}
private Vector ApplyLbfgsUpdate(IObjectiveFunction previousPoint, List> ykhistory, List> skhistory, List rhokhistory)
{
var q = previousPoint.Gradient.Clone();
var alphas = new Stack();
for (int k = ykhistory.Count - 1; k >= 0; k--)
{
var alpha = rhokhistory[k]*q.DotProduct(skhistory[k]);
alphas.Push(alpha);
q -= alpha*ykhistory[k];
}
var yk = ykhistory.Last();
var sk = skhistory.Last();
q *= yk.DotProduct(sk)/yk.DotProduct(yk);
for (int k = 0; k < ykhistory.Count; k++)
{
var beta = rhokhistory[k]*ykhistory[k].DotProduct(q);
q += skhistory[k]*(alphas.Pop() - beta);
}
return q;
}
}
}