Hyperiso 1.0.3
Modular flavour-physics calculations, Wilson coefficients and statistical inference
Loading...
Searching...
No Matches
test_contour_minuit_refactored.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"
19#include "StatParameterProxy.h"
20#include "ObservableInterface.h"
23#include "Fit.h"
24#include "BaseLikelihood.h"
25#include "IProfilingStrategy.h"
27
28#include "FitAbstraction.h"
29#include "NuisanceReader.h"
30
31
32namespace fit_app {
33
34// -----------------------------------------------------------------------------
35// Small utilities
36// -----------------------------------------------------------------------------
37
38void print_vec(const std::vector<double>& vec) {
39 std::cout << "[ ";
40 for (std::size_t i = 0; i < vec.size(); ++i) {
41 std::cout << std::setprecision(17) << vec[i]
42 << (i + 1 == vec.size() ? " " : ", ");
43 }
44 std::cout << "]\n";
45}
46
47// -----------------------------------------------------------------------------
48// CSV export
49// -----------------------------------------------------------------------------
50
52public:
53 static void save_bestfit(const std::string& path,
54 const std::vector<std::string>& names,
55 const Vector& vals,
56 const Vector& errs) {
57 std::ofstream out(path);
58 out << "name,value,error\n";
59 for (std::size_t i = 0; i < vals.size(); ++i) {
60 out << names[i] << ","
61 << std::setprecision(17) << vals[i] << ","
62 << errs[i] << "\n";
63 }
64 }
65
66 static void save_contours(const std::string& path,
67 const std::string& xname,
68 const std::string& yname,
69 const std::vector<std::pair<double, double>>& c68,
70 const std::vector<std::pair<double, double>>& c95) {
71 std::ofstream out(path);
72 out << "# x=" << xname << "\n";
73 out << "# y=" << yname << "\n";
74 out << "cl,x,y\n";
75 for (const auto& p : c68) {
76 out << "0.683," << std::setprecision(17) << p.first << "," << p.second << "\n";
77 }
78 for (const auto& p : c95) {
79 out << "0.95," << std::setprecision(17) << p.first << "," << p.second << "\n";
80 }
81 }
82
83 static void save_grid(const std::string& path,
84 const std::string& xname,
85 const std::string& yname,
86 const std::vector<double>& xs,
87 const std::vector<double>& ys,
88 const std::vector<double>& z) {
89 const std::size_t nx = xs.size();
90 const std::size_t ny = ys.size();
91
92 std::ofstream out(path);
93 out << "# x=" << xname << "\n";
94 out << "# y=" << yname << "\n";
95 out << "x,y,delta_nll\n";
96 out << std::setprecision(17);
97
98 for (std::size_t iy = 0; iy < ny; ++iy) {
99 for (std::size_t ix = 0; ix < nx; ++ix) {
100 out << xs[ix] << "," << ys[iy] << "," << z[iy * nx + ix] << "\n";
101 }
102 }
103 }
104};
105
106
107// -----------------------------------------------------------------------------
108// Backend adapter helpers
109// -----------------------------------------------------------------------------
110
111struct ParamLimit {
112 std::size_t idx;
113 double low;
114 double high;
115};
116
118 double up = 0.5;
119 unsigned strategy = 2;
120 unsigned max_fcn = 100000;
121 double tolerance = 0.2;
122 bool run_hesse = true;
123 unsigned hesse_maxcalls = 0;
124 bool verbose = true;
125};
126
129 std::vector<double> x_err;
130 std::vector<double> cov_eigs;
131 double cond_number = std::numeric_limits<double>::infinity();
132
133 double fmin = std::numeric_limits<double>::quiet_NaN();
134 double edm = std::numeric_limits<double>::quiet_NaN();
135 int nfcn = -1;
136
137 bool ok = false;
138 bool has_valid_covar = false;
139 bool has_posdef_covar = false;
140 bool has_accurate_covar = false;
141 bool made_posdef = false;
142
145};
146
147static std::vector<ParameterDefinition> make_parameter_definitions(
148 const std::vector<std::string>& names,
149 const std::vector<double>& values,
150 const std::vector<double>& scale_hints,
151 const std::vector<ParamLimit>& limits
152) {
153 if (names.size() != values.size() || names.size() != scale_hints.size()) {
154 throw std::invalid_argument("make_parameter_definitions: names/values/scale_hints size mismatch");
155 }
156
157 std::vector<ParameterDefinition> parameters;
158 parameters.reserve(names.size());
159
160 for (std::size_t i = 0; i < names.size(); ++i) {
161 ParameterDefinition parameter;
162 parameter.name = names[i];
163 parameter.value = values[i];
164 parameter.step_hint = scale_hints[i];
165 parameters.push_back(std::move(parameter));
166 }
167
168 for (const auto& lim : limits) {
169 if (lim.idx < parameters.size()) {
170 parameters[lim.idx].limits = std::make_pair(lim.low, lim.high);
171 }
172 }
173
174 return parameters;
175}
176
177static FitOptions to_backend_fit_options(const MinuitFitOptions& opt) {
178 FitOptions out;
179 out.up = opt.up;
180 out.strategy = opt.strategy;
181 out.max_fcn = opt.max_fcn;
182 out.tolerance = opt.tolerance;
183 out.run_hesse = opt.run_hesse;
184 out.hesse_maxcalls = opt.hesse_maxcalls;
185 out.verbose = opt.verbose;
186 return out;
187}
188
190public:
191 explicit MinuitRunner(const IFitBackend& backend) : backend_(backend) {}
192
193 MinuitJointFit fit(const std::function<double(const std::vector<double>&)>& f,
194 const std::vector<std::string>& names,
195 const std::vector<double>& x0,
196 const std::vector<double>& scale_hints,
197 const std::vector<ParamLimit>& limits,
198 const MinuitFitOptions& opt) const {
199 const auto parameters = make_parameter_definitions(names, x0, scale_hints, limits);
200 const LambdaObjectiveFunction objective(f, opt.up);
201 const BackendFitResult backend_fit = backend_.minimize(objective, parameters, to_backend_fit_options(opt));
202 return extract_result(backend_fit, opt.verbose);
203 }
204
205private:
206 static MinuitJointFit extract_result(const BackendFitResult& backend_fit, bool verbose) {
207 MinuitJointFit out;
208 out.backend_fit = backend_fit;
209 out.x_hat = backend_fit.values;
210 out.x_err = backend_fit.errors;
211 out.cov = backend_fit.covariance;
212
213 out.fmin = backend_fit.diagnostics.fmin;
214 out.edm = backend_fit.diagnostics.edm;
215 out.nfcn = backend_fit.diagnostics.nfcn;
216 out.ok = backend_fit.diagnostics.ok;
217
221 out.made_posdef = backend_fit.diagnostics.made_posdef;
222 out.cov_eigs = backend_fit.diagnostics.cov_eigs;
223 out.cond_number = backend_fit.diagnostics.cond_number;
224
225 if (verbose && !out.cov_eigs.empty()) {
226 std::cout << "Cov eigen min/max = "
227 << std::setprecision(6) << out.cov_eigs.front()
228 << " / " << out.cov_eigs.back()
229 << " (cond ~ " << out.cond_number << ")\n";
230 }
231
232 return out;
233 }
234
235 const IFitBackend& backend_;
236};
237
238// -----------------------------------------------------------------------------
239// Problem / fit domain objects
240// -----------------------------------------------------------------------------
241
243 std::vector<std::string> names;
244 std::vector<double> x0;
245 std::vector<double> scale_hints;
246 std::vector<ParamLimit> limits;
247 std::function<double(const std::vector<double>&)> f_joint;
248};
249
255
257public:
258 using ModelFn = std::function<Vector(const Vector& p, const Vector& eta)>;
259
262 ModelFn model,
263 std::size_t max_fcn,
264 double tolerance,
265 unsigned strategy)
266 : backend_(backend)
267 , like_(std::move(ctx))
268 , model_(std::move(model))
269 , max_fcn_(max_fcn)
270 , tolerance_(tolerance)
271 , strategy_(strategy) {}
272
273 std::function<double(const std::vector<double>&)> make_joint_f(std::size_t p_dim) const {
274 return [this, p_dim](const std::vector<double>& x) -> double {
275 Vector p(x.begin(), x.begin() + p_dim);
276 Vector eta(x.begin() + p_dim, x.end());
277 return nll(p, eta);
278 };
279 }
280
281 JointFitOutput fit_joint_with_minuit(const std::vector<ParamId>& p_ids,
282 const std::vector<ParamId>& eta_ids,
283 const Vector& p0) const {
284 const std::size_t p_dim = p0.size();
285 Vector eta0;
286 for (auto eta_def : like_.nuis_defs) {
287 eta0.emplace_back(eta_def.value);
288 }
289 Vector eta_scales = like_.nuisance_dist->get_stds();
290
291 if (eta0.size() != eta_scales.size()) {
292 throw std::runtime_error("eta central values and eta stds do not have same size");
293 }
294
296 problem.x0.reserve(p_dim + eta0.size());
297 problem.x0.insert(problem.x0.end(), p0.begin(), p0.end());
298 problem.x0.insert(problem.x0.end(), eta0.begin(), eta0.end());
299
300 problem.scale_hints.reserve(problem.x0.size());
301 for (std::size_t i = 0; i < p_dim; ++i) {
302 double hint = std::fabs(p0[i]);
303 if (hint < 1e-3) hint = 0.01;
304 problem.scale_hints.push_back(hint);
305 }
306 for (double s : eta_scales) {
307 problem.scale_hints.push_back(std::max(1e-12, std::fabs(s)));
308 }
309
310 problem.names.reserve(problem.x0.size());
311 for (const auto& pid : p_ids) problem.names.push_back(to_string_any(pid));
312 for (const auto& pid : eta_ids) problem.names.push_back(to_string_any(pid));
313
314 for (std::size_t i = 0; i < p_dim; ++i) {
315 if (problem.names[i].find("FCONST") != std::string::npos) {
316 problem.limits.push_back(ParamLimit{i, 0.05, 0.35});
317 }
318 }
319
320 for (std::size_t i = p_dim; i < problem.names.size(); ++i) {
321 const std::string& nm = problem.names[i];
322 const double c = problem.x0[i];
323 const double s = std::max(1e-12, std::fabs(problem.scale_hints[i]));
324
325 if (nm.find("SMINPUTS:3") != std::string::npos) {
326 problem.limits.push_back(ParamLimit{i, 0.05, 0.30});
327 } else if (nm.find("MASS:") != std::string::npos ||
328 nm.find("FLIFE:") != std::string::npos ||
329 nm.find("FCONST:") != std::string::npos ||
330 nm.find("FMASS:") != std::string::npos ||
331 nm.find("SMINPUTS:5") != std::string::npos ||
332 nm.find("SMINPUTS:6") != std::string::npos) {
333 problem.limits.push_back(ParamLimit{i, std::max(1e-12, c - 5.0 * s), c + 5.0 * s});
334 }
335 }
336
337 problem.f_joint = make_joint_f(p_dim);
338
340 opt.up = 0.5;
341 opt.strategy = strategy_;
342 opt.max_fcn = static_cast<unsigned>(max_fcn_);
343 opt.tolerance = tolerance_;
344 opt.run_hesse = true;
345 opt.verbose = true;
346
347 MinuitRunner runner(backend_);
348 MinuitJointFit mj = runner.fit(problem.f_joint,
349 problem.names,
350 problem.x0,
351 problem.scale_hints,
352 problem.limits,
353 opt);
354
355 FitResult fr;
356 fr.ell_hat = mj.fmin;
357 fr.p_hat.assign(mj.x_hat.begin(), mj.x_hat.begin() + p_dim);
358 fr.eta_hat.assign(mj.x_hat.begin() + p_dim, mj.x_hat.end());
359 fr.p_hat_std.assign(p_dim, 0.0);
360 fr.p_hat_correlations = RealMatrix(p_dim, p_dim);
361
362 if (mj.has_valid_covar) {
363 for (std::size_t i = 0; i < p_dim; ++i) {
364 fr.p_hat_std[i] = std::sqrt(std::max(0.0, mj.cov.at(i, i)));
365 }
366
367 for (std::size_t i = 0; i < p_dim; ++i) {
368 for (std::size_t j = 0; j < p_dim; ++j) {
369 const double di = std::sqrt(std::max(0.0, mj.cov.at(i, i)));
370 const double dj = std::sqrt(std::max(0.0, mj.cov.at(j, j)));
371 fr.p_hat_correlations.at(i, j) =
372 (di > 0.0 && dj > 0.0) ? (mj.cov.at(i, j) / (di * dj)) : 0.0;
373 }
374 }
375 } else {
376 for (std::size_t i = 0; i < p_dim && i < mj.x_err.size(); ++i) {
377 fr.p_hat_std[i] = mj.x_err[i];
378 fr.p_hat_correlations.at(i, i) = 1.0;
379 }
380 }
381
382 return JointFitOutput{fr, mj, problem};
383 }
384
385private:
386 double nll(const Vector& p, const Vector& eta) const {
387 Vector pred = model_(p, eta);
388
389 Vector r(pred.size());
390 for (std::size_t i = 0; i < pred.size(); ++i) {
391 r[i] = pred[i] - like_.exp_obs_values[i];
392 }
393
394 const double ell_obs = like_.exp_obs_dist->logpdf(r);
395 const double ell_eta = like_.nuisance_dist->logpdf(eta);
396 return -(ell_obs + ell_eta);
397 }
398
399 const IFitBackend& backend_;
400 LikelihoodContext like_;
401 ModelFn model_;
402 std::size_t max_fcn_;
403 double tolerance_;
404 unsigned strategy_;
405};
406
407// -----------------------------------------------------------------------------
408// Contour strategies
409// -----------------------------------------------------------------------------
410
412 std::string xname;
413 std::string yname;
414 unsigned px = 0;
415 unsigned py = 1;
416 double best_fval = 0.0;
421 const IFitBackend* backend = nullptr;
422};
423
425 bool success = false;
426 bool used_grid = false;
427 std::vector<std::pair<double, double>> c68;
428 std::vector<std::pair<double, double>> c95;
429 std::vector<double> xs;
430 std::vector<double> ys;
431 std::vector<double> z;
432};
433
435 double fmin = 1e300;
436 std::vector<double> x_hat;
437 bool ok = false;
438};
439
440static ProfileXYResult profiled_fit_at_fixed_xy(
441 const IFitBackend& backend,
442 const std::function<double(const std::vector<double>&)>& f_joint,
443 const std::vector<std::string>& names,
444 const std::vector<double>& x_start,
445 const std::vector<double>& scale_hints,
446 const std::vector<ParamLimit>& limits,
447 unsigned px,
448 unsigned py,
449 double xval,
450 double yval,
451 unsigned strategy,
452 unsigned max_fcn,
453 double tolerance
454) {
455 const auto parameters = make_parameter_definitions(names, x_start, scale_hints, limits);
456 const LambdaObjectiveFunction objective(f_joint, 0.5);
457
458 FitOptions opt;
459 opt.up = 0.5;
460 opt.strategy = strategy;
461 opt.max_fcn = max_fcn;
462 opt.tolerance = tolerance;
463 opt.run_hesse = false;
464 opt.verbose = false;
465
466 const BackendFitResult fit = backend.minimize_with_fixed(objective,
467 parameters,
468 opt,
469 {px, py},
470 {xval, yval});
471
472 ProfileXYResult out;
473 out.ok = fit.diagnostics.ok;
474 out.fmin = out.ok ? fit.diagnostics.fmin : 1e300;
475 out.x_hat = x_start;
476
477 if (out.ok && fit.values.size() == x_start.size()) {
478 out.x_hat = fit.values;
479 } else {
480 out.x_hat[px] = xval;
481 out.x_hat[py] = yval;
482 out.fmin = f_joint(out.x_hat);
483 }
484
485 return out;
486}
487
489public:
490 virtual ~IContourStrategy() = default;
492};
493
495public:
496 struct Options {
497 unsigned npoints = 80;
498 unsigned refit_max_fcn = 30000;
499 double tolerance = 0.2;
500 unsigned strategy = 2;
501 };
502
504 explicit MnContoursStrategy(const Options& options) : opt_(options) {}
505
508 if (input.backend == nullptr || !input.best_fit.ok || !input.best_fit.backend_fit.state) {
509 return result;
510 }
511
512 const bool ok68 = compute_single(input, 2.30 / 2.0, result.c68);
513 const bool ok95 = compute_single(input, 5.99 / 2.0, result.c95);
514
515 result.success = ok68 && ok95;
516 result.used_grid = false;
517 return result;
518 }
519
520private:
521 bool compute_single(const ContourComputationInput& input,
522 double up_contour,
523 std::vector<std::pair<double, double>>& out_points) const {
524 std::vector<double> refit_scales = input.problem.scale_hints;
525 const double scale_factor = std::sqrt(up_contour / 0.5);
526
527 for (std::size_t i = 0; i < refit_scales.size() && i < input.best_fit.x_err.size(); ++i) {
528 const double err = std::fabs(input.best_fit.x_err[i]);
529 if (std::isfinite(err) && err > 0.0) {
530 refit_scales[i] = std::max(refit_scales[i], err * scale_factor);
531 }
532 }
533
534 const auto refit_parameters = make_parameter_definitions(input.problem.names,
535 input.best_fit.x_hat,
536 refit_scales,
537 input.problem.limits);
538
539 const LambdaObjectiveFunction objective(input.problem.f_joint, up_contour);
540
541 FitOptions refit_opt;
542 refit_opt.up = up_contour;
543 refit_opt.strategy = opt_.strategy;
544 refit_opt.max_fcn = opt_.refit_max_fcn;
545 refit_opt.tolerance = opt_.tolerance;
546 refit_opt.run_hesse = true;
547 refit_opt.verbose = false;
548
549 const BackendFitResult refit = input.backend->minimize(objective, refit_parameters, refit_opt);
550 if (!refit.diagnostics.ok || !refit.state) {
551 return false;
552 }
553
554 ContourOptionsBackEnd contour_opt;
555 contour_opt.up = up_contour;
556 contour_opt.npoints = opt_.npoints;
557 contour_opt.strategy = opt_.strategy;
558 contour_opt.max_fcn = opt_.refit_max_fcn;
559 contour_opt.tolerance = opt_.tolerance;
560
561 const BackendContourResult contour = input.backend->contour(objective,
562 refit,
563 input.px,
564 input.py,
565 contour_opt);
566 out_points = contour.points;
567 return contour.success;
568 }
569
570 Options opt_{};
571};
572
574public:
575 struct Options {
576 std::size_t nx = 31;
577 std::size_t ny = 31;
578 unsigned strategy = 1;
579 unsigned max_fcn = 1200;
580 double tolerance = 0.5;
581 double n_sigma_window = 4.0;
582 double hard_low = 0.05;
583 double hard_high = 0.35;
584 };
585
587 explicit GridProfileContourStrategy(const Options& options) : opt_(options) {}
588
591 result.used_grid = true;
592
593 double x0 = input.p_hat.at(0);
594 double y0 = input.p_hat.at(1);
595 double sx = std::max(0.01, input.p_std.at(0));
596 double sy = std::max(0.01, input.p_std.at(1));
597
598 double xlo = std::max(opt_.hard_low, x0 - opt_.n_sigma_window * sx);
599 double xhi = std::min(opt_.hard_high, x0 + opt_.n_sigma_window * sx);
600 double ylo = std::max(opt_.hard_low, y0 - opt_.n_sigma_window * sy);
601 double yhi = std::min(opt_.hard_high, y0 + opt_.n_sigma_window * sy);
602
603 if (!(xhi > xlo)) { xlo = 0.10; xhi = 0.30; }
604 if (!(yhi > ylo)) { ylo = 0.10; yhi = 0.30; }
605
606 result.xs = linspace(xlo, xhi, opt_.nx);
607 result.ys = linspace(ylo, yhi, opt_.ny);
608 result.z.assign(opt_.nx * opt_.ny, 1e300);
609
610 std::vector<double> seed = input.best_fit.x_hat;
611
612 for (std::size_t iy = 0; iy < opt_.ny; ++iy) {
613 const bool reverse = (iy % 2 == 1);
614
615 if (!reverse) {
616 for (std::size_t ix = 0; ix < opt_.nx; ++ix) {
617 auto pr = profile_at_fixed_xy(input,
618 seed,
619 result.xs[ix],
620 result.ys[iy]);
621 result.z[iy * opt_.nx + ix] = pr.fmin - input.best_fval;
622 seed = pr.x_hat;
623 }
624 } else {
625 for (std::size_t k = 0; k < opt_.nx; ++k) {
626 std::size_t ix = opt_.nx - 1 - k;
627 auto pr = profile_at_fixed_xy(input,
628 seed,
629 result.xs[ix],
630 result.ys[iy]);
631 result.z[iy * opt_.nx + ix] = pr.fmin - input.best_fval;
632 seed = pr.x_hat;
633 }
634 }
635 }
636
637 result.success = true;
638 return result;
639 }
640
641private:
642 ProfileXYResult profile_at_fixed_xy(const ContourComputationInput& input,
643 const std::vector<double>& seed,
644 double xval,
645 double yval) const {
646 return profiled_fit_at_fixed_xy(*input.backend,
647 input.problem.f_joint,
648 input.problem.names,
649 seed,
650 input.problem.scale_hints,
651 input.problem.limits,
652 input.px,
653 input.py,
654 xval,
655 yval,
656 opt_.strategy,
657 opt_.max_fcn,
658 opt_.tolerance);
659 }
660
661 Options opt_{};
662};
663
665public:
666 FallbackContourStrategy(std::unique_ptr<IContourStrategy> primary,
667 std::unique_ptr<IContourStrategy> fallback)
668 : primary_(std::move(primary)), fallback_(std::move(fallback)) {}
669
671 ContourComputationResult primary_result = primary_->compute(input);
672 if (primary_result.success) {
673 return primary_result;
674 }
675
676 std::cerr << "[WARN] MnContours failed; fallback to grid scan.\n";
677 return fallback_->compute(input);
678 }
679
680private:
681 std::unique_ptr<IContourStrategy> primary_;
682 std::unique_ptr<IContourStrategy> fallback_;
683};
684
685// -----------------------------------------------------------------------------
686// Application bootstrap
687// -----------------------------------------------------------------------------
688
689struct BuiltProblem {
690 std::vector<ParamId> p_ids;
691 std::vector<ParamId> eta_ids;
692 std::vector<ExperimentObs> obs_ids;
694 std::shared_ptr<ObservableInterfaceProxy> model;
696};
697
698BuiltProblem build_problem(StatisticManager& stat,
699 const StatisticConfig& config,
700 const std::shared_ptr<ObservableInterfaceProxy>& model, std::vector<ParamId> p_specs) {
701 LOG_INFO("fill_cache #1");
702 // stat.fill_cache();
703
704 auto start_u = std::chrono::steady_clock::now();
706 auto stop_u = std::chrono::steady_clock::now();
707 auto us_u = std::chrono::duration_cast<std::chrono::microseconds>(stop_u - start_u).count();
708 std::cout << "Uncertainty estimation time: " << us_u << " us\n";
709
710 LOG_INFO("fill_cache #2");
711 stat.update_cache(p_specs);
712
713 auto p_specs_map = stat.get_p_specs(p_specs);
714 auto eta_specs_real = stat.get_all_obss_deps();
715 for (const auto& [pid, _] : p_specs_map) eta_specs_real.erase(pid);
716 auto exp_obs_map = stat.get_obs_exp();
717
718 auto unz_p = unzip(p_specs_map);
719 auto unz_eta = unzip(eta_specs_real);
720 auto unz_obs = unzip(exp_obs_map);
721
722 auto nuisance_dist = stat.build_nuisance_distribution();
723 auto exp_obs_dist = stat.build_exp_data_distribution();
724
725 if (nuisance_dist->get_stds().size() != unz_eta.vals.size()) {
726 throw std::runtime_error("nuisance std size and eta central size mismatch");
727 }
728 if (exp_obs_dist->dim() != unz_obs.vals.size()) {
729 throw std::runtime_error("exp obs dim and exp obs values size mismatch");
730 }
731
733 ctx.nuisance_dist = std::move(nuisance_dist);
734 ctx.exp_obs_dist = std::move(exp_obs_dist);
735 // ctx.nuis_defs =
736 // ctx.fp_defs =
737 ctx.exp_obs_values = unz_obs.vals;
738
739 return BuiltProblem{
740 unz_p.ids,
741 unz_eta.ids,
742 unz_obs.ids,
743 std::move(ctx),
744 model,
745 unz_p.vals
746 };
747}
748
749} // namespace fit_app
750
751int main(int argc, char** argv) {
752 using namespace fit_app;
753
754 HyperisoMaster hyp;
755 HyperisoConfig config_hyp;
756 config_hyp.model = Model::SM;
757 hyp.init("lha/si_input.flha", config_hyp);
758
759 auto oint = std::make_shared<ObservableInterface>();
762
763 StatisticConfig config;
764 config.MC_draws = 100;
765 config.advanced.MLE_max_iter = 120000;
766 config.advanced.MLE_tol = 0.2;
767 std::vector<ParamId> p_specs = {
768 ParamId{ParameterType::FLAVOR, "FCONST", {511, 1}},
769 ParamId{ParameterType::FLAVOR, "FCONST", {531, 1}}
770 };
771
772 std::shared_ptr<IStatParamOptimizerProxy> spop = std::make_shared<StatParamOptimizerProxy>();
773 auto model = std::make_shared<ObservableInterfaceProxy>(oint, spop);
774
775 std::shared_ptr<INuisancePathsProvider> npp = std::make_shared<DefaultNuisancePathsProvider>();
776
777 StatisticManager stat(
778 config,
779 model,
780 std::make_shared<StatCorrelationProxy>(),
781 std::make_shared<StatParameterProxy>(),
782 std::make_shared<StatParamSourcesProxy>(),
783 std::make_shared<StatDependencyPruner>(),
784 std::make_shared<NuisanceReader>(npp),
785 spop
786 );
787
788 BuiltProblem built = build_problem(stat, config, model, p_specs);
789 std::unique_ptr<IFitBackend> backend = make_minuit_backend();
790
791 auto model_fn = [model, obs_ids = built.obs_ids, p_ids = built.p_ids, eta_ids = built.eta_ids]
792 (const Vec& p_vec, const Vec& eta_vec) -> Vec {
793 auto pred_map = model->predict_optimized(zip(p_ids, p_vec), zip(eta_ids, eta_vec));
794
795 Vec out;
796 out.reserve(obs_ids.size());
797
798 for (const auto& bid : obs_ids) {
799 const auto& vec = pred_map.at(bid.obs.s);
800 auto it = std::find_if(vec.begin(), vec.end(), [&](const ObservableValue& ov) {
801 auto bin = ov.bin.value_or(std::pair<double, double>{0., 0.});
802 return bin == bid.obs.p;
803 });
804
805 if (it == vec.end()) {
806 throw std::runtime_error("Missing predicted observable/bin");
807 }
808 out.push_back(it->value);
809 }
810
811 return out;
812 };
813
814 auto start_m = std::chrono::steady_clock::now();
815 MinuitMLEstimatorLocal est(*backend, std::move(built.ctx), model_fn, config.advanced.MLE_max_iter, config.advanced.MLE_tol, 2);
816 JointFitOutput fit_out = est.fit_joint_with_minuit(built.p_ids, built.eta_ids, built.p0);
817 auto stop_m = std::chrono::steady_clock::now();
818
819 const auto& fr = fit_out.fr;
820 const auto& mj = fit_out.mj;
821
822 auto us_m = std::chrono::duration_cast<std::chrono::microseconds>(stop_m - start_m).count();
823 std::cout << "\nMLE (Minuit) fitting time: " << us_m << " us\n";
824
825 std::cout << "ell_hat = " << std::setprecision(17) << fr.ell_hat << "\n";
826 std::cout << "p_hat = "; print_vec(fr.p_hat);
827 std::cout << "p_hat_std = "; print_vec(fr.p_hat_std);
828 std::cout << "p_hat_correlations:\n" << fr.p_hat_correlations << "\n";
829
830 if (!mj.ok) {
831 std::cerr << "[ERROR] Minuit fit invalid.\n";
832 return 5;
833 }
834
835 if (!mj.has_valid_covar || !mj.has_posdef_covar) {
836 std::cerr << "[WARN] Covariance is not fully healthy."
837 << " valid=" << mj.has_valid_covar
838 << " posdef=" << mj.has_posdef_covar
839 << " accurate=" << mj.has_accurate_covar
840 << " cond=" << mj.cond_number << "\n";
841 }
842
843 std::vector<std::string> p_names;
844 for (const auto& pid : built.p_ids) p_names.push_back(to_string_any(pid));
845 CsvExporter::save_bestfit("bestfit.csv", p_names, fr.p_hat, fr.p_hat_std);
846 std::cout << "[INFO] Wrote bestfit.csv\n";
847
848 if (built.p_ids.size() == 2) {
849 ContourComputationInput contour_input;
850 contour_input.xname = to_string_any(built.p_ids[0]);
851 contour_input.yname = to_string_any(built.p_ids[1]);
852 contour_input.px = 0;
853 contour_input.py = 1;
854 contour_input.best_fval = fr.ell_hat;
855 contour_input.p_hat = fr.p_hat;
856 contour_input.p_std = fr.p_hat_std;
857 contour_input.problem = fit_out.problem;
858 contour_input.best_fit = fit_out.mj;
859 contour_input.backend = backend.get();
860
861 FallbackContourStrategy contour_strategy(
862 std::make_unique<MnContoursStrategy>(),
863 std::make_unique<GridProfileContourStrategy>()
864 );
865
866 ContourComputationResult contour_result = contour_strategy.compute(contour_input);
867
868 if (!contour_result.success) {
869 std::cerr << "[ERROR] Contour computation failed.\n";
870 return 6;
871 }
872
873 if (contour_result.used_grid) {
874 CsvExporter::save_grid("grid.csv",
875 contour_input.xname,
876 contour_input.yname,
877 contour_result.xs,
878 contour_result.ys,
879 contour_result.z);
880 std::cout << "[INFO] Wrote grid.csv\n";
881
882 double best_grid = 1e300;
883 std::size_t best_ix = 0;
884 std::size_t best_iy = 0;
885 const std::size_t nx = contour_result.xs.size();
886 const std::size_t ny = contour_result.ys.size();
887
888 for (std::size_t iy = 0; iy < ny; ++iy) {
889 for (std::size_t ix = 0; ix < nx; ++ix) {
890 double val = contour_result.z[iy * nx + ix];
891 if (val < best_grid) {
892 best_grid = val;
893 best_ix = ix;
894 best_iy = iy;
895 }
896 }
897 }
898
899 std::cout << "[INFO] grid min delta_nll = " << best_grid
900 << " at (" << contour_result.xs[best_ix] << ", "
901 << contour_result.ys[best_iy] << ")\n";
902 std::cout << "[INFO] best-fit = (" << fr.p_hat[0] << ", " << fr.p_hat[1] << ")\n";
903 } else {
904 CsvExporter::save_contours("contours.csv",
905 contour_input.xname,
906 contour_input.yname,
907 contour_result.c68,
908 contour_result.c95);
909 std::cout << "[INFO] Wrote contours.csv\n";
910 }
911 }
912
913 return 0;
914}
Concrete profileable likelihood built from a model and joint distributions.
std::function< std::vector< double >(const std::vector< double > &p, const std::vector< double > &eta)> ModelFn
Model function signature used by BaseLikelihood.
Default nuisance-configuration path provider.
High-level maximum-likelihood fitting and confidence-contour API.
Profiling strategies used to build two-dimensional likelihood scan requests.
std::map< T, U > zip(const std::vector< T > &ids, const std::vector< U > &vals)
Builds a map from parallel id and value vectors.
Definition Indexing.h:61
UnzipResult1D< T, U > unzip(const std::map< T, U > &indexed)
Splits a map into parallel id and value vectors.
Definition Indexing.h:176
#define LOG_INFO(...)
Macro for logging informational messages.
Definition Logger.h:39
Concrete reader for nuisance-parameter definition files.
Adapter from ObservableInterface to the statistical model interface.
High-level, user-facing entry point to compute flavor observables.
Concrete statistical proxy forwarding correlation queries to CorrelationProvider.
Statistics-layer adapter over the core DependencyPruner service.
Statistics-layer adapter for retrieving leaf parameter sources.
Statistics-layer proxy for read-only access to parameters and observables.
High-level orchestration of statistical uncertainty propagation, likelihood construction and fit scan...
static IdOf< ObservableTag > to_id(Observables e)
Converts an enum value to an IdOf<Tag>.
High-level interface to initialize and monitor the main framework configuration.
void init(const std::string &lhaFile, HyperisoConfig config)
Initializes Hyperiso using a LHA file and a full Config object.
double & at(size_t i, size_t j)
Returns a mutable reference to element (i,j) with bounds checking.
Definition Matrix.cpp:587
Coordinates statistical inputs, nuisance distributions, MLE fits and contour/scan computations.
std::unique_ptr< JointDistribution > build_nuisance_distribution()
Builds the joint nuisance distribution from cached nuisance marginals and correlations.
std::map< BinnedObservableId, GaussianSummary > compute_uncertainties()
Computes Gaussian summaries for MC-propagated observable uncertainties.
std::map< ExperimentObs, double > get_obs_exp()
std::map< ParamId, double > get_p_specs(const std::vector< ParamId > &p_specs)
Resolves initial fit-parameter values from the parameter proxy.
void update_cache(const std::vector< ParamId > &p_specs=std::vector< ParamId >())
Updates the full statistical cache for the selected fit parameters.
std::unique_ptr< JointDistribution > build_exp_data_distribution()
Builds the joint experimental-data distribution from cached observables and correlations.
std::map< ParamId, double > get_all_obss_deps()
Selects all nuisance dependencies relevant to the current observable set.
static void save_grid(const std::string &path, const std::string &xname, const std::string &yname, const std::vector< double > &xs, const std::vector< double > &ys, const std::vector< double > &z)
static void save_contours(const std::string &path, const std::string &xname, const std::string &yname, const std::vector< std::pair< double, double > > &c68, const std::vector< std::pair< double, double > > &c95)
static void save_bestfit(const std::string &path, const std::vector< std::string > &names, const Vector &vals, const Vector &errs)
ContourComputationResult compute(const ContourComputationInput &input) const override
FallbackContourStrategy(std::unique_ptr< IContourStrategy > primary, std::unique_ptr< IContourStrategy > fallback)
ContourComputationResult compute(const ContourComputationInput &input) const override
virtual ~IContourStrategy()=default
virtual ContourComputationResult compute(const ContourComputationInput &input) const =0
virtual BackendFitResult minimize_with_fixed(const IObjectiveFunction &objective, const std::vector< ParameterDefinition > &parameters, const FitOptions &options, const std::vector< std::size_t > &fixed_indices, const std::vector< double > &fixed_values) const =0
virtual BackendFitResult minimize(const IObjectiveFunction &objective, const std::vector< ParameterDefinition > &parameters, const FitOptions &options) const =0
virtual BackendContourResult contour(const IObjectiveFunction &objective, const BackendFitResult &reference_fit, std::size_t x_index, std::size_t y_index, const ContourOptionsBackEnd &options) const =0
std::function< double(const std::vector< double > &)> make_joint_f(std::size_t p_dim) const
std::function< Vector(const Vector &p, const Vector &eta)> ModelFn
MinuitMLEstimatorLocal(const IFitBackend &backend, LikelihoodContext ctx, ModelFn model, std::size_t max_fcn, double tolerance, unsigned strategy)
JointFitOutput fit_joint_with_minuit(const std::vector< ParamId > &p_ids, const std::vector< ParamId > &eta_ids, const Vector &p0) const
MinuitJointFit fit(const std::function< double(const std::vector< double > &)> &f, const std::vector< std::string > &names, const std::vector< double > &x0, const std::vector< double > &scale_hints, const std::vector< ParamLimit > &limits, const MinuitFitOptions &opt) const
MinuitRunner(const IFitBackend &backend)
ContourComputationResult compute(const ContourComputationInput &input) const override
std::vector< double > Vector
void print_vec(const std::vector< double > &vec)
std::string to_string_any(const T &x)
std::vector< double > linspace(double a, double b, std::size_t n)
Hash specialization for SymbolId<Tag>.
Definition BlockName.h:353
double f(double x)
Wilson special function f depending on x.
double MLE_tol
Minimizer tolerance passed to the backend.
std::size_t MLE_max_iter
Maximum number of minimizer function calls/iterations.
Summary of a global likelihood fit.
std::vector< double > p_hat_std
Standard deviations of the parameters of interest.
std::vector< double > eta_hat
Nuisance estimates at the global maximum-likelihood point.
std::vector< double > p_hat
Maximum-likelihood estimates for parameters of interest.
RealMatrix p_hat_correlations
Correlation matrix for the parameters of interest.
double ell_hat
Minimum NLL value at the global best-fit point.
Configuration object controlling model, input flags and optional MARTY resources.
Definition Config.h:24
Model model
Current model.
Definition Config.h:33
Shared immutable-like data required to evaluate a likelihood.
std::vector< fit_app::ParameterDefinition > nuis_defs
Definitions of nuisance parameters.
std::vector< double > exp_obs_values
Nominal experimental observable values.
std::unique_ptr< JointDistribution > exp_obs_dist
Joint distribution of observable residuals.
std::unique_ptr< JointDistribution > nuisance_dist
Joint distribution of nuisance parameters.
Container for a computed observable value, optionally binned.
Composite identifier for a single parameter.
Definition ParamID.h:57
AdvancedStatisticConfig advanced
Advanced fit/pruning/covariance configuration.
std::size_t MC_draws
Number of accepted MC draws used for uncertainty propagation.
std::vector< std::pair< double, double > > points
std::vector< double > errors
std::vector< double > values
std::shared_ptr< const BackendState > state
std::vector< ExperimentObs > obs_ids
Definition jpp.cpp:41
std::shared_ptr< ObservableInterfaceProxy > model
std::vector< ParamId > eta_ids
Definition jpp.cpp:40
std::vector< ParamId > p_ids
Definition jpp.cpp:39
std::vector< std::pair< double, double > > c95
std::vector< std::pair< double, double > > c68
std::vector< double > cov_eigs
std::function< double(const std::vector< double > &)> f_joint
std::size_t idx
Definition jpp.cpp:33
std::vector< double > Vec