diff --git a/src/Numerics.Tests/OptimizationTests/BfgsMinimizerTests.cs b/src/Numerics.Tests/OptimizationTests/BfgsMinimizerTests.cs index 4f4b3e22..fd9fbd76 100644 --- a/src/Numerics.Tests/OptimizationTests/BfgsMinimizerTests.cs +++ b/src/Numerics.Tests/OptimizationTests/BfgsMinimizerTests.cs @@ -36,6 +36,8 @@ using MathNet.Numerics.UnitTests.OptimizationTests.TestFunctions; using System.Collections.Generic; using System.Collections; using NUnit.Framework.Interfaces; +using MathNet.Numerics.LinearAlgebra; +using MathNet.Numerics.Optimization.ObjectiveFunctions; namespace MathNet.Numerics.UnitTests.OptimizationTests { @@ -155,5 +157,44 @@ namespace MathNet.Numerics.UnitTests.OptimizationTests var success = (abs_min <= 1 && abs_err < 1e-3) || (abs_min > 1 && rel_err < 1e-3); Assert.That(success, "Minimal function value is not as expected."); } + + + [Test] + public void BfgsMinimizer_MinimizesProperlyWhenConstrainedInOneVariable() + { + // Test comes from github issue 510 + // https://github.com/mathnet/mathnet-numerics/issues/510 + Func, double> function = (Vector vectorArg) => + { + double x = vectorArg[0]; + double y = -1.0 * Math.Exp(-x * x); + return y; + }; + + double gradientTolerance = 1e-10; + double parameterTolerance = 1e-10; + double functionProgressTolerance = 1e-10; + int maxIterations = 1000; + + var initialGuess = new DenseVector(new[] { -1.0 }); + + // Bad Bound + var lowerBound = new DenseVector(new[] { -2.0 }); + var upperBound = new DenseVector(new[] { 2.0 }); + + + var objective = ObjectiveFunction.Value(function); + var objectiveWithGradient = new ForwardDifferenceGradientObjectiveFunction(objective, lowerBound, upperBound); + var algorithm = new BfgsBMinimizer(gradientTolerance, parameterTolerance, functionProgressTolerance, maxIterations); + var result = algorithm.FindMinimum(objectiveWithGradient, lowerBound, upperBound, initialGuess); + + var resultVector = result.MinimizingPoint; + double xResult = resultVector[0]; + + // (actual minimum at zero) + var abs_err = Math.Abs(xResult); + var success = abs_err < 1e-6; + Assert.That(success, "Minimal function value is not as expected."); + } } } diff --git a/src/Numerics/Optimization/QuadraticGradientProjectionSearch.cs b/src/Numerics/Optimization/QuadraticGradientProjectionSearch.cs index 9e203697..ecdab210 100644 --- a/src/Numerics/Optimization/QuadraticGradientProjectionSearch.cs +++ b/src/Numerics/Optimization/QuadraticGradientProjectionSearch.cs @@ -80,6 +80,11 @@ namespace MathNet.Numerics.Optimization // while minimum of the last quadratic piece observed is beyond the interval searched while (true) { + if (jj + 1 >= orderedBreakpoint.Count - 1) + { + isFixed[isFixed.Count - 1] = true; + return new GradientProjectionResult(x + maxS * d, lowerBound.Count, isFixed); + } // update data to the beginning of the interval we're searching jj += 1; x = x + d * maxS; @@ -104,11 +109,6 @@ namespace MathNet.Numerics.Optimization if (sMin < maxS) return new GradientProjectionResult(x + sMin * d, fixedCount, isFixed); - else if (jj + 1 >= orderedBreakpoint.Count - 1) - { - isFixed[isFixed.Count - 1] = true; - return new GradientProjectionResult(x + maxS * d, lowerBound.Count, isFixed); - } } }