Hyperiso 1.0.3
Modular flavour-physics calculations, Wilson coefficients and statistical inference
Loading...
Searching...
No Matches
test_gaussian_contour.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 <utility>
14// #include <vector>
15
16// #include "StatisticManager.h"
17// #include "ObservableInterfaceProxy.h"
18// #include "StatCorrelationProxy.h"
19// #include "StatParameterProxy.h"
20// #include "ObservableInterface.h"
21// #include "StatParamSourcesProxy.h"
22// #include "Fit.h"
23// #include "BaseLikelihood.h"
24
25// #include "minuit-cpp/FCNBase.hh"
26// #include "minuit-cpp/FunctionMinimum.hh"
27// #include "minuit-cpp/MnContours.hh"
28// #include "minuit-cpp/MnEigen.hh"
29// #include "minuit-cpp/MnHesse.hh"
30// #include "minuit-cpp/MnMigrad.hh"
31// #include "minuit-cpp/MnUserCovariance.hh"
32// #include "minuit-cpp/MnUserParameters.hh"
33// #include "minuit-cpp/MnUserParameterState.hh"
34
35// namespace M2 = MinuitCpp;
36
37// namespace contour_app {
38
39// // -----------------------------------------------------------------------------
40// // Small utilities
41// // -----------------------------------------------------------------------------
42
43// template <class T>
44// std::string to_string_any(const T& x) {
45// std::ostringstream oss;
46// oss << x;
47// return oss.str();
48// }
49
50// 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// 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// 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// return step;
81// }
82
83// // -----------------------------------------------------------------------------
84// // CSV export
85// // -----------------------------------------------------------------------------
86
87// class CsvExporter {
88// public:
89// static void save_bestfit(const std::string& path,
90// const std::vector<std::string>& names,
91// const Vector& vals,
92// const Vector& errs) {
93// std::ofstream out(path);
94// out << "name,value,error\n";
95// for (std::size_t i = 0; i < vals.size(); ++i) {
96// out << names[i] << ","
97// << std::setprecision(17) << vals[i] << ","
98// << errs[i] << "\n";
99// }
100// }
101
102// static void save_contours(const std::string& path,
103// const std::string& xname,
104// const std::string& yname,
105// const std::vector<std::pair<double, double>>& c68,
106// const std::vector<std::pair<double, double>>& c95) {
107// std::ofstream out(path);
108// out << "# x=" << xname << "\n";
109// out << "# y=" << yname << "\n";
110// out << "cl,x,y\n";
111// for (const auto& p : c68) {
112// out << "0.683," << std::setprecision(17) << p.first << "," << p.second << "\n";
113// }
114// for (const auto& p : c95) {
115// out << "0.95," << std::setprecision(17) << p.first << "," << p.second << "\n";
116// }
117// }
118
119// static void save_grid(const std::string& path,
120// const std::string& xname,
121// const std::string& yname,
122// const std::vector<double>& xs,
123// const std::vector<double>& ys,
124// const std::vector<double>& z) {
125// const std::size_t nx = xs.size();
126// const std::size_t ny = ys.size();
127
128// std::ofstream out(path);
129// out << "# x=" << xname << "\n";
130// out << "# y=" << yname << "\n";
131// out << "x,y,delta_nll\n";
132// out << std::setprecision(17);
133
134// for (std::size_t iy = 0; iy < ny; ++iy) {
135// for (std::size_t ix = 0; ix < nx; ++ix) {
136// out << xs[ix] << "," << ys[iy] << "," << z[iy * nx + ix] << "\n";
137// }
138// }
139// }
140// };
141
142// // -----------------------------------------------------------------------------
143// // Minuit core
144// // -----------------------------------------------------------------------------
145
146// struct ParamLimit {
147// std::size_t idx;
148// double low;
149// double high;
150// };
151
152// struct MinuitFitOptions {
153// double up = 0.5; // NLL => 0.5 for 1D 1σ
154// unsigned strategy = 2;
155// unsigned max_fcn = 100000;
156// double tolerance = 0.2;
157// bool run_hesse = true;
158// unsigned hesse_maxcalls = 0;
159// bool verbose = true;
160// };
161
162// struct MinuitJointFit {
163// Vector x_hat;
164// std::vector<double> x_err;
165// std::vector<double> cov_eigs;
166// double cond_number = std::numeric_limits<double>::infinity();
167
168// double fmin = std::numeric_limits<double>::quiet_NaN();
169// double edm = std::numeric_limits<double>::quiet_NaN();
170// int nfcn = -1;
171
172// bool ok = false;
173// bool has_valid_covar = false;
174// bool has_posdef_covar = false;
175// bool has_accurate_covar = false;
176// bool made_posdef = false;
177
178// RealMatrix cov;
179// std::shared_ptr<M2::FunctionMinimum> min;
180// };
181
182// class GenericFCN final : public M2::FCNBase {
183// public:
184// GenericFCN(std::function<double(const std::vector<double>&)> f, double up)
185// : f_(std::move(f)), up_(up) {}
186
187// double operator()(const std::vector<double>& x) const override {
188// try {
189// const double v = f_(x);
190// return std::isfinite(v) ? v : 1e300;
191// } catch (...) {
192// return 1e300;
193// }
194// }
195
196// double Up() const override { return up_; }
197
198// private:
199// std::function<double(const std::vector<double>&)> f_;
200// double up_;
201// };
202
203// class MinuitRunner {
204// public:
205// static MinuitJointFit fit(const std::function<double(const std::vector<double>&)>& f,
206// const std::vector<std::string>& names,
207// const std::vector<double>& x0,
208// const std::vector<double>& scale_hints,
209// const std::vector<ParamLimit>& limits,
210// const MinuitFitOptions& opt) {
211// if (x0.size() != names.size() || x0.size() != scale_hints.size()) {
212// throw std::invalid_argument("MinuitRunner::fit: names/x0/scale_hints size mismatch");
213// }
214
215// GenericFCN fcn(f, opt.up);
216
217// M2::MnUserParameters upar;
218// for (std::size_t i = 0; i < x0.size(); ++i) {
219// const double step = safe_step(x0[i], scale_hints[i]);
220// upar.Add(names[i].c_str(), x0[i], step);
221// }
222
223// for (const auto& lim : limits) {
224// if (lim.idx < names.size()) {
225// upar.SetLimits(names[lim.idx].c_str(), lim.low, lim.high);
226// }
227// }
228
229// M2::MnMigrad migrad(fcn, upar, opt.strategy);
230// M2::FunctionMinimum min = migrad(opt.max_fcn, opt.tolerance);
231
232// if (opt.run_hesse) {
233// M2::MnHesse hesse(opt.strategy);
234// hesse(fcn, min, opt.hesse_maxcalls);
235// }
236
237// if (opt.verbose) {
238// log_summary("MIGRAD+HESSE", min);
239// }
240
241// return extract_result(min, names, x0.size(), opt.verbose);
242// }
243
244// static void log_summary(const std::string& tag, const M2::FunctionMinimum& min) {
245// std::cout << "\n=== [" << tag << "] FunctionMinimum ===\n";
246// std::cout << "IsValid = " << min.IsValid() << "\n";
247// std::cout << "HasValidParameters = " << min.HasValidParameters() << "\n";
248// std::cout << "HasValidCovariance = " << min.HasValidCovariance() << "\n";
249// std::cout << "HasAccurateCovar = " << min.HasAccurateCovar() << "\n";
250// std::cout << "HasPosDefCovar = " << min.HasPosDefCovar() << "\n";
251// std::cout << "HasMadePosDefCovar = " << min.HasMadePosDefCovar() << "\n";
252// std::cout << "HesseFailed = " << min.HesseFailed() << "\n";
253// std::cout << "Fval = " << std::setprecision(17) << min.Fval() << "\n";
254// std::cout << "EDM = " << std::setprecision(17) << min.Edm() << "\n";
255// std::cout << "NFcn = " << min.NFcn() << "\n";
256// std::cout << "Up = " << min.Up() << "\n";
257// }
258
259// private:
260// static MinuitJointFit extract_result(const M2::FunctionMinimum& min,
261// const std::vector<std::string>& names,
262// std::size_t n,
263// bool verbose) {
264// std::vector<double> eigs;
265// double cond = std::numeric_limits<double>::infinity();
266
267// if (min.HasValidCovariance()) {
268// M2::MnEigen eigen;
269// eigs = eigen(min.UserState().Covariance());
270
271// double min_pos = std::numeric_limits<double>::infinity();
272// double max_pos = 0.0;
273// for (double e : eigs) {
274// if (std::isfinite(e) && e > 0.0) {
275// min_pos = std::min(min_pos, e);
276// max_pos = std::max(max_pos, e);
277// }
278// }
279// if (min_pos < std::numeric_limits<double>::infinity() && max_pos > 0.0) {
280// cond = max_pos / min_pos;
281// }
282
283// if (verbose && !eigs.empty()) {
284// std::cout << "Cov eigen min/max = "
285// << std::setprecision(6) << eigs.front()
286// << " / " << eigs.back()
287// << " (cond ~ " << cond << ")\n";
288// }
289// }
290
291// MinuitJointFit out;
292// out.fmin = min.Fval();
293// out.edm = min.Edm();
294// out.nfcn = min.NFcn();
295// out.ok = min.IsValid();
296
297// out.has_valid_covar = min.HasValidCovariance();
298// out.has_posdef_covar = min.HasPosDefCovar();
299// out.has_accurate_covar = min.HasAccurateCovar();
300// out.made_posdef = min.HasMadePosDefCovar();
301
302// out.cov_eigs = std::move(eigs);
303// out.cond_number = cond;
304// out.min = std::make_shared<M2::FunctionMinimum>(min);
305
306// const auto& st = min.UserState();
307// out.x_hat.resize(n);
308// out.x_err.assign(n, 0.0);
309// out.cov = RealMatrix(n, n);
310
311// for (std::size_t i = 0; i < n; ++i) {
312// out.x_hat[i] = st.Value(names[i].c_str());
313// out.x_err[i] = st.Error(names[i].c_str());
314// }
315
316// if (min.HasValidCovariance()) {
317// const auto& cov = st.Covariance();
318// for (std::size_t i = 0; i < n; ++i) {
319// for (std::size_t j = 0; j < n; ++j) {
320// out.cov.at(i, j) = cov(i, j);
321// }
322// }
323// }
324
325// return out;
326// }
327// };
328
329// // -----------------------------------------------------------------------------
330// // Problem / fit domain objects
331// // -----------------------------------------------------------------------------
332
333// struct JointLikelihoodProblem {
334// std::vector<std::string> names;
335// std::vector<double> x0;
336// std::vector<double> scale_hints;
337// std::vector<ParamLimit> limits;
338// std::function<double(const std::vector<double>&)> f_joint;
339// };
340
341// struct JointFitOutput {
342// FitResult fr;
343// MinuitJointFit mj;
344// JointLikelihoodProblem problem;
345// };
346
347// class MinuitMLEstimatorLocal {
348// public:
349// // using ModelFn = ProfiledLikelihood::ModelFn;
350
351// MinuitMLEstimatorLocal(LikelihoodContext ctx,
352// ModelFn model,
353// std::size_t max_fcn,
354// double tolerance,
355// unsigned strategy)
356// : like_(std::move(ctx))
357// , model_(std::move(model))
358// , max_fcn_(max_fcn)
359// , tolerance_(tolerance)
360// , strategy_(strategy) {}
361
362// std::function<double(const std::vector<double>&)> make_joint_f(std::size_t p_dim) const {
363// return [this, p_dim](const std::vector<double>& x) -> double {
364// Vector p(x.begin(), x.begin() + p_dim);
365// Vector eta(x.begin() + p_dim, x.end());
366// return nll(p, eta);
367// };
368// }
369
370// JointFitOutput fit_joint_with_minuit(const std::vector<ParamId>& p_ids,
371// const std::vector<ParamId>& eta_ids,
372// const Vector& p0) const {
373// const std::size_t p_dim = p0.size();
374// Vector eta0;
375// for (auto elem : like_.nuis_defs) {
376// eta0.push_back(elem.value);
377// }
378// // Vector eta0 = like_.nuisance_central_values;
379// Vector eta_scales = like_.nuisance_dist->get_stds();
380
381// if (eta0.size() != eta_scales.size()) {
382// throw std::runtime_error("eta central values and eta stds do not have same size");
383// }
384
385// JointLikelihoodProblem problem;
386// problem.x0.reserve(p_dim + eta0.size());
387// problem.x0.insert(problem.x0.end(), p0.begin(), p0.end());
388// problem.x0.insert(problem.x0.end(), eta0.begin(), eta0.end());
389
390// problem.scale_hints.reserve(problem.x0.size());
391// for (std::size_t i = 0; i < p_dim; ++i) {
392// double hint = std::fabs(p0[i]);
393// if (hint < 1e-3) hint = 0.01;
394// problem.scale_hints.push_back(hint);
395// }
396// for (double s : eta_scales) {
397// problem.scale_hints.push_back(std::max(1e-12, std::fabs(s)));
398// }
399
400// problem.names.reserve(problem.x0.size());
401// for (const auto& pid : p_ids) problem.names.push_back(to_string_any(pid));
402// for (const auto& pid : eta_ids) problem.names.push_back(to_string_any(pid));
403
404// for (std::size_t i = 0; i < p_dim; ++i) {
405// if (problem.names[i].find("FCONST") != std::string::npos) {
406// problem.limits.push_back(ParamLimit{i, 0.05, 0.35});
407// }
408// }
409
410// for (std::size_t i = p_dim; i < problem.names.size(); ++i) {
411// const std::string& nm = problem.names[i];
412// const double c = problem.x0[i];
413// const double s = std::max(1e-12, std::fabs(problem.scale_hints[i]));
414
415// if (nm.find("SMINPUTS:3") != std::string::npos) {
416// problem.limits.push_back(ParamLimit{i, 0.05, 0.30});
417// } else if (nm.find("MASS:") != std::string::npos ||
418// nm.find("FLIFE:") != std::string::npos ||
419// nm.find("FCONST:") != std::string::npos ||
420// nm.find("FMASS:") != std::string::npos ||
421// nm.find("SMINPUTS:5") != std::string::npos ||
422// nm.find("SMINPUTS:6") != std::string::npos) {
423// problem.limits.push_back(ParamLimit{i, std::max(1e-12, c - 5.0 * s), c + 5.0 * s});
424// }
425// }
426
427// problem.f_joint = make_joint_f(p_dim);
428
429// MinuitFitOptions opt;
430// opt.up = 0.5;
431// opt.strategy = strategy_;
432// opt.max_fcn = static_cast<unsigned>(max_fcn_);
433// opt.tolerance = tolerance_;
434// opt.run_hesse = true;
435// opt.verbose = true;
436
437// MinuitJointFit mj = MinuitRunner::fit(problem.f_joint,
438// problem.names,
439// problem.x0,
440// problem.scale_hints,
441// problem.limits,
442// opt);
443
444// FitResult fr;
445// fr.ell_hat = mj.fmin;
446// fr.p_hat.assign(mj.x_hat.begin(), mj.x_hat.begin() + p_dim);
447// fr.eta_hat.assign(mj.x_hat.begin() + p_dim, mj.x_hat.end());
448// fr.p_hat_std.assign(p_dim, 0.0);
449// fr.p_hat_correlations = RealMatrix(p_dim, p_dim);
450
451// if (mj.has_valid_covar) {
452// for (std::size_t i = 0; i < p_dim; ++i) {
453// fr.p_hat_std[i] = std::sqrt(std::max(0.0, mj.cov.at(i, i)));
454// }
455
456// for (std::size_t i = 0; i < p_dim; ++i) {
457// for (std::size_t j = 0; j < p_dim; ++j) {
458// const double di = std::sqrt(std::max(0.0, mj.cov.at(i, i)));
459// const double dj = std::sqrt(std::max(0.0, mj.cov.at(j, j)));
460// fr.p_hat_correlations.at(i, j) =
461// (di > 0.0 && dj > 0.0) ? (mj.cov.at(i, j) / (di * dj)) : 0.0;
462// }
463// }
464// } else {
465// for (std::size_t i = 0; i < p_dim && i < mj.x_err.size(); ++i) {
466// fr.p_hat_std[i] = mj.x_err[i];
467// fr.p_hat_correlations.at(i, i) = 1.0;
468// }
469// }
470
471// return JointFitOutput{fr, mj, problem};
472// }
473
474// private:
475// double nll(const Vector& p, const Vector& eta) const {
476// Vector pred = model_(p, eta);
477
478// Vector r(pred.size());
479// for (std::size_t i = 0; i < pred.size(); ++i) {
480// r[i] = pred[i] - like_.exp_obs_values[i];
481// }
482
483// const double ell_obs = like_.exp_obs_dist->logpdf(r);
484// const double ell_eta = like_.nuisance_dist->logpdf(eta);
485// return -(ell_obs + ell_eta);
486// }
487
488// LikelihoodContext like_;
489// ModelFn model_;
490// std::size_t max_fcn_;
491// double tolerance_;
492// unsigned strategy_;
493// };
494
495// // -----------------------------------------------------------------------------
496// // Contour strategies
497// // -----------------------------------------------------------------------------
498
499// struct ContourComputationInput {
500// std::string xname;
501// std::string yname;
502// unsigned px = 0;
503// unsigned py = 1;
504// double best_fval = 0.0;
505// Vector p_hat;
506// Vector p_std;
507// JointLikelihoodProblem problem;
508// MinuitJointFit best_fit;
509// };
510
511// struct ContourComputationResult {
512// bool success = false;
513// bool used_grid = false;
514// std::vector<std::pair<double, double>> c68;
515// std::vector<std::pair<double, double>> c95;
516// std::vector<double> xs;
517// std::vector<double> ys;
518// std::vector<double> z;
519// };
520
521// struct ProfileXYResult {
522// double fmin = 1e300;
523// std::vector<double> x_hat;
524// bool ok = false;
525// };
526
527// static ProfileXYResult profiled_fit_at_fixed_xy(
528// const std::function<double(const std::vector<double>&)>& f_joint,
529// const std::vector<std::string>& names,
530// const std::vector<double>& x_start,
531// const std::vector<double>& scale_hints,
532// const std::vector<ParamLimit>& limits,
533// unsigned px,
534// unsigned py,
535// double xval,
536// double yval,
537// unsigned strategy,
538// unsigned max_fcn,
539// double tolerance
540// ) {
541// GenericFCN fcn(f_joint, 0.5);
542
543// M2::MnUserParameters upar;
544// for (std::size_t i = 0; i < x_start.size(); ++i) {
545// upar.Add(names[i].c_str(), x_start[i], safe_step(x_start[i], scale_hints[i]));
546// }
547
548// for (const auto& lim : limits) {
549// if (lim.idx < names.size()) {
550// upar.SetLimits(names[lim.idx].c_str(), lim.low, lim.high);
551// }
552// }
553
554// M2::MnMigrad migrad(fcn, upar, strategy);
555// migrad.SetValue(px, xval);
556// migrad.SetValue(py, yval);
557// migrad.Fix(px);
558// migrad.Fix(py);
559
560// M2::FunctionMinimum min = migrad(max_fcn, tolerance);
561
562// ProfileXYResult out;
563// out.ok = min.IsValid();
564// out.fmin = out.ok ? min.Fval() : 1e300;
565// out.x_hat = x_start;
566
567// if (out.ok) {
568// const auto& st = min.UserState();
569// for (std::size_t i = 0; i < x_start.size(); ++i) {
570// out.x_hat[i] = st.Value(names[i].c_str());
571// }
572// } else {
573// out.x_hat[px] = xval;
574// out.x_hat[py] = yval;
575// out.fmin = f_joint(out.x_hat);
576// }
577
578// return out;
579// }
580
581// class IContourStrategy {
582// public:
583// virtual ~IContourStrategy() = default;
584// virtual ContourComputationResult compute(const ContourComputationInput& input) const = 0;
585// };
586
587// class MnContoursStrategy final : public IContourStrategy {
588// public:
589// struct Options {
590// unsigned npoints = 80;
591// unsigned refit_max_fcn = 30000;
592// double tolerance = 0.2;
593// unsigned strategy = 2;
594// };
595
596// MnContoursStrategy() = default;
597// explicit MnContoursStrategy(const Options& options) : opt_(options) {}
598
599// ContourComputationResult compute(const ContourComputationInput& input) const override {
600// ContourComputationResult result;
601// if (!input.best_fit.ok || !input.best_fit.min) {
602// return result;
603// }
604
605// const bool ok68 = compute_single(input, 2.30 / 2.0, result.c68);
606// const bool ok95 = compute_single(input, 5.99 / 2.0, result.c95);
607
608// result.success = ok68 && ok95;
609// result.used_grid = false;
610// return result;
611// }
612
613// private:
614// bool compute_single(const ContourComputationInput& input,
615// double up_contour,
616// std::vector<std::pair<double, double>>& out_points) const {
617// std::vector<double> refit_scales = input.problem.scale_hints;
618// const double scale_factor = std::sqrt(up_contour / 0.5);
619
620// for (std::size_t i = 0; i < refit_scales.size() && i < input.best_fit.x_err.size(); ++i) {
621// const double err = std::fabs(input.best_fit.x_err[i]);
622// if (std::isfinite(err) && err > 0.0) {
623// refit_scales[i] = std::max(refit_scales[i], err * scale_factor);
624// }
625// }
626
627// MinuitFitOptions refit_opt;
628// refit_opt.up = up_contour;
629// refit_opt.strategy = opt_.strategy;
630// refit_opt.max_fcn = opt_.refit_max_fcn;
631// refit_opt.tolerance = opt_.tolerance;
632// refit_opt.run_hesse = true;
633// refit_opt.verbose = false;
634
635// MinuitJointFit refit = MinuitRunner::fit(input.problem.f_joint,
636// input.problem.names,
637// input.best_fit.x_hat,
638// refit_scales,
639// input.problem.limits,
640// refit_opt);
641
642// if (!refit.ok || !refit.min) {
643// return false;
644// }
645
646// try {
647// GenericFCN fcn(input.problem.f_joint, up_contour);
648// M2::MnContours contours(fcn, *refit.min, 2);
649// out_points = contours(input.px, input.py, opt_.npoints);
650// return out_points.size() >= 4;
651// } catch (...) {
652// out_points.clear();
653// return false;
654// }
655// }
656
657// Options opt_{};
658// };
659
660// class GridProfileContourStrategy final : public IContourStrategy {
661// public:
662// struct Options {
663// std::size_t nx = 31;
664// std::size_t ny = 31;
665// unsigned strategy = 1;
666// unsigned max_fcn = 1200;
667// double tolerance = 0.5;
668// double n_sigma_window = 4.0;
669// double hard_low = 0.05;
670// double hard_high = 0.35;
671// };
672
673// GridProfileContourStrategy() = default;
674// explicit GridProfileContourStrategy(const Options& options) : opt_(options) {}
675
676// ContourComputationResult compute(const ContourComputationInput& input) const override {
677// ContourComputationResult result;
678// result.used_grid = true;
679
680// double x0 = input.p_hat.at(0);
681// double y0 = input.p_hat.at(1);
682// double sx = std::max(0.01, input.p_std.at(0));
683// double sy = std::max(0.01, input.p_std.at(1));
684
685// double xlo = std::max(opt_.hard_low, x0 - opt_.n_sigma_window * sx);
686// double xhi = std::min(opt_.hard_high, x0 + opt_.n_sigma_window * sx);
687// double ylo = std::max(opt_.hard_low, y0 - opt_.n_sigma_window * sy);
688// double yhi = std::min(opt_.hard_high, y0 + opt_.n_sigma_window * sy);
689
690// if (!(xhi > xlo)) { xlo = 0.10; xhi = 0.30; }
691// if (!(yhi > ylo)) { ylo = 0.10; yhi = 0.30; }
692
693// result.xs = linspace(xlo, xhi, opt_.nx);
694// result.ys = linspace(ylo, yhi, opt_.ny);
695// result.z.assign(opt_.nx * opt_.ny, 1e300);
696
697// std::vector<double> seed = input.best_fit.x_hat;
698
699// for (std::size_t iy = 0; iy < opt_.ny; ++iy) {
700// const bool reverse = (iy % 2 == 1);
701
702// if (!reverse) {
703// for (std::size_t ix = 0; ix < opt_.nx; ++ix) {
704// auto pr = profile_at_fixed_xy(input,
705// seed,
706// result.xs[ix],
707// result.ys[iy]);
708// result.z[iy * opt_.nx + ix] = pr.fmin - input.best_fval;
709// seed = pr.x_hat;
710// }
711// } else {
712// for (std::size_t k = 0; k < opt_.nx; ++k) {
713// std::size_t ix = opt_.nx - 1 - k;
714// auto pr = profile_at_fixed_xy(input,
715// seed,
716// result.xs[ix],
717// result.ys[iy]);
718// result.z[iy * opt_.nx + ix] = pr.fmin - input.best_fval;
719// seed = pr.x_hat;
720// }
721// }
722// }
723
724// result.success = true;
725// return result;
726// }
727
728// private:
729// ProfileXYResult profile_at_fixed_xy(const ContourComputationInput& input,
730// const std::vector<double>& seed,
731// double xval,
732// double yval) const {
733// return profiled_fit_at_fixed_xy(input.problem.f_joint,
734// input.problem.names,
735// seed,
736// input.problem.scale_hints,
737// input.problem.limits,
738// input.px,
739// input.py,
740// xval,
741// yval,
742// opt_.strategy,
743// opt_.max_fcn,
744// opt_.tolerance);
745// }
746
747// Options opt_{};
748// };
749
750// class FallbackContourStrategy final : public IContourStrategy {
751// public:
752// FallbackContourStrategy(std::unique_ptr<IContourStrategy> primary,
753// std::unique_ptr<IContourStrategy> fallback)
754// : primary_(std::move(primary)), fallback_(std::move(fallback)) {}
755
756// ContourComputationResult compute(const ContourComputationInput& input) const override {
757// ContourComputationResult primary_result = primary_->compute(input);
758// if (primary_result.success) {
759// return primary_result;
760// }
761
762// std::cerr << "[WARN] MnContours failed; fallback to grid scan.\n";
763// return fallback_->compute(input);
764// }
765
766// private:
767// std::unique_ptr<IContourStrategy> primary_;
768// std::unique_ptr<IContourStrategy> fallback_;
769// };
770
771// // -----------------------------------------------------------------------------
772// // Application bootstrap
773// // -----------------------------------------------------------------------------
774
775// struct BuiltProblem {
776// std::vector<ParamId> p_ids;
777// std::vector<ParamId> eta_ids;
778// std::vector<ExperimentObs> obs_ids;
779// LikelihoodContext ctx;
780// std::shared_ptr<ObservableInterfaceProxy> model;
781// Vector p0;
782// };
783
784// BuiltProblem build_problem(StatisticManager& stat,
785// const StatisticConfig& config,
786// const std::shared_ptr<ObservableInterfaceProxy>& model, std::vector<ParamId> p_specs) {
787// LOG_INFO("fill_cache #1");
788// // stat.fill_cache();
789
790// auto start_u = std::chrono::steady_clock::now();
791// stat.compute_uncertainties();
792// auto stop_u = std::chrono::steady_clock::now();
793// auto us_u = std::chrono::duration_cast<std::chrono::microseconds>(stop_u - start_u).count();
794// std::cout << "Uncertainty estimation time: " << us_u << " us\n";
795
796// LOG_INFO("fill_cache #2");
797// // stat.fill_cache();
798
799// auto p_specs_map = stat.get_p_specs(p_specs);
800// auto eta_specs_real = stat.get_all_obss_deps();
801// for (const auto& [pid, _] : p_specs_map) eta_specs_real.erase(pid);
802// auto exp_obs_map = stat.get_obs_exp();
803
804// auto unz_p = unzip(p_specs_map);
805// auto unz_eta = unzip(eta_specs_real);
806// auto unz_obs = unzip(exp_obs_map);
807
808// auto nuisance_dist = stat.build_nuisance_distribution();
809// auto exp_obs_dist = stat.build_exp_data_distribution();
810
811// if (nuisance_dist->get_stds().size() != unz_eta.vals.size()) {
812// throw std::runtime_error("nuisance std size and eta central size mismatch");
813// }
814// if (exp_obs_dist->dim() != unz_obs.vals.size()) {
815// throw std::runtime_error("exp obs dim and exp obs values size mismatch");
816// }
817
818// LikelihoodContext ctx;
819// ctx.nuisance_dist = std::move(nuisance_dist);
820// ctx.exp_obs_dist = std::move(exp_obs_dist);
821// // ctx.nuisance_central_values = unz_eta.vals;
822// ctx.exp_obs_values = unz_obs.vals;
823
824// return BuiltProblem{
825// unz_p.ids,
826// unz_eta.ids,
827// unz_obs.ids,
828// std::move(ctx),
829// model,
830// unz_p.vals
831// };
832// }
833
834
835// struct GaussianToyConfig {
836// double mu_x = 0.20;
837// double mu_y = 0.20;
838// double sigma_x = 0.04;
839// double sigma_y = 0.04;
840// double rho = 0.50;
841// double hard_low = 0.05;
842// double hard_high = 0.35;
843// };
844
845// double gaussian_toy_nll(const std::vector<double>& x, const GaussianToyConfig& cfg) {
846// if (x.size() != 2) {
847// throw std::invalid_argument("gaussian_toy_nll expects exactly 2 parameters");
848// }
849
850// const double dx = x[0] - cfg.mu_x;
851// const double dy = x[1] - cfg.mu_y;
852
853// const double vx = cfg.sigma_x * cfg.sigma_x;
854// const double vy = cfg.sigma_y * cfg.sigma_y;
855// const double cxy = cfg.rho * cfg.sigma_x * cfg.sigma_y;
856
857// const double det = vx * vy - cxy * cxy;
858// if (!(det > 0.0)) {
859// throw std::runtime_error("Gaussian covariance is not positive definite");
860// }
861
862// const double inv00 = vy / det;
863// const double inv11 = vx / det;
864// const double inv01 = -cxy / det;
865
866// return 0.5 * (inv00 * dx * dx + 2.0 * inv01 * dx * dy + inv11 * dy * dy);
867// }
868
869// RealMatrix gaussian_toy_covariance(const GaussianToyConfig& cfg) {
870// RealMatrix cov(2, 2);
871// cov.at(0, 0) = cfg.sigma_x * cfg.sigma_x;
872// cov.at(0, 1) = cfg.rho * cfg.sigma_x * cfg.sigma_y;
873// cov.at(1, 0) = cov.at(0, 1);
874// cov.at(1, 1) = cfg.sigma_y * cfg.sigma_y;
875// return cov;
876// }
877
878// RealMatrix gaussian_toy_hessian(const GaussianToyConfig& cfg) {
879// const double vx = cfg.sigma_x * cfg.sigma_x;
880// const double vy = cfg.sigma_y * cfg.sigma_y;
881// const double cxy = cfg.rho * cfg.sigma_x * cfg.sigma_y;
882// const double det = vx * vy - cxy * cxy;
883
884// RealMatrix h(2, 2);
885// h.at(0, 0) = vy / det;
886// h.at(0, 1) = -cxy / det;
887// h.at(1, 0) = h.at(0, 1);
888// h.at(1, 1) = vx / det;
889// return h;
890// }
891
892// std::vector<std::pair<double, double>>
893// gaussian_toy_contour_points(const GaussianToyConfig& cfg,
894// double delta_nll,
895// std::size_t npoints = 240) {
896// std::vector<std::pair<double, double>> pts;
897// pts.reserve(npoints);
898
899// const double scale = std::sqrt(2.0 * delta_nll);
900// const double sx = cfg.sigma_x;
901// const double sy = cfg.sigma_y;
902// const double rho = cfg.rho;
903// const double rho_perp = std::sqrt(std::max(0.0, 1.0 - rho * rho));
904
905// constexpr double two_pi = 6.2831853071795864769;
906
907// for (std::size_t i = 0; i < npoints; ++i) {
908// const double t = two_pi * double(i) / double(npoints);
909// const double u0 = std::cos(t);
910// const double u1 = std::sin(t);
911
912// const double x = cfg.mu_x + scale * sx * u0;
913// const double y = cfg.mu_y + scale * sy * (rho * u0 + rho_perp * u1);
914// pts.emplace_back(x, y);
915// }
916
917// return pts;
918// }
919
920// std::vector<double> gaussian_toy_grid(const GaussianToyConfig& cfg,
921// const std::vector<double>& xs,
922// const std::vector<double>& ys) {
923// std::vector<double> z(xs.size() * ys.size(), 0.0);
924// for (std::size_t iy = 0; iy < ys.size(); ++iy) {
925// for (std::size_t ix = 0; ix < xs.size(); ++ix) {
926// z[iy * xs.size() + ix] = gaussian_toy_nll({xs[ix], ys[iy]}, cfg);
927// }
928// }
929// return z;
930// }
931
932// void save_gaussian_reference(const std::string& path, const GaussianToyConfig& cfg) {
933// std::ofstream out(path);
934// out << std::setprecision(17);
935// out << "key,value\n";
936// out << "mu_x," << cfg.mu_x << "\n";
937// out << "mu_y," << cfg.mu_y << "\n";
938// out << "sigma_x," << cfg.sigma_x << "\n";
939// out << "sigma_y," << cfg.sigma_y << "\n";
940// out << "rho," << cfg.rho << "\n";
941// out << "delta68," << 2.30 / 2.0 << "\n";
942// out << "delta95," << 5.99 / 2.0 << "\n";
943// }
944
945// } // namespace contour_app
946
947int main(int argc, char** argv) {
948 // using namespace contour_app;
949
950 // (void)argc;
951 // (void)argv;
952
953 // const GaussianToyConfig cfg;
954
955 // std::cout << "=== Gaussian toy validation ===\n";
956 // std::cout << "Expected best-fit = (" << cfg.mu_x << ", " << cfg.mu_y << ")\n";
957 // std::cout << "Expected std = (" << cfg.sigma_x << ", " << cfg.sigma_y << ")\n";
958 // std::cout << "Expected rho = " << cfg.rho << "\n";
959 // std::cout << "Expected covariance:\n" << gaussian_toy_covariance(cfg) << "\n";
960 // std::cout << "Expected Hessian = covariance^{-1}:\n" << gaussian_toy_hessian(cfg) << "\n";
961
962 // JointLikelihoodProblem problem;
963 // problem.names = {"x", "y"};
964 // problem.x0 = {0.12, 0.28};
965 // problem.scale_hints = {cfg.sigma_x, cfg.sigma_y};
966 // problem.limits = {
967 // ParamLimit{0, cfg.hard_low, cfg.hard_high},
968 // ParamLimit{1, cfg.hard_low, cfg.hard_high}
969 // };
970 // problem.f_joint = [cfg](const std::vector<double>& x) {
971 // return gaussian_toy_nll(x, cfg);
972 // };
973
974 // MinuitFitOptions opt;
975 // opt.up = 0.5;
976 // opt.strategy = 2;
977 // opt.max_fcn = 5000;
978 // opt.tolerance = 0.1;
979 // opt.run_hesse = true;
980 // opt.verbose = true;
981
982 // auto start_m = std::chrono::steady_clock::now();
983 // MinuitJointFit mj = MinuitRunner::fit(problem.f_joint,
984 // problem.names,
985 // problem.x0,
986 // problem.scale_hints,
987 // problem.limits,
988 // opt);
989 // auto stop_m = std::chrono::steady_clock::now();
990
991 // auto us_m = std::chrono::duration_cast<std::chrono::microseconds>(stop_m - start_m).count();
992 // std::cout << "\nMLE (Minuit) fitting time: " << us_m << " us\n";
993
994 // Vector p_hat = mj.x_hat;
995 // Vector p_std(2);
996 // RealMatrix p_corr(2, 2);
997
998 // if (mj.has_valid_covar) {
999 // for (std::size_t i = 0; i < 2; ++i) {
1000 // p_std[i] = std::sqrt(std::max(0.0, mj.cov.at(i, i)));
1001 // }
1002 // for (std::size_t i = 0; i < 2; ++i) {
1003 // for (std::size_t j = 0; j < 2; ++j) {
1004 // const double di = std::sqrt(std::max(0.0, mj.cov.at(i, i)));
1005 // const double dj = std::sqrt(std::max(0.0, mj.cov.at(j, j)));
1006 // p_corr.at(i, j) = (di > 0.0 && dj > 0.0) ? mj.cov.at(i, j) / (di * dj) : 0.0;
1007 // }
1008 // }
1009 // } else {
1010 // p_std[0] = mj.x_err.size() > 0 ? mj.x_err[0] : 0.0;
1011 // p_std[1] = mj.x_err.size() > 1 ? mj.x_err[1] : 0.0;
1012 // p_corr.at(0, 0) = 1.0;
1013 // p_corr.at(1, 1) = 1.0;
1014 // p_corr.at(0, 1) = 0.0;
1015 // p_corr.at(1, 0) = 0.0;
1016 // }
1017
1018 // std::cout << "ell_hat = " << std::setprecision(17) << mj.fmin << "\n";
1019 // std::cout << "p_hat = "; print_vec(p_hat);
1020 // std::cout << "p_hat_std = "; print_vec(p_std);
1021 // std::cout << "p_hat_correlations:\n" << p_corr << "\n";
1022
1023 // std::cout << "\nAbsolute differences wrt expected:\n";
1024 // std::cout << "|x_hat - mu_x| = " << std::fabs(p_hat[0] - cfg.mu_x) << "\n";
1025 // std::cout << "|y_hat - mu_y| = " << std::fabs(p_hat[1] - cfg.mu_y) << "\n";
1026 // std::cout << "|sigma_x_fit - sigma_x| = " << std::fabs(p_std[0] - cfg.sigma_x) << "\n";
1027 // std::cout << "|sigma_y_fit - sigma_y| = " << std::fabs(p_std[1] - cfg.sigma_y) << "\n";
1028 // std::cout << "|rho_fit - rho| = " << std::fabs(p_corr.at(0, 1) - cfg.rho) << "\n";
1029 // std::cout << "|sigma_x_fit - sigma_y_fit| = " << std::fabs(p_std[0] - p_std[1]) << "\n";
1030 // std::cout << "|x_hat - y_hat| = " << std::fabs(p_hat[0] - p_hat[1]) << "\n";
1031
1032 // if (!mj.ok) {
1033 // std::cerr << "[ERROR] Minuit fit invalid.\n";
1034 // return 5;
1035 // }
1036
1037 // if (!mj.has_valid_covar || !mj.has_posdef_covar) {
1038 // std::cerr << "[WARN] Covariance is not fully healthy."
1039 // << " valid=" << mj.has_valid_covar
1040 // << " posdef=" << mj.has_posdef_covar
1041 // << " accurate=" << mj.has_accurate_covar
1042 // << " cond=" << mj.cond_number << "\n";
1043 // }
1044
1045 // CsvExporter::save_bestfit("bestfit.csv", problem.names, p_hat, p_std);
1046 // save_gaussian_reference("gaussian_reference.csv", cfg);
1047 // std::cout << "[INFO] Wrote bestfit.csv\n";
1048 // std::cout << "[INFO] Wrote gaussian_reference.csv\n";
1049
1050 // ContourComputationInput contour_input;
1051 // contour_input.xname = problem.names[0];
1052 // contour_input.yname = problem.names[1];
1053 // contour_input.px = 0;
1054 // contour_input.py = 1;
1055 // contour_input.best_fval = mj.fmin;
1056 // contour_input.p_hat = p_hat;
1057 // contour_input.p_std = p_std;
1058 // contour_input.problem = problem;
1059 // contour_input.best_fit = mj;
1060
1061 // MnContoursStrategy::Options native_opt;
1062 // native_opt.npoints = 120;
1063 // native_opt.refit_max_fcn = 10000;
1064 // native_opt.tolerance = 0.1;
1065 // native_opt.strategy = 2;
1066
1067 // MnContoursStrategy native_strategy(native_opt);
1068 // ContourComputationResult native_result = native_strategy.compute(contour_input);
1069
1070 // if (native_result.success) {
1071 // CsvExporter::save_contours("contours.csv",
1072 // contour_input.xname,
1073 // contour_input.yname,
1074 // native_result.c68,
1075 // native_result.c95);
1076 // CsvExporter::save_contours("contours_native.csv",
1077 // contour_input.xname,
1078 // contour_input.yname,
1079 // native_result.c68,
1080 // native_result.c95);
1081 // std::cout << "[INFO] Wrote contours.csv and contours_native.csv\n";
1082 // } else {
1083 // std::cerr << "[WARN] Native MnContours failed on Gaussian toy.\n";
1084 // }
1085
1086 // GridProfileContourStrategy::Options grid_opt;
1087 // grid_opt.nx = 61;
1088 // grid_opt.ny = 61;
1089 // grid_opt.strategy = 1;
1090 // grid_opt.max_fcn = 1500;
1091 // grid_opt.tolerance = 0.3;
1092 // grid_opt.n_sigma_window = 4.0;
1093 // grid_opt.hard_low = cfg.hard_low;
1094 // grid_opt.hard_high = cfg.hard_high;
1095
1096 // GridProfileContourStrategy grid_strategy(grid_opt);
1097 // ContourComputationResult grid_result = grid_strategy.compute(contour_input);
1098
1099 // if (!grid_result.success) {
1100 // std::cerr << "[ERROR] Grid computation failed.\n";
1101 // return 6;
1102 // }
1103
1104 // CsvExporter::save_grid("grid.csv",
1105 // contour_input.xname,
1106 // contour_input.yname,
1107 // grid_result.xs,
1108 // grid_result.ys,
1109 // grid_result.z);
1110 // std::cout << "[INFO] Wrote grid.csv\n";
1111
1112 // const std::vector<double> z_expected = gaussian_toy_grid(cfg, grid_result.xs, grid_result.ys);
1113 // CsvExporter::save_grid("grid_expected.csv",
1114 // contour_input.xname,
1115 // contour_input.yname,
1116 // grid_result.xs,
1117 // grid_result.ys,
1118 // z_expected);
1119 // std::cout << "[INFO] Wrote grid_expected.csv\n";
1120
1121 // const auto expected_c68 = gaussian_toy_contour_points(cfg, 2.30 / 2.0);
1122 // const auto expected_c95 = gaussian_toy_contour_points(cfg, 5.99 / 2.0);
1123 // CsvExporter::save_contours("contours_expected.csv",
1124 // contour_input.xname,
1125 // contour_input.yname,
1126 // expected_c68,
1127 // expected_c95);
1128 // std::cout << "[INFO] Wrote contours_expected.csv\n";
1129
1130 // double max_abs_grid_diff = 0.0;
1131 // for (std::size_t i = 0; i < grid_result.z.size(); ++i) {
1132 // max_abs_grid_diff = std::max(max_abs_grid_diff, std::fabs(grid_result.z[i] - z_expected[i]));
1133 // }
1134 // std::cout << "max |grid_observed - grid_expected| = " << max_abs_grid_diff << "\n";
1135
1136 return 0;
1137}