// // 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; } } }