diff --git a/Build.targets b/Build.targets index 3133625..b288527 100644 --- a/Build.targets +++ b/Build.targets @@ -1,53 +1,56 @@  - b5fd6643c1cc36fa95297c6a5cda4140573006d5 + 3b5475ca350bd5e50b58fd2d8642051ee0c19916 $(MSBuildThisFileDirectory)minuit2.net/obj/root $(MSBuildThisFileDirectory)/minuit2.net/obj/gen - + - + - + + WorkingDirectory="$(RootGitPath)"/> + WorkingDirectory="$(RootGitPath)"/> + WorkingDirectory="$(RootGitPath)"/> + WorkingDirectory="$(RootGitPath)"/> - + - + x64 Win32 @@ -60,22 +63,24 @@ - + cmake --build "$(CmakeBuildDir)" --config Release + WorkingDirectory="$(MSBuildThisFileDirectory)"/> - + @@ -83,13 +88,16 @@ - + - + - + PreserveNewest PreserveNewest false @@ -97,11 +105,19 @@ - + - - - + + + - + x64;x86;ARM64 @@ -125,7 +142,8 @@ - + - + - + HessianFor(IReadOnlyList parameter } public override IReadOnlyList HessianDiagonalFor(IReadOnlyList parameterValues) - { - return modelHessianDiagonal == null - ? HessianDiagonalFromModelHessianFor(parameterValues) - : HessianDiagonalFromModelHessianDiagonalFor(parameterValues); - } - - private double[] HessianDiagonalFromModelHessianFor(IReadOnlyList parameterValues) - { - var hessianDiagonal = new double[Parameters.Count]; - for (var i = 0; i < x.Count; i++) - { - var yError = yErrorForIndex(i); - var r = (y[i] - model(x[i], parameterValues)) / yError; - var g = modelGradient!(x[i], parameterValues); - var h = modelHessian!(x[i], parameterValues); - for (var j = 0; j < Parameters.Count; j++) - { - var jj = j * (Parameters.Count + 1); - hessianDiagonal[j] -= 2 / yError * (r * h[jj] - g[j] * g[j] / yError); - } - } - - return hessianDiagonal; - } - - private double[] HessianDiagonalFromModelHessianDiagonalFor(IReadOnlyList parameterValues) { var hessianDiagonal = new double[Parameters.Count]; for (var i = 0; i < x.Count; i++) diff --git a/minuit2.net/CostFunctions/LeastSquaresBase.cs b/minuit2.net/CostFunctions/LeastSquaresBase.cs index c962321..4cf395f 100644 --- a/minuit2.net/CostFunctions/LeastSquaresBase.cs +++ b/minuit2.net/CostFunctions/LeastSquaresBase.cs @@ -12,7 +12,7 @@ internal abstract class LeastSquaresBase( public IReadOnlyList Parameters { get; } = parameters; public bool HasGradient { get; } = hasModelGradient; public bool HasHessian { get; } = hasModelGradient && hasModelHessian; - public bool HasHessianDiagonal { get; } = hasModelGradient && (hasModelHessianDiagonal || hasModelHessian); + public bool HasHessianDiagonal { get; } = hasModelGradient && hasModelHessianDiagonal; public double ErrorDefinition { get; } = errorDefinition; // For least squares fits, an error definition of 1 corresponds to 1-sigma parameter errors diff --git a/test/minuit2.net.Benchmarks/SurfaceBiosensorBindingKineticsMigradBenchmarks.cs b/test/minuit2.net.Benchmarks/SurfaceBiosensorBindingKineticsMigradBenchmarks.cs index 7952ae9..319c7a5 100644 --- a/test/minuit2.net.Benchmarks/SurfaceBiosensorBindingKineticsMigradBenchmarks.cs +++ b/test/minuit2.net.Benchmarks/SurfaceBiosensorBindingKineticsMigradBenchmarks.cs @@ -12,9 +12,7 @@ namespace minuit2.net.Benchmarks; [Orderer(SummaryOrderPolicy.Method)] public class SurfaceBiosensorBindingKineticsMigradBenchmarks { - [Params(WithoutDerivatives, WithGradient)] - // Benchmark all configurations once Hessian-indexing bug is fixed in Minuit2 - // (see https://github.com/root-project/root/pull/20936) + [Params(WithoutDerivatives, WithGradient, WithGradientAndHessian, WithGradientHessianAndHessianDiagonal)] public DerivativeConfiguration DerivativeConfiguration; [Params(Fast, Balanced, Rigorous, VeryRigorous)] diff --git a/test/minuit2.net.UnitTests/Any_gradient_based_minimizer.spec.cs b/test/minuit2.net.UnitTests/Any_gradient_based_minimizer.spec.cs index 6c82e75..670518e 100644 --- a/test/minuit2.net.UnitTests/Any_gradient_based_minimizer.spec.cs +++ b/test/minuit2.net.UnitTests/Any_gradient_based_minimizer.spec.cs @@ -193,15 +193,10 @@ public void when_minimizing_a_cost_function_with_an_analytical_hessian_that_thro action.Should().ThrowExactly(); } - [Test, - Description("This test ensures the Hessian (diagonal) is regularized during minimizer seeding to prevent the " + - "minimizer from initially stepping away from the minimum (and eventually failing).")] - public void when_minimizing_a_cost_function_with_an_analytical_hessian_that_is_not_positive_definite_for_the_initial_parameter_values_yields_a_result_matching_the_result_obtained_for_numerical_approximation() + [Test] + public void when_minimizing_a_cost_function_with_an_analytical_hessian_that_is_not_positive_definite_for_the_initial_parameter_values_and_some_parameters_are_limited_yields_a_result_matching_the_result_obtained_for_numerical_approximation() { - // For the initial parameter values [2, 1, 0], the Hessian is not positive definite. Consequently, the initial - // Newton step points in the wrong direction — away from the local minimum. To prevent this, the initial - // Hessian (or its diagonal approximation) must be regularized to ensure positive definiteness during minimizer - // seeding. Without this safeguard, the minimizer will fail in this case (cf. https://github.com/root-project/root/issues/20665). + // For the initial parameter values [2, 1, 0], the Hessian is not positive definite. var problem = new ExponentialDecayProblem(); var parameterConfigurations = problem.ParameterConfigurations .WithParameter(1).WithLimits(0, null) diff --git a/test/minuit2.net.UnitTests/Least_squares_cost_function.spec.cs b/test/minuit2.net.UnitTests/Least_squares_cost_function.spec.cs index 7ba6b0c..ebfec00 100644 --- a/test/minuit2.net.UnitTests/Least_squares_cost_function.spec.cs +++ b/test/minuit2.net.UnitTests/Least_squares_cost_function.spec.cs @@ -129,24 +129,6 @@ public void and_analytical_model_gradient_and_hessian_when_asked_for_its_hessian options => options.WithSmartDoubleTolerance(0.001)); } - [Test] - public void and_analytical_model_gradient_and_hessian_when_asked_for_its_hessian_diagonal_returns_the_expected_vector() - { - var cost = LeastSquares(_xValues, _yValues, _yError, _parameters, TestModel, TestModelGradient, TestModelHessian); - - var expectedHessianDiagonal = new double[2]; - for (var i = 0; i < _valueCount; i++) - { - var g = TestModelGradient(_xValues[i], _parameterValues); - var h = TestModelHessian(_xValues[i], _parameterValues); - expectedHessianDiagonal[0] -= 2 / _yError * (Residual(i) * h[0] - g[0] * g[0] / _yError); - expectedHessianDiagonal[1] -= 2 / _yError * (Residual(i) * h[3] - g[1] * g[1] / _yError); - } - - cost.HessianDiagonalFor(_parameterValues).Should().BeEquivalentTo(expectedHessianDiagonal, - options => options.WithSmartDoubleTolerance(0.001)); - } - [Test] public void and_analytical_model_gradient_and_hessian_and_hessian_diagonal_when_asked_for_its_hessian_diagonal_returns_the_expected_vector() { @@ -309,24 +291,6 @@ public void and_analytical_model_gradient_and_hessian_when_asked_for_its_hessian options => options.WithSmartDoubleTolerance(0.001)); } - [Test] - public void and_analytical_model_gradient_and_hessian_when_asked_for_its_hessian_diagonal_returns_the_expected_vector() - { - var cost = LeastSquares(_xValues, _yValues, _yErrors, _parameters, TestModel, TestModelGradient, TestModelHessian); - - var expectedHessianDiagonal = new double[2]; - for (var i = 0; i < _valueCount; i++) - { - var g = TestModelGradient(_xValues[i], _parameterValues); - var h = TestModelHessian(_xValues[i], _parameterValues); - expectedHessianDiagonal[0] -= 2 / _yErrors[i] * (Residual(i) * h[0] - g[0] * g[0] / _yErrors[i]); - expectedHessianDiagonal[1] -= 2 / _yErrors[i] * (Residual(i) * h[3] - g[1] * g[1] / _yErrors[i]); - } - - cost.HessianDiagonalFor(_parameterValues).Should().BeEquivalentTo(expectedHessianDiagonal, - options => options.WithSmartDoubleTolerance(0.001)); - } - [Test] public void and_analytical_model_gradient_and_hessian_and_hessian_diagonal_when_asked_for_its_hessian_diagonal_returns_the_expected_vector() { @@ -477,24 +441,6 @@ public void and_analytical_model_gradient_and_hessian_when_asked_for_its_hessian options => options.WithSmartDoubleTolerance(0.001)); } - [Test] - public void and_analytical_model_gradient_and_hessian_when_asked_for_its_hessian_diagonal_returns_the_expected_vector() - { - var cost = LeastSquares(_xValues, _yValues, _parameters, TestModel, TestModelGradient, TestModelHessian); - - var expectedHessianDiagonal = new double[2]; - for (var i = 0; i < _valueCount; i++) - { - var g = TestModelGradient(_xValues[i], _parameterValues); - var h = TestModelHessian(_xValues[i], _parameterValues); - expectedHessianDiagonal[0] -= 2 * (Residual(i) * h[0] - g[0] * g[0]); - expectedHessianDiagonal[1] -= 2 * (Residual(i) * h[3] - g[1] * g[1]); - } - - cost.HessianDiagonalFor(_parameterValues).Should().BeEquivalentTo(expectedHessianDiagonal, - options => options.WithSmartDoubleTolerance(0.001)); - } - [Test] public void and_analytical_model_gradient_and_hessian_and_hessian_diagonal_when_asked_for_its_hessian_diagonal_returns_the_expected_vector() {