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
947
int
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
}
main
int main()
Definition
test_dynamic_registry.cpp:12
Hyperiso
core
src
Statistic
app
test_gaussian_contour.cpp
Generated by
1.9.8