Hyperiso 1.0.3
Modular flavour-physics calculations, Wilson coefficients and statistical inference
Loading...
Searching...
No Matches
MinuitCppBackend.cpp
Go to the documentation of this file.
1#include "FitAbstraction.h"
2
3#include <algorithm>
4#include <iostream>
5
6#include "minuit-cpp/FCNBase.hh"
7#include "minuit-cpp/FunctionMinimum.hh"
8#include "minuit-cpp/MnContours.hh"
9#include "minuit-cpp/MnEigen.hh"
10#include "minuit-cpp/MnHesse.hh"
11#include "minuit-cpp/MnMigrad.hh"
12#include "minuit-cpp/MnUserParameters.hh"
13
14namespace M2 = MinuitCpp;
15
16namespace fit_app {
17namespace {
18
19class MinuitObjective final : public M2::FCNBase {
20public:
21 explicit MinuitObjective(const IObjectiveFunction& objective)
22 : objective_(objective) {}
23
24 double operator()(const std::vector<double>& x) const override {
25 try {
26 const double value = objective_(x);
27 return std::isfinite(value) ? value : 1e300;
28 } catch (...) {
29 return 1e300;
30 }
31 }
32
33 double Up() const override {
34 return objective_.error_definition();
35 }
36
37private:
38 const IObjectiveFunction& objective_;
39};
40
41class MinuitState final : public BackendState {
42public:
43 explicit MinuitState(M2::FunctionMinimum minimum)
44 : minimum_(std::move(minimum)) {}
45
46 const M2::FunctionMinimum& minimum() const {
47 return minimum_;
48 }
49
50private:
51 M2::FunctionMinimum minimum_;
52};
53
54class MinuitCppBackend final : public IFitBackend {
55public:
56 BackendFitResult minimize(const IObjectiveFunction& objective,
57 const std::vector<ParameterDefinition>& parameters,
58 const FitOptions& options) const override {
59 return do_minimize(objective, parameters, options, {}, {});
60 }
61
62 BackendFitResult minimize_with_fixed(const IObjectiveFunction& objective,
63 const std::vector<ParameterDefinition>& parameters,
64 const FitOptions& options,
65 const std::vector<std::size_t>& fixed_indices,
66 const std::vector<double>& fixed_values) const override {
67 if (fixed_indices.size() != fixed_values.size()) {
68 throw std::invalid_argument("fixed_indices/fixed_values size mismatch");
69 }
70 return do_minimize(objective, parameters, options, fixed_indices, fixed_values);
71 }
72
73 BackendContourResult contour(const IObjectiveFunction& objective,
74 const BackendFitResult& reference_fit,
75 std::size_t x_index,
76 std::size_t y_index,
77 const ContourOptionsBackEnd& options) const override {
78 BackendContourResult out;
79
80 auto state = std::dynamic_pointer_cast<const MinuitState>(reference_fit.state);
81 if (!state) return out;
82
83 try {
84 MinuitObjective minuit_objective(objective);
85 M2::MnContours mn_contours(minuit_objective, state->minimum(), options.strategy);
86 out.points = mn_contours(static_cast<unsigned>(x_index), static_cast<unsigned>(y_index), options.npoints);
87 out.success = out.points.size() >= 4;
88 } catch (...) {
89 out.points.clear();
90 out.success = false;
91 }
92
93 return out;
94 }
95
96private:
97 static void apply_parameters(M2::MnUserParameters& upar,
98 const std::vector<ParameterDefinition>& parameters) {
99 for (const auto& parameter : parameters) {
100 upar.Add(parameter.name.c_str(), parameter.value, safe_step(parameter.value, parameter.step_hint));
101 if (parameter.limits.has_value()) {
102 upar.SetLimits(parameter.name.c_str(), parameter.limits->first, parameter.limits->second);
103 }
104 if (parameter.fixed) {
105 upar.Fix(parameter.name.c_str());
106 }
107 }
108 }
109
110 static void apply_fixed_values(M2::MnMigrad& migrad,
111 const std::vector<std::size_t>& fixed_indices,
112 const std::vector<double>& fixed_values) {
113 for (std::size_t i = 0; i < fixed_indices.size(); ++i) {
114 const unsigned idx = static_cast<unsigned>(fixed_indices[i]);
115 migrad.SetValue(idx, fixed_values[i]);
116 migrad.Fix(idx);
117 }
118 }
119
120 static void log_summary(const M2::FunctionMinimum& minimum) {
121 std::cout << "\n=== [MINUIT] FunctionMinimum ===\n";
122 std::cout << "IsValid = " << minimum.IsValid() << "\n";
123 std::cout << "HasValidParameters = " << minimum.HasValidParameters() << "\n";
124 std::cout << "HasValidCovariance = " << minimum.HasValidCovariance() << "\n";
125 std::cout << "HasAccurateCovar = " << minimum.HasAccurateCovar() << "\n";
126 std::cout << "HasPosDefCovar = " << minimum.HasPosDefCovar() << "\n";
127 std::cout << "HasMadePosDefCovar = " << minimum.HasMadePosDefCovar() << "\n";
128 std::cout << "HesseFailed = " << minimum.HesseFailed() << "\n";
129 std::cout << "Fval = " << minimum.Fval() << "\n";
130 std::cout << "EDM = " << minimum.Edm() << "\n";
131 std::cout << "NFcn = " << minimum.NFcn() << "\n";
132 std::cout << "Up = " << minimum.Up() << "\n";
133 }
134
135 static BackendFitResult extract_result(const M2::FunctionMinimum& minimum,
136 const std::vector<ParameterDefinition>& parameters,
137 bool extract_full_covariance) {
138 BackendFitResult out;
139 out.values.resize(parameters.size());
140 out.errors.assign(parameters.size(), 0.0);
141 out.covariance = RealMatrix(parameters.size(), parameters.size());
142 out.state = std::make_shared<MinuitState>(minimum);
143
144 out.diagnostics.fmin = minimum.Fval();
145 out.diagnostics.edm = minimum.Edm();
146 out.diagnostics.nfcn = minimum.NFcn();
147 out.diagnostics.ok = minimum.IsValid();
148 out.diagnostics.has_valid_parameters = minimum.HasValidParameters();
149 out.diagnostics.hesse_failed = minimum.HesseFailed();
150 out.diagnostics.has_valid_covar = minimum.HasValidCovariance();
151 out.diagnostics.has_posdef_covar = minimum.HasPosDefCovar();
152 out.diagnostics.has_accurate_covar = minimum.HasAccurateCovar();
153 out.diagnostics.made_posdef = minimum.HasMadePosDefCovar();
154 out.diagnostics.reached_call_limit = minimum.HasReachedCallLimit();
155 out.diagnostics.above_max_edm = minimum.IsAboveMaxEdm();
156
157 const auto& state = minimum.UserState();
158 for (std::size_t i = 0; i < parameters.size(); ++i) {
159 out.values[i] = state.Value(parameters[i].name.c_str());
160 out.errors[i] = parameters[i].fixed ? 0.0 : state.Error(parameters[i].name.c_str());
161 }
162
163 // if (!extract_full_covariance || !minimum.HasValidCovariance()) {
164 // out.diagnostics.has_valid_covar = false;
165 // out.diagnostics.has_posdef_covar = false;
166 // out.diagnostics.has_accurate_covar = false;
167 // out.diagnostics.made_posdef = false;
168 // return out;
169 // }
170 if (!minimum.HasValidCovariance()) {
171 return out;
172 }
173
174 if (!extract_full_covariance) {
175 return out;
176 }
177 const auto& cov = state.Covariance();
178 for (std::size_t i = 0; i < parameters.size(); ++i) {
179 for (std::size_t j = 0; j < parameters.size(); ++j) {
180 out.covariance.at(i, j) = cov(i, j);
181 }
182 }
183
184 M2::MnEigen eigen;
185 out.diagnostics.cov_eigs = eigen(cov);
186
187 double min_pos = std::numeric_limits<double>::infinity();
188 double max_pos = 0.0;
189 for (double eig : out.diagnostics.cov_eigs) {
190 if (std::isfinite(eig) && eig > 0.0) {
191 min_pos = std::min(min_pos, eig);
192 max_pos = std::max(max_pos, eig);
193 }
194 }
195
196 if (min_pos < std::numeric_limits<double>::infinity() && max_pos > 0.0) {
197 out.diagnostics.cond_number = max_pos / min_pos;
198 }
199
200 return out;
201 }
202
203 static BackendFitResult do_minimize(const IObjectiveFunction& objective,
204 const std::vector<ParameterDefinition>& parameters,
205 const FitOptions& options,
206 const std::vector<std::size_t>& fixed_indices,
207 const std::vector<double>& fixed_values) {
208 MinuitObjective minuit_objective(objective);
209
210 M2::MnUserParameters upar;
211 apply_parameters(upar, parameters);
212
213 M2::MnMigrad migrad(minuit_objective, upar, options.strategy);
214 apply_fixed_values(migrad, fixed_indices, fixed_values);
215
216 M2::FunctionMinimum minimum = migrad(options.max_fcn, options.tolerance);
217
218 if (options.run_hesse) {
219 M2::MnHesse hesse(options.strategy);
220 hesse(minuit_objective, minimum, options.hesse_maxcalls);
221 }
222
223 if (options.verbose) {
224 log_summary(minimum);
225 }
226
227 const bool has_explicitly_fixed_parameters = !fixed_indices.empty() ||
228 std::any_of(parameters.begin(), parameters.end(), [](const ParameterDefinition& parameter) {
229 return parameter.fixed;
230 });
231
232 return extract_result(minimum, parameters, !has_explicitly_fixed_parameters);
233 }
234};
235
236} // namespace
237
238std::unique_ptr<IFitBackend> make_minuit_backend() {
239 return std::make_unique<MinuitCppBackend>();
240}
241
242} // namespace fit_app
std::unique_ptr< IFitBackend > make_minuit_backend()
double safe_step(double value, double scale_hint)
Hash specialization for SymbolId<Tag>.
Definition BlockName.h:353