Hyperiso 1.0.3
Modular flavour-physics calculations, Wilson coefficients and statistical inference
Loading...
Searching...
No Matches
test_contour_minuit2.cpp
Go to the documentation of this file.
1// #include <algorithm>
2// #include <chrono>
3// #include <cmath>
4// #include <fstream>
5// #include <functional>
6// #include <iomanip>
7// #include <iostream>
8// #include <limits>
9// #include <memory>
10// #include <sstream>
11// #include <stdexcept>
12// #include <string>
13// #include <vector>
14
15// #include "StatisticManager.h"
16// #include "ObservableInterfaceProxy.h"
17// #include "StatCorrelationProxy.h"
18// #include "StatParameterProxy.h"
19// #include "ObservableInterface.h"
20// #include "StatParamSourcesProxy.h"
21// #include "StatDependencyPruner.h"
22
23// #include "Fit.h"
24// #include "BaseLikelihood.h"
25// #include "NuisanceReader.h"
26// #include "DefaultNuisancePathsProvider.h"
27
28// #include "minuit-cpp/FCNBase.hh"
29// #include "minuit-cpp/FunctionMinimum.hh"
30// #include "minuit-cpp/MnEigen.hh"
31// #include "minuit-cpp/MnHesse.hh"
32// #include "minuit-cpp/MnMigrad.hh"
33// #include "minuit-cpp/MnUserCovariance.hh"
34// #include "minuit-cpp/MnUserParameters.hh"
35// #include "minuit-cpp/MnUserParameterState.hh"
36
37// namespace M2 = MinuitCpp;
38
39// // -----------------------------------------------------------------------------
40// // Helpers
41// // -----------------------------------------------------------------------------
42
43// template <class T>
44// static std::string stream_str(const T& x) {
45// std::ostringstream oss;
46// oss << x;
47// return oss.str();
48// }
49
50// static void print_vec(const std::vector<double>& vec) {
51// std::cout << "[ ";
52// for (std::size_t i = 0; i < vec.size(); ++i) {
53// std::cout << std::setprecision(17) << vec[i]
54// << (i + 1 == vec.size() ? " " : ", ");
55// }
56// std::cout << "]\n";
57// }
58
59// static std::vector<double> linspace(double a, double b, std::size_t n) {
60// std::vector<double> out(n);
61// if (n == 0) return out;
62// if (n == 1) {
63// out[0] = a;
64// return out;
65// }
66// for (std::size_t i = 0; i < n; ++i) {
67// out[i] = a + (b - a) * double(i) / double(n - 1);
68// }
69// return out;
70// }
71
72// static double safe_step(double value, double scale_hint) {
73// double a = std::fabs(value);
74// double s = std::fabs(scale_hint);
75
76// double step = 0.0;
77// if (std::isfinite(s) && s > 0.0) step = 0.05 * s;
78// if (std::isfinite(a) && a > 0.0) step = std::max(step, 0.01 * a);
79// if (!std::isfinite(step) || step <= 0.0) step = 1e-3;
80
81// return step;
82// }
83
84// static void save_bestfit_csv(const std::string& path,
85// const std::vector<std::string>& names,
86// const Vector& vals,
87// const Vector& errs) {
88// std::ofstream out(path);
89// out << "name,value,error\n";
90// for (std::size_t i = 0; i < vals.size(); ++i) {
91// out << names[i] << ","
92// << std::setprecision(17) << vals[i] << ","
93// << errs[i] << "\n";
94// }
95// }
96
97// static void save_grid_csv(const std::string& path,
98// const std::string& xname,
99// const std::string& yname,
100// const std::vector<double>& xs,
101// const std::vector<double>& ys,
102// const std::vector<double>& z) {
103// const std::size_t nx = xs.size();
104// const std::size_t ny = ys.size();
105
106// std::ofstream out(path);
107// out << "# x=" << xname << "\n";
108// out << "# y=" << yname << "\n";
109// out << "x,y,delta_nll\n";
110// out << std::setprecision(17);
111
112// for (std::size_t iy = 0; iy < ny; ++iy) {
113// for (std::size_t ix = 0; ix < nx; ++ix) {
114// out << xs[ix] << "," << ys[iy] << "," << z[iy * nx + ix] << "\n";
115// }
116// }
117// }
118
119// // -----------------------------------------------------------------------------
120// // Minuit wrappers
121// // -----------------------------------------------------------------------------
122
123// struct ParamLimit {
124// std::size_t idx;
125// double low;
126// double high;
127// };
128
129// struct MinuitFitOptions {
130// double up = 0.5; // NLL => 0.5 for 1D 1σ
131// unsigned strategy = 2;
132// unsigned max_fcn = 100000;
133// double tolerance = 0.2; // Minuit "toler"
134// bool run_hesse = true;
135// unsigned hesse_maxcalls = 0;
136// bool verbose = true;
137// };
138
139// struct MinuitJointFit {
140// Vector x_hat; // full vector [p, eta]
141// std::vector<double> x_err; // HESSE errors
142// std::vector<double> cov_eigs; // covariance eigenvalues
143// double cond_number = std::numeric_limits<double>::infinity();
144
145// double fmin = std::numeric_limits<double>::quiet_NaN();
146// double edm = std::numeric_limits<double>::quiet_NaN();
147// int nfcn = -1;
148
149// bool ok = false;
150// bool has_valid_covar = false;
151// bool has_posdef_covar = false;
152// bool has_accurate_covar = false;
153// bool made_posdef = false;
154
155// RealMatrix cov;
156// };
157
158// class GenericFCN final : public M2::FCNBase {
159// public:
160// GenericFCN(std::function<double(const std::vector<double>&)> f, double up)
161// : f_(std::move(f)), up_(up) {}
162
163// double operator()(const std::vector<double>& x) const override {
164// try {
165// const double v = f_(x);
166// return std::isfinite(v) ? v : 1e300;
167// } catch (...) {
168// return 1e300;
169// }
170// }
171
172// double Up() const override { return up_; }
173
174// private:
175// std::function<double(const std::vector<double>&)> f_;
176// double up_;
177// };
178
179// static void log_minuit_summary(const std::string& tag, const M2::FunctionMinimum& min) {
180// std::cout << "\n=== [" << tag << "] FunctionMinimum ===\n";
181// std::cout << "IsValid = " << min.IsValid() << "\n";
182// std::cout << "HasValidParameters = " << min.HasValidParameters() << "\n";
183// std::cout << "HasValidCovariance = " << min.HasValidCovariance() << "\n";
184// std::cout << "HasAccurateCovar = " << min.HasAccurateCovar() << "\n";
185// std::cout << "HasPosDefCovar = " << min.HasPosDefCovar() << "\n";
186// std::cout << "HasMadePosDefCovar = " << min.HasMadePosDefCovar() << "\n";
187// std::cout << "HesseFailed = " << min.HesseFailed() << "\n";
188// std::cout << "Fval = " << std::setprecision(17) << min.Fval() << "\n";
189// std::cout << "EDM = " << std::setprecision(17) << min.Edm() << "\n";
190// std::cout << "NFcn = " << min.NFcn() << "\n";
191// std::cout << "Up = " << min.Up() << "\n";
192// }
193
194// static MinuitJointFit minuit_migrad_hesse(
195// const std::function<double(const std::vector<double>&)>& f,
196// const std::vector<std::string>& names,
197// const std::vector<double>& x0,
198// const std::vector<double>& scale_hints,
199// const std::vector<ParamLimit>& limits,
200// const MinuitFitOptions& opt
201// ) {
202// if (x0.size() != names.size() || x0.size() != scale_hints.size()) {
203// throw std::invalid_argument("minuit_migrad_hesse: names/x0/scale_hints size mismatch");
204// }
205
206// GenericFCN fcn(f, opt.up);
207
208// M2::MnUserParameters upar;
209// for (std::size_t i = 0; i < x0.size(); ++i) {
210// const double step = safe_step(x0[i], scale_hints[i]);
211// upar.Add(names[i].c_str(), x0[i], step);
212// }
213
214// for (const auto& lim : limits) {
215// if (lim.idx < names.size()) {
216// upar.SetLimits(names[lim.idx].c_str(), lim.low, lim.high);
217// }
218// }
219
220// M2::MnMigrad migrad(fcn, upar, opt.strategy);
221// M2::FunctionMinimum min = migrad(opt.max_fcn, opt.tolerance);
222
223// if (opt.run_hesse) {
224// M2::MnHesse hesse(opt.strategy);
225// hesse(fcn, min, opt.hesse_maxcalls);
226// }
227
228// if (opt.verbose) {
229// log_minuit_summary("MIGRAD+HESSE", min);
230// }
231
232// std::vector<double> eigs;
233// double cond = std::numeric_limits<double>::infinity();
234
235// if (min.HasValidCovariance()) {
236// M2::MnEigen eigen;
237// eigs = eigen(min.UserState().Covariance());
238
239// double min_pos = std::numeric_limits<double>::infinity();
240// double max_pos = 0.0;
241// for (double e : eigs) {
242// if (std::isfinite(e) && e > 0.0) {
243// min_pos = std::min(min_pos, e);
244// max_pos = std::max(max_pos, e);
245// }
246// }
247// if (min_pos < std::numeric_limits<double>::infinity() && max_pos > 0.0) {
248// cond = max_pos / min_pos;
249// }
250
251// if (opt.verbose) {
252// if (!eigs.empty()) {
253// std::cout << "Cov eigen min/max = "
254// << std::setprecision(6) << eigs.front()
255// << " / " << eigs.back()
256// << " (cond ~ " << cond << ")\n";
257// } else {
258// std::cout << "Cov eigenvalues unavailable\n";
259// }
260// }
261// }
262
263// MinuitJointFit out;
264// out.fmin = min.Fval();
265// out.edm = min.Edm();
266// out.nfcn = min.NFcn();
267// out.ok = min.IsValid();
268
269// out.has_valid_covar = min.HasValidCovariance();
270// out.has_posdef_covar = min.HasPosDefCovar();
271// out.has_accurate_covar = min.HasAccurateCovar();
272// out.made_posdef = min.HasMadePosDefCovar();
273
274// out.cov_eigs = std::move(eigs);
275// out.cond_number = cond;
276
277// const auto& st = min.UserState();
278// const std::size_t n = x0.size();
279
280// out.x_hat.resize(n);
281// out.x_err.assign(n, 0.0);
282// out.cov = RealMatrix(n, n);
283
284// for (std::size_t i = 0; i < n; ++i) {
285// out.x_hat[i] = st.Value(names[i].c_str());
286// out.x_err[i] = st.Error(names[i].c_str());
287// }
288
289// if (min.HasValidCovariance()) {
290// const auto& cov = st.Covariance();
291// for (std::size_t i = 0; i < n; ++i) {
292// for (std::size_t j = 0; j < n; ++j) {
293// out.cov.at(i, j) = cov(i, j);
294// }
295// }
296// }
297
298// return out;
299// }
300
301// struct ProfileXYResult {
302// double fmin = 1e300;
303// std::vector<double> x_hat;
304// bool ok = false;
305// };
306
307// static ProfileXYResult profiled_fit_at_fixed_xy(
308// const std::function<double(const std::vector<double>&)>& f_joint,
309// const std::vector<std::string>& names,
310// const std::vector<double>& x_start,
311// const std::vector<double>& scale_hints,
312// const std::vector<ParamLimit>& limits,
313// unsigned px,
314// unsigned py,
315// double xval,
316// double yval,
317// unsigned strategy,
318// unsigned max_fcn,
319// double tolerance
320// ) {
321// GenericFCN fcn(f_joint, 0.5);
322
323// M2::MnUserParameters upar;
324// for (std::size_t i = 0; i < x_start.size(); ++i) {
325// upar.Add(names[i].c_str(), x_start[i], safe_step(x_start[i], scale_hints[i]));
326// }
327
328// for (const auto& lim : limits) {
329// if (lim.idx < names.size()) {
330// upar.SetLimits(names[lim.idx].c_str(), lim.low, lim.high);
331// }
332// }
333
334// M2::MnMigrad migrad(fcn, upar, strategy);
335// migrad.SetValue(px, xval);
336// migrad.SetValue(py, yval);
337// migrad.Fix(px);
338// migrad.Fix(py);
339
340// M2::FunctionMinimum min = migrad(max_fcn, tolerance);
341
342// ProfileXYResult out;
343// out.ok = min.IsValid();
344// out.fmin = out.ok ? min.Fval() : 1e300;
345// out.x_hat = x_start;
346
347// if (out.ok) {
348// const auto& st = min.UserState();
349// for (std::size_t i = 0; i < x_start.size(); ++i) {
350// out.x_hat[i] = st.Value(names[i].c_str());
351// }
352// } else {
353// out.x_hat[px] = xval;
354// out.x_hat[py] = yval;
355// out.fmin = f_joint(out.x_hat);
356// }
357
358// return out;
359// }
360
361// // -----------------------------------------------------------------------------
362// // Local estimator using Minuit
363// // -----------------------------------------------------------------------------
364
365// struct JointFitOutput {
366// FitResult fr;
367// MinuitJointFit mj;
368// std::vector<std::string> names; // full order [p, eta]
369// std::vector<double> scale_hints; // full order [p, eta]
370// std::vector<ParamLimit> limits; // full order indices
371// };
372
373// class MinuitMLEstimatorLocal {
374// public:
375// // using ModelFn = ProfiledLikelihood::ModelFn;
376
377// MinuitMLEstimatorLocal(LikelihoodContext ctx,
378// ModelFn model,
379// std::size_t max_fcn,
380// double tolerance,
381// unsigned strategy)
382// : like_(std::move(ctx))
383// , model_(std::move(model))
384// , max_fcn_(max_fcn)
385// , tolerance_(tolerance)
386// , strategy_(strategy) {}
387
388// std::function<double(const std::vector<double>&)> make_joint_f(std::size_t p_dim) const {
389// return [this, p_dim](const std::vector<double>& x) -> double {
390// Vector p(x.begin(), x.begin() + p_dim);
391// Vector eta(x.begin() + p_dim, x.end());
392// return nll(p, eta);
393// };
394// }
395
396// JointFitOutput fit_joint_with_minuit(
397// const std::vector<ParamId>& p_ids,
398// const std::vector<ParamId>& eta_ids,
399// const Vector& p0
400// ) const {
401// const std::size_t p_dim = p0.size();
402
403// // Vector eta0 = like_.nuisance_central_values;
404// Vector eta0;
405// for (auto elem : like_.nuis_defs) {
406// eta0.push_back(elem.value);
407// }
408// Vector eta_scales = like_.nuisance_dist->get_stds();
409
410// if (eta0.size() != eta_scales.size()) {
411// throw std::runtime_error("eta central values and eta stds do not have same size");
412// }
413
414// std::vector<double> x0;
415// x0.reserve(p_dim + eta0.size());
416// x0.insert(x0.end(), p0.begin(), p0.end());
417// x0.insert(x0.end(), eta0.begin(), eta0.end());
418
419// std::vector<double> scale_hints;
420// scale_hints.reserve(x0.size());
421
422// for (std::size_t i = 0; i < p_dim; ++i) {
423// double hint = std::fabs(p0[i]);
424// if (hint < 1e-3) hint = 0.01;
425// scale_hints.push_back(hint);
426// }
427// for (double s : eta_scales) {
428// scale_hints.push_back(std::max(1e-12, std::fabs(s)));
429// }
430
431// std::vector<std::string> names;
432// names.reserve(x0.size());
433// for (const auto& pid : p_ids) names.push_back(stream_str(pid));
434// for (const auto& pid : eta_ids) names.push_back(stream_str(pid));
435
436// std::vector<ParamLimit> limits;
437
438// // bornes sur les paramètres de fit
439// for (std::size_t i = 0; i < p_dim; ++i) {
440// if (names[i].find("FCONST") != std::string::npos) {
441// limits.push_back(ParamLimit{i, 0.05, 0.35});
442// }
443// }
444
445// // bornes sur certains nuisances sensibles / positifs
446// for (std::size_t i = p_dim; i < names.size(); ++i) {
447// const std::string& nm = names[i];
448// const double c = x0[i];
449// const double s = std::max(1e-12, std::fabs(scale_hints[i]));
450
451// if (nm.find("SMINPUTS:3") != std::string::npos) {
452// limits.push_back(ParamLimit{i, 0.05, 0.30});
453// } else if (nm.find("MASS:") != std::string::npos ||
454// nm.find("FLIFE:") != std::string::npos ||
455// nm.find("FCONST:") != std::string::npos ||
456// nm.find("FMASS:") != std::string::npos ||
457// nm.find("SMINPUTS:5") != std::string::npos ||
458// nm.find("SMINPUTS:6") != std::string::npos) {
459// limits.push_back(ParamLimit{i, std::max(1e-12, c - 5.0 * s), c + 5.0 * s});
460// }
461// }
462
463// auto f_joint = make_joint_f(p_dim);
464
465// MinuitFitOptions opt;
466// opt.up = 0.5;
467// opt.strategy = strategy_;
468// opt.max_fcn = static_cast<unsigned>(max_fcn_);
469// opt.tolerance = tolerance_;
470// opt.run_hesse = true;
471// opt.verbose = true;
472
473// MinuitJointFit mj = minuit_migrad_hesse(
474// f_joint, names, x0, scale_hints, limits, opt
475// );
476
477// FitResult fr;
478// fr.ell_hat = mj.fmin;
479
480// fr.p_hat.assign(mj.x_hat.begin(), mj.x_hat.begin() + p_dim);
481// fr.eta_hat.assign(mj.x_hat.begin() + p_dim, mj.x_hat.end());
482
483// fr.p_hat_std.assign(p_dim, 0.0);
484// fr.p_hat_correlations = RealMatrix(p_dim, p_dim);
485
486// if (mj.has_valid_covar) {
487// for (std::size_t i = 0; i < p_dim; ++i) {
488// fr.p_hat_std[i] = std::sqrt(std::max(0.0, mj.cov.at(i, i)));
489// }
490
491// for (std::size_t i = 0; i < p_dim; ++i) {
492// for (std::size_t j = 0; j < p_dim; ++j) {
493// const double di = std::sqrt(std::max(0.0, mj.cov.at(i, i)));
494// const double dj = std::sqrt(std::max(0.0, mj.cov.at(j, j)));
495// fr.p_hat_correlations.at(i, j) =
496// (di > 0.0 && dj > 0.0) ? (mj.cov.at(i, j) / (di * dj)) : 0.0;
497// }
498// }
499// } else {
500// for (std::size_t i = 0; i < p_dim && i < mj.x_err.size(); ++i) {
501// fr.p_hat_std[i] = mj.x_err[i];
502// fr.p_hat_correlations.at(i, i) = 1.0;
503// }
504// }
505
506// return JointFitOutput{fr, mj, names, scale_hints, limits};
507// }
508
509// private:
510// double nll(const Vector& p, const Vector& eta) const {
511// Vector pred = model_(p, eta);
512
513// Vector r(pred.size());
514// for (std::size_t i = 0; i < pred.size(); ++i) {
515// r[i] = pred[i] - like_.exp_obs_values[i];
516// }
517
518// const double ell_obs = like_.exp_obs_dist->logpdf(r);
519// const double ell_eta = like_.nuisance_dist->logpdf(eta);
520
521// return -(ell_obs + ell_eta);
522// }
523
524// LikelihoodContext like_;
525// ModelFn model_;
526// std::size_t max_fcn_;
527// double tolerance_;
528// unsigned strategy_;
529// };
530
531// // -----------------------------------------------------------------------------
532// // main
533// // -----------------------------------------------------------------------------
534
535int main(int argc, char** argv) {
536// HyperisoMaster hyp;
537// HyperisoConfig config_hyp;
538// config_hyp.model = Model::SM;
539// hyp.init("lha/si_input.flha", config_hyp);
540
541// auto oint = std::make_shared<ObservableInterface>();
542// oint->add_observable(ObservableMapper::to_id(Observables::BR_BS_MUMU_UNTAG), QCDOrder::LO, true)
543// .add_observable(ObservableMapper::to_id(Observables::BR_BD_MUMU), QCDOrder::LO, true);
544
545// StatisticConfig config;
546// config.MC_draws = 100;
547
548// // Ici ces valeurs pilotent Minuit
549// config.MLE_max_iter = 120000;
550// config.MLE_tol = 0.2;
551
552// std::vector<ParamId> p_specs = {
553// ParamId{ParameterType::FLAVOR, "FCONST", {511, 1}},
554// ParamId{ParameterType::FLAVOR, "FCONST", {531, 1}}
555// };
556
557// std::shared_ptr<IStatParamOptimizerProxy> spop = std::make_shared<StatParamOptimizerProxy>();
558// auto model = std::make_shared<ObservableInterfaceProxy>(oint, spop);
559
560// std::shared_ptr<INuisancePathsProvider> npp = std::make_shared<DefaultNuisancePathsProvider>();
561
562// StatisticManager stat(
563// config,
564// model,
565// std::make_shared<StatCorrelationProxy>(),
566// std::make_shared<StatParameterProxy>(),
567// std::make_shared<StatParamSourcesProxy>(),
568// std::make_shared<StatDependencyPruner>(),
569// std::make_shared<NuisanceReader>(npp),
570// spop
571// );
572
573// LOG_INFO("fill_cache #1");
574// // stat.fill_cache();
575
576// auto start_u = std::chrono::steady_clock::now();
577// stat.compute_uncertainties();
578// auto stop_u = std::chrono::steady_clock::now();
579
580// auto us_u = std::chrono::duration_cast<std::chrono::microseconds>(stop_u - start_u).count();
581// std::cout << "Uncertainty estimation time: " << us_u << " us\n";
582
583// LOG_INFO("fill_cache #2");
584// // stat.fill_cache();
585
586// auto p_specs_map = stat.get_p_specs(p_specs);
587// auto eta_specs_real = stat.get_all_obss_deps();
588// for (const auto& [pid, _] : p_specs_map) eta_specs_real.erase(pid);
589// auto exp_obs_map = stat.get_obs_exp();
590
591// auto unz_p = unzip(p_specs_map);
592// auto unz_eta = unzip(eta_specs_real);
593// auto unz_obs = unzip(exp_obs_map);
594
595// std::vector<ParamId> p_ids = unz_p.ids;
596// std::vector<ParamId> eta_ids = unz_eta.ids;
597// std::vector<ExperimentObs> obs_ids = unz_obs.ids;
598
599// auto nuisance_dist = stat.build_nuisance_distribution();
600// auto exp_obs_dist = stat.build_exp_data_distribution();
601
602// if (nuisance_dist->get_stds().size() != unz_eta.vals.size()) {
603// std::cerr << "[ERROR] nuisance std size = " << nuisance_dist->get_stds().size()
604// << " but eta central size = " << unz_eta.vals.size() << "\n";
605// return 3;
606// }
607
608// if (exp_obs_dist->dim() != unz_obs.vals.size()) {
609// std::cerr << "[ERROR] exp obs dim = " << exp_obs_dist->dim()
610// << " but values size = " << unz_obs.vals.size() << "\n";
611// return 4;
612// }
613
614// LikelihoodContext ctx;
615// ctx.nuisance_dist = std::move(nuisance_dist);
616// ctx.exp_obs_dist = std::move(exp_obs_dist);
617// // ctx.nuisance_central_values = unz_eta.vals;
618// ctx.exp_obs_values = unz_obs.vals;
619
620// auto model_fn = [model, obs_ids, p_ids, eta_ids](const Vec& p_vec, const Vec& eta_vec) -> Vec {
621// auto pred_map = model->predict_optimized(zip(p_ids, p_vec), zip(eta_ids, eta_vec));
622
623// Vec out;
624// out.reserve(obs_ids.size());
625
626// for (const auto& bid : obs_ids) {
627// const auto& vec = pred_map.at(bid.obs.s);
628
629// auto it = std::find_if(vec.begin(), vec.end(), [&](const ObservableValue& ov) {
630// auto bin = ov.bin.value_or(std::pair<double, double>{0., 0.});
631// return bin == bid.obs.p;
632// });
633
634// if (it == vec.end()) {
635// throw std::runtime_error("Missing predicted observable/bin");
636// }
637
638// out.push_back(it->value);
639// }
640
641// return out;
642// };
643
644// auto start_m = std::chrono::steady_clock::now();
645// MinuitMLEstimatorLocal est(std::move(ctx), model_fn, config.MLE_max_iter, config.MLE_tol, 2);
646// JointFitOutput fit_out = est.fit_joint_with_minuit(p_ids, eta_ids, unz_p.vals);
647// auto stop_m = std::chrono::steady_clock::now();
648
649// const auto& fr = fit_out.fr;
650// const auto& mj = fit_out.mj;
651
652// auto us_m = std::chrono::duration_cast<std::chrono::microseconds>(stop_m - start_m).count();
653// std::cout << "\nMLE (Minuit) fitting time: " << us_m << " us\n";
654
655// std::cout << "ell_hat = " << std::setprecision(17) << fr.ell_hat << "\n";
656// std::cout << "p_hat = "; print_vec(fr.p_hat);
657// std::cout << "p_hat_std = "; print_vec(fr.p_hat_std);
658// std::cout << "p_hat_correlations:\n" << fr.p_hat_correlations << "\n";
659
660// if (!mj.ok) {
661// std::cerr << "[ERROR] Minuit fit invalid.\n";
662// return 5;
663// }
664
665// if (!mj.has_valid_covar || !mj.has_posdef_covar) {
666// std::cerr << "[WARN] Covariance is not fully healthy."
667// << " valid=" << mj.has_valid_covar
668// << " posdef=" << mj.has_posdef_covar
669// << " accurate=" << mj.has_accurate_covar
670// << " cond=" << mj.cond_number << "\n";
671// }
672
673// {
674// std::vector<std::string> p_names;
675// for (const auto& pid : p_ids) p_names.push_back(stream_str(pid));
676// save_bestfit_csv("bestfit.csv", p_names, fr.p_hat, fr.p_hat_std);
677// std::cout << "[INFO] Wrote bestfit.csv\n";
678// }
679
680// // -------------------------------------------------------------------------
681// // Contour 2D uniquement par grille profilée
682// // -------------------------------------------------------------------------
683// if (p_ids.size() == 2) {
684// const std::string xname = stream_str(p_ids[0]);
685// const std::string yname = stream_str(p_ids[1]);
686
687// const unsigned px = 0;
688// const unsigned py = 1;
689
690// double x0 = fr.p_hat[0];
691// double y0 = fr.p_hat[1];
692// double sx = std::max(0.01, fr.p_hat_std[0]);
693// double sy = std::max(0.01, fr.p_hat_std[1]);
694
695// double xlo = std::max(0.05, x0 - 4.0 * sx);
696// double xhi = std::min(0.35, x0 + 4.0 * sx);
697// double ylo = std::max(0.05, y0 - 4.0 * sy);
698// double yhi = std::min(0.35, y0 + 4.0 * sy);
699
700// if (!(xhi > xlo)) { xlo = 0.10; xhi = 0.30; }
701// if (!(yhi > ylo)) { ylo = 0.10; yhi = 0.30; }
702
703// // Grille raisonnable et profilage léger
704// const std::size_t nx = 31;
705// const std::size_t ny = 31;
706// const unsigned profile_strategy = 1;
707// const unsigned profile_max_fcn = 1200;
708// const double profile_tol = 0.5;
709
710// std::vector<double> xs = linspace(xlo, xhi, nx);
711// std::vector<double> ys = linspace(ylo, yhi, ny);
712// std::vector<double> z(nx * ny, 1e300);
713
714// auto f_joint = est.make_joint_f(/*p_dim=*/2);
715
716// // warm-start : on repart du best-fit global
717// std::vector<double> seed = mj.x_hat;
718
719// for (std::size_t iy = 0; iy < ny; ++iy) {
720// bool reverse = (iy % 2 == 1);
721
722// if (!reverse) {
723// for (std::size_t ix = 0; ix < nx; ++ix) {
724// auto pr = profiled_fit_at_fixed_xy(
725// f_joint,
726// fit_out.names,
727// seed,
728// fit_out.scale_hints,
729// fit_out.limits,
730// px, py,
731// xs[ix], ys[iy],
732// profile_strategy,
733// profile_max_fcn,
734// profile_tol
735// );
736
737// z[iy * nx + ix] = pr.fmin - fr.ell_hat;
738// seed = pr.x_hat;
739// }
740// } else {
741// for (std::size_t k = 0; k < nx; ++k) {
742// std::size_t ix = nx - 1 - k;
743
744// auto pr = profiled_fit_at_fixed_xy(
745// f_joint,
746// fit_out.names,
747// seed,
748// fit_out.scale_hints,
749// fit_out.limits,
750// px, py,
751// xs[ix], ys[iy],
752// profile_strategy,
753// profile_max_fcn,
754// profile_tol
755// );
756
757// z[iy * nx + ix] = pr.fmin - fr.ell_hat;
758// seed = pr.x_hat;
759// }
760// }
761
762// std::cout << "[INFO] grid row " << (iy + 1) << "/" << ny << " done\n";
763// }
764
765// save_grid_csv("grid.csv", xname, yname, xs, ys, z);
766// std::cout << "[INFO] Wrote grid.csv\n";
767
768// // diagnostic : minimum trouvé sur la grille
769// double best_grid = 1e300;
770// std::size_t best_ix = 0;
771// std::size_t best_iy = 0;
772
773// for (std::size_t iy = 0; iy < ny; ++iy) {
774// for (std::size_t ix = 0; ix < nx; ++ix) {
775// double val = z[iy * nx + ix];
776// if (val < best_grid) {
777// best_grid = val;
778// best_ix = ix;
779// best_iy = iy;
780// }
781// }
782// }
783
784// std::cout << "[INFO] grid min delta_nll = " << best_grid
785// << " at (" << xs[best_ix] << ", " << ys[best_iy] << ")\n";
786// std::cout << "[INFO] best-fit = (" << fr.p_hat[0] << ", " << fr.p_hat[1] << ")\n";
787// }
788
789 return 0;
790}