Skip to content

Commit 1ea6400

Browse files
committed
[Minuit2] Merge FCNAdapter with -GradAdapter and use only std::function
The base classes `FCNBase` and `FCNGradientBase` were already unified in 71806b3, and the Minuit code can be further simplified by merging also the derived adapter class for `std::function` objects. This will also make it easier for the users, because then don't have to use different classes depending on whether they pass external gradients or not. We also make the new `FCNAdapter` purely `std::function` based, instead of using a template parameter. This makes is possible to use Minuit2 directly from Python, as we can benefit from the `std::function` pythonization. It also makes the Minuit2 code more consistent, because `std::function` was already used for the G2 (second derivatives) and the Hessian.
1 parent 04f1a45 commit 1ea6400

File tree

6 files changed

+86
-151
lines changed

6 files changed

+86
-151
lines changed

math/minuit2/CMakeLists.txt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ if(CMAKE_PROJECT_NAME STREQUAL ROOT)
2626
Minuit2/ExternalInternalGradientCalculator.h
2727
Minuit2/FCNAdapter.h
2828
Minuit2/FCNBase.h
29-
Minuit2/FCNGradAdapter.h
3029
Minuit2/FCNGradientBase.h
3130
Minuit2/FumiliBuilder.h
3231
Minuit2/FumiliChi2FCN.h

math/minuit2/inc/Minuit2/FCNAdapter.h

Lines changed: 73 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#include <ROOT/RSpan.hxx>
1616

1717
#include <vector>
18+
#include <functional>
1819

1920
namespace ROOT {
2021

@@ -23,29 +24,94 @@ namespace Minuit2 {
2324
/**
2425
2526
26-
template wrapped class for adapting to FCNBase signature
27+
Wrapper class for std::function objects, adapting to FCNBase signature.
2728
2829
@author Lorenzo Moneta
2930
3031
@ingroup Minuit
3132
3233
*/
3334

34-
template <class Function>
3535
class FCNAdapter : public FCNBase {
3636

3737
public:
38-
FCNAdapter(const Function &f, double up = 1.) : fFunc(f), fUp(up) {}
38+
FCNAdapter(std::function<double(double const *)> f, double up = 1.) : fUp(up), fFunc(std::move(f)) {}
39+
40+
bool HasGradient() const override { return bool(fGradFunc); }
41+
bool HasG2() const override { return bool(fG2Func); }
42+
bool HasHessian() const override { return bool(fHessianFunc); }
43+
44+
double operator()(std::vector<double> const &v) const override { return fFunc(v.data()); }
3945

40-
double operator()(std::vector<double> const& v) const override { return fFunc.operator()(&v[0]); }
41-
double operator()(const double *v) const { return fFunc.operator()(v); }
4246
double Up() const override { return fUp; }
4347

48+
std::vector<double> Gradient(std::vector<double> const &v) const override
49+
{
50+
std::vector<double> output(v.size());
51+
fGradFunc(v.data(), output.data());
52+
return output;
53+
}
54+
55+
/// return second derivatives (diagonal of the Hessian matrix)
56+
std::vector<double> G2(std::vector<double> const &x) const override
57+
{
58+
std::vector<double> output;
59+
if (fG2Func)
60+
return fG2Func(x);
61+
if (fHessianFunc) {
62+
std::size_t n = x.size();
63+
output.resize(n);
64+
if (fHessian.empty())
65+
fHessian.resize(n * n);
66+
fHessianFunc(x, fHessian.data());
67+
if (!fHessian.empty()) {
68+
// get diagonal element of h
69+
for (unsigned int i = 0; i < n; i++)
70+
output[i] = fHessian[i * n + i];
71+
}
72+
}
73+
return output;
74+
}
75+
76+
/// compute Hessian. Return Hessian as a std::vector of size(n*n)
77+
std::vector<double> Hessian(std::vector<double> const &x) const override
78+
{
79+
std::vector<double> output;
80+
if (fHessianFunc) {
81+
std::size_t n = x.size();
82+
output.resize(n * n);
83+
bool ret = fHessianFunc(x, output.data());
84+
if (!ret) {
85+
output.clear();
86+
fHessianFunc = nullptr;
87+
}
88+
}
89+
90+
return output;
91+
}
92+
93+
void SetGradientFunction(std::function<void(double const *, double *)> f) { fGradFunc = std::move(f); }
94+
void SetG2Function(std::function<std::vector<double>(std::vector<double> const &)> f) { fG2Func = std::move(f); }
95+
void SetHessianFunction(std::function<bool(std::vector<double> const &, double *)> f)
96+
{
97+
fHessianFunc = std::move(f);
98+
}
99+
44100
void SetErrorDef(double up) override { fUp = up; }
45101

46102
private:
47-
const Function &fFunc;
48-
double fUp;
103+
using Function = std::function<double(double const *)>;
104+
using GradFunction = std::function<void(double const *, double *)>;
105+
using G2Function = std::function<std::vector<double>(std::vector<double> const &)>;
106+
using HessianFunction = std::function<bool(std::vector<double> const &, double *)>;
107+
108+
double fUp = 1.;
109+
mutable std::vector<double> fHessian;
110+
111+
Function fFunc;
112+
GradFunction fGradFunc;
113+
G2Function fG2Func;
114+
mutable HessianFunction fHessianFunc;
49115
};
50116

51117
} // end namespace Minuit2

math/minuit2/inc/Minuit2/FCNBase.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,10 @@ class FCNBase {
113113
virtual bool HasGradient() const { return false; }
114114

115115
virtual std::vector<double> Gradient(std::vector<double> const&) const { return {}; }
116+
117+
/// In some cases, the gradient algorithm will use information from the previous step, these can be passed
118+
/// in with this overload. The `previous_*` arrays can also be used to return second derivative and step size
119+
/// so that these can be passed forward again as well at the call site, if necessary.
116120
virtual std::vector<double> GradientWithPrevResult(std::vector<double> const& parameters, double * /*previous_grad*/,
117121
double * /*previous_g2*/, double * /*previous_gstep*/) const
118122
{

math/minuit2/inc/Minuit2/FCNGradAdapter.h

Lines changed: 0 additions & 135 deletions
This file was deleted.

math/minuit2/src/CMakeLists.txt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ set(MINUIT2_HEADERS
1616
ExternalInternalGradientCalculator.h
1717
FCNAdapter.h
1818
FCNBase.h
19-
FCNGradAdapter.h
2019
FCNGradientBase.h
2120
FumiliBuilder.h
2221
FumiliChi2FCN.h

math/minuit2/src/Minuit2Minimizer.cxx

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919

2020
#include "Minuit2/FCNAdapter.h"
2121
#include "Minuit2/FumiliFCNAdapter.h"
22-
#include "Minuit2/FCNGradAdapter.h"
2322
#include "Minuit2/FunctionMinimum.h"
2423
#include "Minuit2/MnMigrad.h"
2524
#include "Minuit2/MnMinos.h"
@@ -375,11 +374,14 @@ void Minuit2Minimizer::SetFunction(const ROOT::Math::IMultiGenFunction &func)
375374
fDim = func.NDim();
376375
const bool hasGrad = func.HasGradient();
377376
if (!fUseFumili) {
378-
if (hasGrad)
379-
fMinuitFCN = std::make_unique<ROOT::Minuit2::FCNGradAdapter<ROOT::Math::IMultiGradFunction>>(
380-
dynamic_cast<ROOT::Math::IMultiGradFunction const &>(func), ErrorDef());
381-
else
382-
fMinuitFCN = std::make_unique<ROOT::Minuit2::FCNAdapter<ROOT::Math::IMultiGenFunction>>(func, ErrorDef());
377+
auto lambdaFunc = [&func](double const *params) { return func(params); };
378+
auto adapter = std::make_unique<ROOT::Minuit2::FCNAdapter>(lambdaFunc, ErrorDef());
379+
if (hasGrad) {
380+
auto const &gradFunc = dynamic_cast<ROOT::Math::IMultiGradFunction const &>(func);
381+
auto lambdaGrad = [&gradFunc](double const *params, double *grad) { return gradFunc.Gradient(params, grad); };
382+
adapter->SetGradientFunction(lambdaGrad);
383+
}
384+
fMinuitFCN = std::move(adapter);
383385
return;
384386
}
385387
if (hasGrad) {
@@ -409,7 +411,7 @@ void Minuit2Minimizer::SetHessianFunction(std::function<bool(std::span<const dou
409411
{
410412
// for Fumili not supported for the time being
411413
if (fUseFumili) return;
412-
auto fcn = static_cast<ROOT::Minuit2::FCNGradAdapter<ROOT::Math::IMultiGradFunction> *>(fMinuitFCN.get());
414+
auto fcn = static_cast<ROOT::Minuit2::FCNAdapter *>(fMinuitFCN.get());
413415
if (!fcn) return;
414416
fcn->SetHessianFunction(hfunc);
415417
}

0 commit comments

Comments
 (0)