diff --git a/src/Numerics/Numerics.csproj b/src/Numerics/Numerics.csproj index cadd0bf1..163c728e 100644 --- a/src/Numerics/Numerics.csproj +++ b/src/Numerics/Numerics.csproj @@ -198,10 +198,12 @@ + + diff --git a/src/Numerics/Optimization/ConjugateGradientMinimizer.cs b/src/Numerics/Optimization/ConjugateGradientMinimizer.cs index c16e2173..4fe75e83 100644 --- a/src/Numerics/Optimization/ConjugateGradientMinimizer.cs +++ b/src/Numerics/Optimization/ConjugateGradientMinimizer.cs @@ -21,11 +21,12 @@ namespace MathNet.Numerics.Optimization public MinimizationOutput FindMinimum(IObjectiveFunction objective, Vector initial_guess) { if (!objective.GradientSupported) - throw new Exception("Gradient not supported in objective function, but required for ConjugateGradient minimization."); + throw new IncompatibleObjectiveException("Gradient not supported in objective function, but required for ConjugateGradient minimization."); + + objective = new ObjectiveChecker(objective, this.ValidateObjective, this.ValidateGradient, null); IEvaluation initial_eval = objective.Evaluate(initial_guess); - var gradient = initial_eval.Gradient; - this.ValidateGradient(gradient, initial_guess); + var gradient = initial_eval.Gradient; // Check that we're not already done if (this.ExitCriteriaSatisfied(initial_guess, gradient)) @@ -42,11 +43,20 @@ namespace MathNet.Numerics.Optimization steepest_direction = -gradient; search_direction = steepest_direction; double initial_step_size = 100 * this.GradientTolerance / (gradient * gradient); - var result = line_searcher.FindConformingStep(objective, initial_eval, search_direction, initial_step_size); - candidate_point = result.FunctionInfoAtMinimum; - this.ValidateGradient(candidate_point.Gradient, candidate_point.Point); + LineSearchOutput result; + try + { + result = line_searcher.FindConformingStep(objective, initial_eval, search_direction, initial_step_size); + } + catch (Exception e) + { + throw new InnerOptimizationException("Line search failed.", e); + } + + candidate_point = result.FunctionInfoAtMinimum; double step_size = (candidate_point.Point - initial_guess).Norm(2.0); + // Subsequent steps int iterations = 1; int total_line_search_steps = result.Iterations; @@ -73,8 +83,15 @@ namespace MathNet.Numerics.Optimization search_direction = steepest_direction; steepest_descent_resets += 1; } - - result = line_searcher.FindConformingStep(objective, candidate_point, search_direction, step_size); + + try + { + result = line_searcher.FindConformingStep(objective, candidate_point, search_direction, step_size); + } + catch (Exception e) + { + throw new InnerOptimizationException("Line search failed.", e); + } no_line_search_iterations += result.Iterations == 0 ? 1 : 0; total_line_search_steps += result.Iterations; @@ -85,6 +102,9 @@ namespace MathNet.Numerics.Optimization iterations += 1; } + if (iterations == this.MaximumIterations) + throw new MaximumIterationsException(String.Format("Maximum iterations ({0}) reached.", this.MaximumIterations)); + return new MinimizationWithLineSearchOutput(candidate_point, iterations, total_line_search_steps, no_line_search_iterations); } @@ -98,14 +118,14 @@ namespace MathNet.Numerics.Optimization foreach (var x in gradient) { if (Double.IsNaN(x) || Double.IsInfinity(x)) - throw new Exception("Non-finite gradient returned."); + throw new EvaluationException("Non-finite gradient returned."); } } private void ValidateObjective(double objective, Vector input) { if (Double.IsNaN(objective) || Double.IsInfinity(objective)) - throw new Exception("Non-finite objective function returned."); + throw new EvaluationException("Non-finite objective function returned."); } } } diff --git a/src/Numerics/Optimization/Exceptions.cs b/src/Numerics/Optimization/Exceptions.cs new file mode 100644 index 00000000..fcb0f758 --- /dev/null +++ b/src/Numerics/Optimization/Exceptions.cs @@ -0,0 +1,48 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace MathNet.Numerics.Optimization +{ + public class OptimizationException : Exception + { + public OptimizationException(string message) + : base(message) {} + + public OptimizationException(string message, Exception inner_exception) + : base(message, inner_exception) { } + } + + public class MaximumIterationsException : OptimizationException + { + public MaximumIterationsException(string message) + : base(message) {} + } + + public class EvaluationException : OptimizationException + { + public EvaluationException(string message) + : base(message) {} + + public EvaluationException(string message, Exception inner_exception) + : base(message, inner_exception) { } + } + + public class InnerOptimizationException : OptimizationException + { + public InnerOptimizationException(string message) + : base(message) {} + + public InnerOptimizationException(string message, Exception inner_exception) + : base(message, inner_exception) { } + } + + public class IncompatibleObjectiveException : OptimizationException + { + public IncompatibleObjectiveException(string message) + : base(message) {} + } + + +} diff --git a/src/Numerics/Optimization/ObjectiveChecker.cs b/src/Numerics/Optimization/ObjectiveChecker.cs new file mode 100644 index 00000000..04c14495 --- /dev/null +++ b/src/Numerics/Optimization/ObjectiveChecker.cs @@ -0,0 +1,132 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using MathNet.Numerics.LinearAlgebra; + +namespace MathNet.Numerics.Optimization +{ + public class CheckedEvaluation : IEvaluation + { + private ObjectiveChecker Checker; + private IEvaluation InnerEvaluation; + private bool ValueChecked; + private bool GradientChecked; + private bool HessianChecked; + + public CheckedEvaluation(ObjectiveChecker checker, IEvaluation evaluation) + { + this.Checker = checker; + this.InnerEvaluation = evaluation; + } + + public Vector Point + { + get { return this.InnerEvaluation.Point; } + } + + public double Value + { + get + { + + if (!this.ValueChecked) + { + double tmp; + try + { + tmp = this.InnerEvaluation.Value; + } + catch (Exception e) + { + throw new EvaluationException("Objective function evaluation failed.", e); + } + this.Checker.ValueChecker(tmp,this.InnerEvaluation.Point); + } + return this.InnerEvaluation.Value; + } + } + + public Vector Gradient + { + get + { + + if (!this.GradientChecked) + { + Vector tmp; + try + { + tmp = this.InnerEvaluation.Gradient; + } + catch (Exception e) + { + throw new EvaluationException("Objective gradient evaluation failed.", e); + } + this.Checker.GradientChecker(tmp, this.InnerEvaluation.Point); + } + return this.InnerEvaluation.Gradient; + } + } + + public Matrix Hessian + { + get + { + + if (!this.HessianChecked) + { + Matrix tmp; + try + { + tmp = this.InnerEvaluation.Hessian; + } + catch (Exception e) + { + throw new EvaluationException("Objective hessian evaluation failed.", e); + } + this.Checker.HessianChecker(tmp, this.InnerEvaluation.Point); + } + return this.InnerEvaluation.Hessian; + } + } + } + + public class ObjectiveChecker : IObjectiveFunction + { + public IObjectiveFunction InnerObjective { get; private set; } + public Action> ValueChecker { get; private set; } + public Action, Vector> GradientChecker { get; private set; } + public Action, Vector> HessianChecker { get; private set; } + + public ObjectiveChecker(IObjectiveFunction objective, Action> value_checker, Action, Vector> gradient_checker, Action, Vector> hessian_checker) + { + this.InnerObjective = objective; + this.ValueChecker = value_checker; + this.GradientChecker = gradient_checker; + this.HessianChecker = hessian_checker; + } + + public bool GradientSupported + { + get { return this.InnerObjective.GradientSupported; } + } + + public bool HessianSupported + { + get { return this.InnerObjective.HessianSupported; } + } + + public IEvaluation Evaluate(Vector point) + { + try + { + return new CheckedEvaluation(this, this.InnerObjective.Evaluate(point)); + } + catch (Exception e) + { + throw new EvaluationException("Objective evaluation failed.", e); + } + } + } +} diff --git a/src/Numerics/Optimization/WeakWolfeLineSearch.cs b/src/Numerics/Optimization/WeakWolfeLineSearch.cs index 7286a79c..0690a100 100644 --- a/src/Numerics/Optimization/WeakWolfeLineSearch.cs +++ b/src/Numerics/Optimization/WeakWolfeLineSearch.cs @@ -57,7 +57,7 @@ namespace MathNet.Numerics.Optimization } if (ii == this.MaximumIterations) - throw new Exception("Line search failed with max iterations. Function is likely unbounded in search direction."); + throw new MaximumIterationsException(String.Format("Maximum iterations ({0}) reached. Function may be unbounded in search direction.",this.MaximumIterations)); else return new LineSearchOutput(candidate_eval, ii, step); }