diff --git a/src/Numerics/Numerics.csproj b/src/Numerics/Numerics.csproj index 9c7227b9..580bbe0d 100644 --- a/src/Numerics/Numerics.csproj +++ b/src/Numerics/Numerics.csproj @@ -118,6 +118,12 @@ + + + + + + diff --git a/src/Numerics/Optimization/ObjectiveFunction.cs b/src/Numerics/Optimization/ObjectiveFunction.cs new file mode 100644 index 00000000..e3a1d577 --- /dev/null +++ b/src/Numerics/Optimization/ObjectiveFunction.cs @@ -0,0 +1,65 @@ +using System; +using MathNet.Numerics.LinearAlgebra; +using MathNet.Numerics.Optimization.ObjectiveFunctions; + +namespace MathNet.Numerics.Optimization +{ + public static class ObjectiveFunction + { + /// + /// Objective function where neither Gradient nor Hessian is available. + /// + public static IObjectiveFunction Value(Func, double> function) + { + return new ValueObjectiveFunction(function); + } + + /// + /// Objective function where the Gradient is available. Greedy evaluation. + /// + public static IObjectiveFunction Gradient(Func, Tuple>> function) + { + return new GradientObjectiveFunction(function); + } + + /// + /// Objective function where the Gradient is available. Lazy evaluation. + /// + public static IObjectiveFunction Gradient(Func, double> function, Func, Vector> gradient) + { + return new LazyObjectiveFunction(function, gradient: gradient); + } + + /// + /// Objective function where the Hessian is available. Greedy evaluation. + /// + public static IObjectiveFunction Hessian(Func, Tuple>> function) + { + return new HessianObjectiveFunction(function); + } + + /// + /// Objective function where the Hessian is available. Lazy evaluation. + /// + public static IObjectiveFunction Hessian(Func, double> function, Func, Matrix> hessian) + { + return new LazyObjectiveFunction(function, hessian: hessian); + } + + /// + /// Objective function where both Gradient and Hessian are available. Greedy evaluation. + /// + public static IObjectiveFunction GradientHessian(Func, Tuple, Matrix>> function) + { + return new GradientHessianObjectiveFunction(function); + } + + /// + /// Objective function where both Gradient and Hessian are available. Lazy evaluation. + /// + public static IObjectiveFunction GradientHessian(Func, double> function, Func, Vector> gradient, Func, Matrix> hessian) + { + return new LazyObjectiveFunction(function, gradient: gradient, hessian: hessian); + } + } +} diff --git a/src/Numerics/Optimization/ObjectiveFunctions/GradientHessianObjectiveFunction.cs b/src/Numerics/Optimization/ObjectiveFunctions/GradientHessianObjectiveFunction.cs new file mode 100644 index 00000000..24d2a914 --- /dev/null +++ b/src/Numerics/Optimization/ObjectiveFunctions/GradientHessianObjectiveFunction.cs @@ -0,0 +1,57 @@ +using System; +using MathNet.Numerics.LinearAlgebra; + +namespace MathNet.Numerics.Optimization.ObjectiveFunctions +{ + internal class GradientHessianObjectiveFunction : IObjectiveFunction + { + readonly Func, Tuple, Matrix>> _function; + + public GradientHessianObjectiveFunction(Func, Tuple, Matrix>> function) + { + _function = function; + } + + public IObjectiveFunction CreateNew() + { + return new GradientHessianObjectiveFunction(_function); + } + + public IObjectiveFunction Fork() + { + // no need to deep-clone values since they are replaced on evaluation + return new GradientHessianObjectiveFunction(_function) + { + Point = Point, + Value = Value, + Gradient = Gradient, + Hessian = Hessian + }; + } + + public bool IsGradientSupported + { + get { return true; } + } + + public bool IsHessianSupported + { + get { return true; } + } + + public void EvaluateAt(Vector point) + { + Point = point; + + var result = _function(point); + Value = result.Item1; + Gradient = result.Item2; + Hessian = result.Item3; + } + + public Vector Point { get; private set; } + public double Value { get; private set; } + public Vector Gradient { get; private set; } + public Matrix Hessian { get; private set; } + } +} \ No newline at end of file diff --git a/src/Numerics/Optimization/ObjectiveFunctions/GradientObjectiveFunction.cs b/src/Numerics/Optimization/ObjectiveFunctions/GradientObjectiveFunction.cs new file mode 100644 index 00000000..8d967f01 --- /dev/null +++ b/src/Numerics/Optimization/ObjectiveFunctions/GradientObjectiveFunction.cs @@ -0,0 +1,59 @@ +using System; +using MathNet.Numerics.LinearAlgebra; + +namespace MathNet.Numerics.Optimization.ObjectiveFunctions +{ + internal class GradientObjectiveFunction : IObjectiveFunction + { + readonly Func, Tuple>> _function; + + public GradientObjectiveFunction(Func, Tuple>> function) + { + _function = function; + } + + public IObjectiveFunction CreateNew() + { + return new GradientObjectiveFunction(_function); + } + + public IObjectiveFunction Fork() + { + // no need to deep-clone values since they are replaced on evaluation + return new GradientObjectiveFunction(_function) + { + Point = Point, + Value = Value, + Gradient = Gradient + }; + } + + public bool IsGradientSupported + { + get { return true; } + } + + public bool IsHessianSupported + { + get { return false; } + } + + public void EvaluateAt(Vector point) + { + Point = point; + + var result = _function(point); + Value = result.Item1; + Gradient = result.Item2; + } + + public Vector Point { get; private set; } + public double Value { get; private set; } + public Vector Gradient { get; private set; } + + public Matrix Hessian + { + get { throw new NotSupportedException(); } + } + } +} \ No newline at end of file diff --git a/src/Numerics/Optimization/ObjectiveFunctions/HessianObjectiveFunction.cs b/src/Numerics/Optimization/ObjectiveFunctions/HessianObjectiveFunction.cs new file mode 100644 index 00000000..54215980 --- /dev/null +++ b/src/Numerics/Optimization/ObjectiveFunctions/HessianObjectiveFunction.cs @@ -0,0 +1,59 @@ +using System; +using MathNet.Numerics.LinearAlgebra; + +namespace MathNet.Numerics.Optimization.ObjectiveFunctions +{ + internal class HessianObjectiveFunction : IObjectiveFunction + { + readonly Func, Tuple>> _function; + + public HessianObjectiveFunction(Func, Tuple>> function) + { + _function = function; + } + + public IObjectiveFunction CreateNew() + { + return new HessianObjectiveFunction(_function); + } + + public IObjectiveFunction Fork() + { + // no need to deep-clone values since they are replaced on evaluation + return new HessianObjectiveFunction(_function) + { + Point = Point, + Value = Value, + Hessian = Hessian + }; + } + + public bool IsGradientSupported + { + get { return false; } + } + + public bool IsHessianSupported + { + get { return true; } + } + + public void EvaluateAt(Vector point) + { + Point = point; + + var result = _function(point); + Value = result.Item1; + Hessian = result.Item2; + } + + public Vector Point { get; private set; } + public double Value { get; private set; } + public Matrix Hessian { get; private set; } + + public Vector Gradient + { + get { throw new NotSupportedException(); } + } + } +} \ No newline at end of file diff --git a/src/Numerics/Optimization/ObjectiveFunctions/LazyObjectiveFunction.cs b/src/Numerics/Optimization/ObjectiveFunctions/LazyObjectiveFunction.cs new file mode 100644 index 00000000..8b629cfd --- /dev/null +++ b/src/Numerics/Optimization/ObjectiveFunctions/LazyObjectiveFunction.cs @@ -0,0 +1,112 @@ +using System; +using MathNet.Numerics.LinearAlgebra; + +namespace MathNet.Numerics.Optimization.ObjectiveFunctions +{ + internal class LazyObjectiveFunction : IObjectiveFunction + { + readonly Func, double> _function; + readonly Func, Vector> _gradient; + readonly Func, Matrix> _hessian; + + Vector _point; + + bool _hasFunctionValue; + double _functionValue; + + bool _hasGradientValue; + Vector _gradientValue; + + bool _hasHessianValue; + Matrix _hessianValue; + + public LazyObjectiveFunction(Func, double> function, Func, Vector> gradient = null, Func, Matrix> hessian = null) + { + _function = function; + _gradient = gradient; + _hessian = hessian; + + IsGradientSupported = gradient != null; + IsHessianSupported = hessian != null; + } + + public IObjectiveFunction CreateNew() + { + return new LazyObjectiveFunction(_function, _gradient, _hessian); + } + + public IObjectiveFunction Fork() + { + // no need to deep-clone values since they are replaced on evaluation + return new LazyObjectiveFunction(_function, _gradient, _hessian) + { + _point = _point, + _hasFunctionValue = _hasFunctionValue, + _functionValue = _functionValue, + _hasGradientValue = _hasGradientValue, + _gradientValue = _gradientValue, + _hasHessianValue = _hasHessianValue, + _hessianValue = _hessianValue + }; + } + + public bool IsGradientSupported { get; private set; } + public bool IsHessianSupported { get; private set; } + + public void EvaluateAt(Vector point) + { + _point = point; + _hasFunctionValue = false; + _hasGradientValue = false; + _hasHessianValue = false; + + // don't keep references unnecessarily + _gradientValue = null; + _hessianValue = null; + } + + public Vector Point + { + get { return _point; } + } + + public double Value + { + get + { + if (!_hasFunctionValue) + { + _functionValue = _function(_point); + _hasFunctionValue = true; + } + return _functionValue; + } + } + + public Vector Gradient + { + get + { + if (!_hasGradientValue) + { + _gradientValue = _gradient(_point); + _hasGradientValue = true; + } + return _gradientValue; + } + } + + public Matrix Hessian + { + get + { + if (!_hasHessianValue) + { + _hessianValue = _hessian(_point); + _hasHessianValue = true; + } + return _hessianValue; + } + } + } +} \ No newline at end of file diff --git a/src/Numerics/Optimization/ObjectiveFunctions/ValueObjectiveFunction.cs b/src/Numerics/Optimization/ObjectiveFunctions/ValueObjectiveFunction.cs new file mode 100644 index 00000000..19e75d74 --- /dev/null +++ b/src/Numerics/Optimization/ObjectiveFunctions/ValueObjectiveFunction.cs @@ -0,0 +1,59 @@ +using System; +using MathNet.Numerics.LinearAlgebra; + +namespace MathNet.Numerics.Optimization.ObjectiveFunctions +{ + internal class ValueObjectiveFunction : IObjectiveFunction + { + readonly Func, double> _function; + + public ValueObjectiveFunction(Func, double> function) + { + _function = function; + } + + public IObjectiveFunction CreateNew() + { + return new ValueObjectiveFunction(_function); + } + + public IObjectiveFunction Fork() + { + // no need to deep-clone values since they are replaced on evaluation + return new ValueObjectiveFunction(_function) + { + Point = Point, + Value = Value, + }; + } + + public bool IsGradientSupported + { + get { return false; } + } + + public bool IsHessianSupported + { + get { return false; } + } + + public void EvaluateAt(Vector point) + { + Point = point; + Value = _function(point); + } + + public Vector Point { get; private set; } + public double Value { get; private set; } + + public Matrix Hessian + { + get { throw new NotSupportedException(); } + } + + public Vector Gradient + { + get { throw new NotSupportedException(); } + } + } +} \ No newline at end of file diff --git a/src/UnitTests/OptimizationTests/TestNewtonMinimizer.cs b/src/UnitTests/OptimizationTests/TestNewtonMinimizer.cs index d7f910d7..ad8bb32f 100644 --- a/src/UnitTests/OptimizationTests/TestNewtonMinimizer.cs +++ b/src/UnitTests/OptimizationTests/TestNewtonMinimizer.cs @@ -34,12 +34,10 @@ namespace MathNet.Numerics.UnitTests.OptimizationTests [TestFixture] public class TestNewtonMinimizer { - [Test] public void FindMinimum_Rosenbrock_Easy() { - var obj = new RosenbrockObjectiveFunction(); - + var obj = ObjectiveFunction.GradientHessian(RosenbrockFunction.Value, RosenbrockFunction.Gradient, RosenbrockFunction.Hessian); var solver = new NewtonMinimizer(1e-5, 1000); var result = solver.FindMinimum(obj, new DenseVector(new[] { 1.2, 1.2 })); @@ -50,7 +48,7 @@ namespace MathNet.Numerics.UnitTests.OptimizationTests [Test] public void FindMinimum_Rosenbrock_Hard() { - var obj = new RosenbrockObjectiveFunction(); + var obj = ObjectiveFunction.GradientHessian(point => Tuple.Create(RosenbrockFunction.Value(point), RosenbrockFunction.Gradient(point), RosenbrockFunction.Hessian(point))); var solver = new NewtonMinimizer(1e-5, 1000); var result = solver.FindMinimum(obj, new DenseVector(new[] { -1.2, 1.0 }));