Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 51 additions & 31 deletions Build.targets
Original file line number Diff line number Diff line change
@@ -1,53 +1,56 @@
<Project>

<PropertyGroup>
<RootRevision>b5fd6643c1cc36fa95297c6a5cda4140573006d5</RootRevision>
<RootRevision>3b5475ca350bd5e50b58fd2d8642051ee0c19916</RootRevision>
<RootGitPath>$(MSBuildThisFileDirectory)minuit2.net/obj/root</RootGitPath>
<SwigOutputPath>$(MSBuildThisFileDirectory)/minuit2.net/obj/gen</SwigOutputPath>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="swigwintools" Version="4.2.0" PrivateAssets="all"/>
<PackageReference Include="swigwintools"
Version="4.2.0"
PrivateAssets="all"/>
</ItemGroup>

<Target Name="CleanGeneratedDirectories" BeforeTargets="Clean">
<Target Name="CleanGeneratedDirectories"
BeforeTargets="Clean">
<RemoveDir Directories="$(MSBuildThisFileDirectory)/minuit2.wrap/build;$(SwigOutputPath)"/>
<Exec Command="git clean -fdx"
WorkingDirectory="$(RootGitPath)"
ContinueOnError="false"
Condition="Exists('$(RootGitPath)')"/>
</Target>

<Target Name="Fetch Minuit" BeforeTargets="BeforeBuild" Condition="!Exists('$(RootGitPath)')">
<Target Name="Fetch Minuit"
BeforeTargets="BeforeBuild"
Condition="!Exists('$(RootGitPath)')">

<MakeDir Directories="$(RootGitPath)"/>
<Exec Command="git init"
WorkingDirectory="$(RootGitPath)"
ContinueOnError="false"/>
WorkingDirectory="$(RootGitPath)"/>

<!-- Add remote origin -->
<Exec Command="git remote add origin https://github.com/root-project/root.git"
WorkingDirectory="$(RootGitPath)"
ContinueOnError="false"/>
WorkingDirectory="$(RootGitPath)"/>

<!-- Fetch only the specific commit without history -->
<Exec Command="git fetch --depth 1 --no-tags origin $(RootRevision)"
WorkingDirectory="$(RootGitPath)"
ContinueOnError="false"/>
WorkingDirectory="$(RootGitPath)"/>

<!-- Checkout the fetched commit -->
<Exec Command="git checkout $(RootRevision)"
WorkingDirectory="$(RootGitPath)"
ContinueOnError="false"/>
WorkingDirectory="$(RootGitPath)"/>
</Target>

<!-- Ensure build directory exists -->
<Target Name="CreateBuildDirectories" BeforeTargets="BeforeBuild">
<Target Name="CreateBuildDirectories"
BeforeTargets="BeforeBuild">
<MakeDir Directories="$(BuildDir);$(SwigOutputPath);$(CmakeBuildDir)"/>
</Target>

<!-- Run CMake Configure -->
<Target Name="CmakeConfigure" BeforeTargets="BeforeBuild" DependsOnTargets="CreateBuildDirectories">
<Target Name="CmakeConfigure"
BeforeTargets="BeforeBuild"
DependsOnTargets="CreateBuildDirectories">
<PropertyGroup>
<CmakeArchitecture Condition="'$(Platform)' == 'x64'">x64</CmakeArchitecture>
<CmakeArchitecture Condition="'$(Platform)' == 'x86'">Win32</CmakeArchitecture>
Expand All @@ -60,48 +63,61 @@

<Exec Command="$(CmakeBuildCommand)"
WorkingDirectory="$(MSBuildThisFileDirectory)"
ContinueOnError="false"
IgnoreExitCode="false"/>
</Target>

<!-- Build C++ Library and Generate SWIG Wrappers -->
<Target Name="BuildNativeLibrary" BeforeTargets="BeforeBuild" DependsOnTargets="CmakeConfigure">
<Target Name="BuildNativeLibrary"
BeforeTargets="BeforeBuild"
DependsOnTargets="CmakeConfigure">
<PropertyGroup>
<CmakeBuildCommand>cmake --build "$(CmakeBuildDir)" --config Release</CmakeBuildCommand>
</PropertyGroup>

<Exec Command="$(CmakeBuildCommand)"
WorkingDirectory="$(MSBuildThisFileDirectory)"
ContinueOnError="false"/>
WorkingDirectory="$(MSBuildThisFileDirectory)"/>
</Target>

<Target Name="IncludeGeneratedCSharpFiles" AfterTargets="BuildNativeLibrary" BeforeTargets="CoreCompile">
<Target Name="IncludeGeneratedCSharpFiles"
AfterTargets="BuildNativeLibrary"
BeforeTargets="CoreCompile">
<ItemGroup>
<GeneratedCSharpFiles Include="$(MSBuildThisFileDirectory)/minuit2.net/obj/gen/*.cs"/>
<Compile Include="@(GeneratedCSharpFiles)"/>
</ItemGroup>
</Target>

<!-- Copy native DLL to output directory -->
<Target Name="CopyWrapperDll" AfterTargets="BuildNativeLibrary">
<Target Name="CopyWrapperDll"
AfterTargets="BuildNativeLibrary">
<ItemGroup>
<NativeDlls Include="$(CmakeBuildDir)/minuit2.wrap/Release/$(NativeAssemblyName).dll" Visible="false"/>
<NativeDlls Include="$(CmakeBuildDir)/minuit2.wrap/Release/$(NativeAssemblyName).dll"
Visible="false"/>
</ItemGroup>

<ItemGroup>
<None Include="@(NativeDlls)" Visible="false">
<None Include="@(NativeDlls)"
Visible="false">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
<Visible>false</Visible>
</None>
</ItemGroup>
</Target>

<Target Name="CopyNativeBinariesForNuGet" AfterTargets="CopyWrapperDll" Condition="'$(Platform)' != 'AnyCPU' AND '$(PlatformTarget)'!= 'AnyCPU'">
<Target Name="CopyNativeBinariesForNuGet"
AfterTargets="CopyWrapperDll"
Condition="'$(Platform)' != 'AnyCPU' AND '$(PlatformTarget)'!= 'AnyCPU'">
<ItemGroup>
<NativeBinaries Include="$(OutputPath)$(NativeAssemblyName).dll" Condition="'$(Platform)' == 'x64'" Visible="false"/>
<NativeBinaries Include="$(OutputPath)$(NativeAssemblyName).dll" Condition="'$(Platform)' == 'ARM64'" Visible="false"/>
<NativeBinaries Include="$(OutputPath)$(NativeAssemblyName).dll" Condition="'$(Platform)' == 'x86'" Visible="false"/>
<NativeBinaries Include="$(OutputPath)$(NativeAssemblyName).dll"
Condition="'$(Platform)' == 'x64'"
Visible="false"/>
<NativeBinaries Include="$(OutputPath)$(NativeAssemblyName).dll"
Condition="'$(Platform)' == 'ARM64'"
Visible="false"/>
<NativeBinaries Include="$(OutputPath)$(NativeAssemblyName).dll"
Condition="'$(Platform)' == 'x86'"
Visible="false"/>
</ItemGroup>
<Copy
SourceFiles="@(NativeDlls)"
Expand All @@ -111,7 +127,8 @@

</Target>

<Target Name="CheckAvailableDlls" BeforeTargets="GenerateNuspec">
<Target Name="CheckAvailableDlls"
BeforeTargets="GenerateNuspec">
<PropertyGroup>
<RequiredPlatforms>x64;x86;ARM64</RequiredPlatforms>
</PropertyGroup>
Expand All @@ -125,7 +142,8 @@

<!-- Check each required native DLL exists -->
<ItemGroup>
<MissingNativeDlls Include="@(RequiredNativeDlls)" Condition="!Exists('%(RequiredNativeDlls.Identity)')"/>
<MissingNativeDlls Include="@(RequiredNativeDlls)"
Condition="!Exists('%(RequiredNativeDlls.Identity)')"/>
</ItemGroup>

<Error Text="Cannot pack: Missing native DLL for platform. Missing files: @(MissingNativeDlls, ', '). Please build all platforms (x64, x86, ARM64) before packing."
Expand All @@ -134,12 +152,14 @@
</Target>


<Target Name="RenameROOTLicense" AfterTargets="CopyNativeBinariesForNuGet">
<Target Name="RenameROOTLicense"
AfterTargets="CopyNativeBinariesForNuGet">
<Copy SourceFiles="$(RootGitPath)/LICENSE"
DestinationFiles="$(IntermediateOutputPath)/ROOT-LICENSE"/>
</Target>

<Target Name="AddROOTLicenseToNuget" AfterTargets="CopyNativeBinariesForNuGet; RenameROOTLicense">
<Target Name="AddROOTLicenseToNuget"
AfterTargets="CopyNativeBinariesForNuGet; RenameROOTLicense">
<ItemGroup>
<None Include="$(IntermediateOutputPath)/ROOT-LICENSE"
Pack="true"
Expand Down
26 changes: 0 additions & 26 deletions minuit2.net/CostFunctions/LeastSquares.cs
Original file line number Diff line number Diff line change
Expand Up @@ -68,32 +68,6 @@ public override IReadOnlyList<double> HessianFor(IReadOnlyList<double> parameter
}

public override IReadOnlyList<double> HessianDiagonalFor(IReadOnlyList<double> parameterValues)
{
return modelHessianDiagonal == null
? HessianDiagonalFromModelHessianFor(parameterValues)
: HessianDiagonalFromModelHessianDiagonalFor(parameterValues);
}

private double[] HessianDiagonalFromModelHessianFor(IReadOnlyList<double> 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<double> parameterValues)
{
var hessianDiagonal = new double[Parameters.Count];
for (var i = 0; i < x.Count; i++)
Expand Down
2 changes: 1 addition & 1 deletion minuit2.net/CostFunctions/LeastSquaresBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ internal abstract class LeastSquaresBase(
public IReadOnlyList<string> 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down
11 changes: 3 additions & 8 deletions test/minuit2.net.UnitTests/Any_gradient_based_minimizer.spec.cs
Original file line number Diff line number Diff line change
Expand Up @@ -193,15 +193,10 @@ public void when_minimizing_a_cost_function_with_an_analytical_hessian_that_thro
action.Should().ThrowExactly<TestException>();
}

[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)
Expand Down
54 changes: 0 additions & 54 deletions test/minuit2.net.UnitTests/Least_squares_cost_function.spec.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
{
Expand Down Expand Up @@ -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()
{
Expand Down Expand Up @@ -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()
{
Expand Down