Hyperiso 1.0.3
Modular flavour-physics calculations, Wilson coefficients and statistical inference
Loading...
Searching...
No Matches
jpp.cpp
Go to the documentation of this file.
1#include <algorithm>
2#include <array>
3#include <chrono>
4#include <cmath>
5#include <iomanip>
6#include <iostream>
7#include <limits>
8#include <map>
9#include <memory>
10#include <stdexcept>
11#include <string>
12#include <utility>
13#include <vector>
14
15#include "StatisticManager.h"
16#include "ObservableInterface.h"
19#include "StatParameterProxy.h"
22#include "Fit.h"
23#include "BaseLikelihood.h"
24#include "Profiler.h"
25#include "IProfilingStrategy.h"
26#include "FitAbstraction.h"
27#include "NuisanceReader.h"
29
30namespace fit_app {
31
32struct ParamLimit {
33 std::size_t idx;
34 double low;
35 double high;
36};
37
39 std::vector<ParamId> p_ids;
40 std::vector<ParamId> eta_ids;
41 std::vector<ExperimentObs> obs_ids;
42
43 std::shared_ptr<LikelihoodContext> ctx;
44 std::shared_ptr<BaseLikelihood> like;
45
46 std::vector<std::string> names;
47 std::vector<double> x0;
48 std::vector<double> scale_hints;
49 std::vector<ParamLimit> limits;
50};
51
52static std::string param_name(const ParamId& pid) {
53 std::ostringstream oss;
54 oss << pid;
55 return oss.str();
56}
57
58static fit_app::ParameterDefinition make_fit_param_def_local(
59 const ParamId& pid, double value, double sigma_hint)
60{
62 out.name = param_name(pid);
63 out.value = value;
64 out.step_hint = (std::isfinite(sigma_hint) && sigma_hint > 0.0)
65 ? sigma_hint
66 : std::max(1e-3, 0.01 * std::abs(value));
67
68 if (out.name.find("FCONST") != std::string::npos) {
69 out.limits = std::make_pair(0.05, 0.35);
70 }
71
72 return out;
73}
74
75static fit_app::ParameterDefinition make_nuisance_param_def_local(
76 const ParamId& pid, double value, double sigma_hint)
77{
79 out.name = param_name(pid);
80 out.value = value;
81
82 const double s = (std::isfinite(sigma_hint) && sigma_hint > 0.0)
83 ? std::abs(sigma_hint)
84 : std::max(1e-3, 0.01 * std::abs(value));
85
86 out.step_hint = s;
87
88 const std::string& nm = out.name;
89 if (nm.find("SMINPUTS:3") != std::string::npos) {
90 out.limits = std::make_pair(0.05, 0.30);
91 } else if (nm.find("MASS:") != std::string::npos ||
92 nm.find("FLIFE:") != std::string::npos ||
93 nm.find("FCONST:") != std::string::npos ||
94 nm.find("FMASS:") != std::string::npos ||
95 nm.find("SMINPUTS:5") != std::string::npos ||
96 nm.find("SMINPUTS:6") != std::string::npos) {
97 out.limits = std::make_pair(std::max(1e-12, value - 5.0 * s), value + 5.0 * s);
98 }
99
100 return out;
101}
102
103static std::vector<double> ordered_prediction_vector(
104 const std::vector<ExperimentObs>& obs_ids,
105 const std::map<ObservableId, std::vector<ObservableValue>>& pred_map)
106{
107 std::vector<double> out;
108 out.reserve(obs_ids.size());
109
110 for (const auto& bid : obs_ids) {
111 const auto& vec = pred_map.at(bid.obs.s);
112
113 auto it = std::find_if(vec.begin(), vec.end(), [&](const ObservableValue& ov) {
114 auto bin = ov.bin.value_or(std::pair<double, double>{0.0, 0.0});
115 return bin == bid.obs.p;
116 });
117
118 if (it == vec.end()) {
119 throw std::runtime_error("Missing predicted observable/bin.");
120 }
121
122 out.push_back(it->value);
123 }
124
125 return out;
126}
127
128static std::vector<fit_app::ParameterDefinition> make_parameter_definitions(
129 const std::vector<std::string>& names,
130 const std::vector<double>& values,
131 const std::vector<double>& scale_hints,
132 const std::vector<ParamLimit>& limits)
133{
134 if (names.size() != values.size() || names.size() != scale_hints.size()) {
135 throw std::invalid_argument("names/values/scale_hints size mismatch");
136 }
137
138 std::vector<fit_app::ParameterDefinition> parameters;
139 parameters.reserve(names.size());
140
141 for (std::size_t i = 0; i < names.size(); ++i) {
143 p.name = names[i];
144 p.value = values[i];
145 p.step_hint = scale_hints[i];
146 parameters.push_back(std::move(p));
147 }
148
149 for (const auto& lim : limits) {
150 if (lim.idx < parameters.size()) {
151 parameters[lim.idx].limits = std::make_pair(lim.low, lim.high);
152 }
153 }
154
155 return parameters;
156}
157
158static BuiltProblem build_problem(
159 StatisticManager& stat,
160 const StatisticConfig& config,
161 const std::shared_ptr<ObservableInterfaceProxy>& model, std::vector<ParamId> p_specs)
162{
164 stat.update_cache(p_specs);
165
166 auto p_specs_map = stat.get_p_specs(p_specs);
167 auto eta_specs_real = stat.get_all_obss_deps();
168 for (const auto& [pid, _] : p_specs_map) eta_specs_real.erase(pid);
169 auto exp_obs_map = stat.get_obs_exp();
170
171 auto unz_p = unzip(p_specs_map);
172 auto unz_eta = unzip(eta_specs_real);
173 auto unz_obs = unzip(exp_obs_map);
174
175 auto ctx = std::make_shared<LikelihoodContext>();
176 ctx->nuisance_dist = stat.build_nuisance_distribution();
177 ctx->exp_obs_dist = stat.build_exp_data_distribution();
178 ctx->exp_obs_values = unz_obs.vals;
179
180 // Définitions paramètres: mêmes limites que le manager actuel
181 ctx->fp_defs.reserve(unz_p.ids.size());
182 for (std::size_t i = 0; i < unz_p.ids.size(); ++i) {
183 double sigma_hint = std::max(1e-3, std::abs(unz_p.vals[i]));
184 ctx->fp_defs.emplace_back(make_fit_param_def_local(unz_p.ids[i], unz_p.vals[i], sigma_hint));
185 }
186
187 const auto eta_stds = ctx->nuisance_dist->get_stds();
188 ctx->nuis_defs.reserve(unz_eta.ids.size());
189 for (std::size_t i = 0; i < unz_eta.ids.size(); ++i) {
190 double sigma_hint = (i < eta_stds.size()) ? eta_stds[i] : 1e-3;
191 ctx->nuis_defs.emplace_back(make_nuisance_param_def_local(unz_eta.ids[i], unz_eta.vals[i], sigma_hint));
192 }
193
194 auto model_fn = [model, obs_ids = unz_obs.ids, p_ids = unz_p.ids, eta_ids = unz_eta.ids]
195 (const std::vector<double>& p_vec, const std::vector<double>& eta_vec) -> std::vector<double> {
196 auto pred_map = model->predict_optimized(zip(p_ids, p_vec), zip(eta_ids, eta_vec));
197 return ordered_prediction_vector(obs_ids, pred_map);
198 };
199
200 auto like = std::make_shared<BaseLikelihood>(model_fn, ctx, unz_p.ids.size());
201
202 BuiltProblem bp;
203 bp.p_ids = unz_p.ids;
204 bp.eta_ids = unz_eta.ids;
205 bp.obs_ids = unz_obs.ids;
206 bp.ctx = ctx;
207 bp.like = like;
208
209 // vieux style: noms / x0 / scale_hints / limits
210 bp.names.reserve(unz_p.ids.size() + unz_eta.ids.size());
211 bp.x0.reserve(unz_p.vals.size() + unz_eta.vals.size());
212 bp.scale_hints.reserve(unz_p.vals.size() + unz_eta.vals.size());
213
214 for (std::size_t i = 0; i < unz_p.ids.size(); ++i) {
215 bp.names.push_back(to_string_any(unz_p.ids[i]));
216 bp.x0.push_back(unz_p.vals[i]);
217 bp.scale_hints.push_back(std::max(1e-3, std::abs(unz_p.vals[i])));
218 if (bp.names.back().find("FCONST") != std::string::npos) {
219 bp.limits.push_back({i, 0.05, 0.35});
220 }
221 }
222
223 for (std::size_t i = 0; i < unz_eta.ids.size(); ++i) {
224 const std::size_t idx = unz_p.ids.size() + i;
225 const double c = unz_eta.vals[i];
226 const double s = (i < eta_stds.size()) ? std::max(1e-12, std::abs(eta_stds[i])) : 1e-3;
227
228 bp.names.push_back(to_string_any(unz_eta.ids[i]));
229 bp.x0.push_back(c);
230 bp.scale_hints.push_back(s);
231
232 const std::string& nm = bp.names.back();
233 if (nm.find("SMINPUTS:3") != std::string::npos) {
234 bp.limits.push_back({idx, 0.05, 0.30});
235 } else if (nm.find("MASS:") != std::string::npos ||
236 nm.find("FLIFE:") != std::string::npos ||
237 nm.find("FCONST:") != std::string::npos ||
238 nm.find("FMASS:") != std::string::npos ||
239 nm.find("SMINPUTS:5") != std::string::npos ||
240 nm.find("SMINPUTS:6") != std::string::npos) {
241 bp.limits.push_back({idx, std::max(1e-12, c - 5.0 * s), c + 5.0 * s});
242 }
243 }
244
245 return bp;
246}
247
249 bool ok = false;
250 double fmin = 1e300;
251 std::vector<double> x_hat;
253};
254
255static OldProfileResult old_profile_at_fixed_xy(
256 const fit_app::IFitBackend& backend,
257 const std::shared_ptr<BaseLikelihood>& like,
258 const std::vector<std::string>& names,
259 const std::vector<double>& x_start,
260 const std::vector<double>& scale_hints,
261 const std::vector<ParamLimit>& limits,
262 unsigned px,
263 unsigned py,
264 double xval,
265 double yval)
266{
267 const auto parameters = make_parameter_definitions(names, x_start, scale_hints, limits);
268
269 auto objective = fit_app::LambdaObjectiveFunction(
270 [like](const std::vector<double>& theta) {
271 return like->nll(theta);
272 },
273 0.5
274 );
275
277 opt.up = 0.5;
278 opt.strategy = 2;
279 opt.max_fcn = 30000;
280 opt.tolerance = 0.2;
281 opt.run_hesse = false;
282 opt.verbose = true;
283
284 auto raw = backend.minimize_with_fixed(objective, parameters, opt, {px, py}, {xval, yval});
285
286 OldProfileResult out;
287 out.ok = raw.diagnostics.ok;
288 out.raw = raw;
289 out.x_hat = x_start;
290
291 if (raw.diagnostics.ok && raw.values.size() == x_start.size()) {
292 out.fmin = raw.diagnostics.fmin;
293 out.x_hat = raw.values;
294 } else {
295 out.x_hat[px] = xval;
296 out.x_hat[py] = yval;
297 out.fmin = like->nll(out.x_hat); // fallback fini comme dans l'ancien esprit
298 }
299
300 return out;
301}
302
303static void print_theta(const std::string& label, const std::vector<double>& x) {
304 std::cout << label << " = (";
305 for (std::size_t i = 0; i < x.size(); ++i) {
306 std::cout << std::setprecision(17) << x[i];
307 if (i + 1 != x.size()) std::cout << ", ";
308 }
309 std::cout << ")\n";
310}
311
312} // namespace fit_app
313
314int main() {
315 using namespace fit_app;
316
317 HyperisoMaster hyp;
318 HyperisoConfig config_hyp;
319 config_hyp.model = Model::SM;
320 hyp.init("lha/si_input.flha", config_hyp);
321
322 auto oint = std::make_shared<ObservableInterface>();
325
326 StatisticConfig config;
327 config.MC_draws = 100;
328 config.advanced.MLE_max_iter = 120000;
329 config.advanced.MLE_tol = 0.2;
330 std::vector<ParamId> p_specs = {
331 ParamId{ParameterType::FLAVOR, "FCONST", {511, 1}},
332 ParamId{ParameterType::FLAVOR, "FCONST", {531, 1}}
333 };
334
335 std::shared_ptr<IStatParamOptimizerProxy> spop = std::make_shared<StatParamOptimizerProxy>();
336 auto model = std::make_shared<ObservableInterfaceProxy>(oint, spop);
337
338 std::shared_ptr<INuisancePathsProvider> npp = std::make_shared<DefaultNuisancePathsProvider>();
339
340 StatisticManager stat(
341 config,
342 model,
343 std::make_shared<StatCorrelationProxy>(),
344 std::make_shared<StatParameterProxy>(),
345 std::make_shared<StatParamSourcesProxy>(),
346 std::make_shared<StatDependencyPruner>(),
347 std::make_shared<NuisanceReader>(npp),
348 spop
349 );
350
351 BuiltProblem bp = build_problem(stat, config, model, p_specs);
352
353 // MLE jointe actuelle
354 auto fitter = std::make_shared<MLFitter>(bp.ctx, [model, obs_ids = bp.obs_ids, p_ids = bp.p_ids, eta_ids = bp.eta_ids]
355 (const std::vector<double>& p_vec, const std::vector<double>& eta_vec) -> std::vector<double> {
356 auto pred_map = model->predict_optimized(zip(p_ids, p_vec), zip(eta_ids, eta_vec));
357 return ordered_prediction_vector(obs_ids, pred_map);
358 });
359
360 auto fit = fitter->maximum_likelihood_fit(std::vector<double>(bp.x0.begin(), bp.x0.begin() + bp.p_ids.size()));
361
362 std::vector<double> theta_hat = fit.p_hat;
363 theta_hat.insert(theta_hat.end(), fit.eta_hat.begin(), fit.eta_hat.end());
364
365 std::cout << "\n=== JOINT MLE ===\n";
366 print_theta("theta_hat", theta_hat);
367 std::cout << "ell_hat = " << std::setprecision(17) << fit.ell_hat << "\n";
368
369 // Points à comparer
370 std::vector<std::pair<double,double>> test_points = {
371 {fit.p_hat[0], fit.p_hat[1]},
372 {0.20, 0.20},
373 {0.125, 0.125},
374 {0.05, 0.20},
375 {0.35, 0.35}
376 };
377
378
379
380 auto backend = make_minuit_backend();
381 Profiler profiler(make_minuit_backend());
382
383
384 SliceProfilingStrategy slice(0, 1, fit);
385
386 std::map<std::size_t, double> current = slice.init_warm_start();
387
388 for (const auto& [x, y] : test_points) {
389 std::cout << "\n====================================================\n";
390 std::cout << "Testing point (x, y) = (" << x << ", " << y << ")\n";
391
392 std::map<std::size_t, double> fresh = slice.init_warm_start();
393 ProfileRequest pr_fresh = slice.build_request(x, y, fresh);
394 auto new_res_fresh = profiler.profile(bp.like, pr_fresh);
395
396 std::cout << "\n[NEW Profiler::profile / fresh global seed]\n";
397 std::cout << "ok/converged = " << new_res_fresh.converged << "\n";
398 std::cout << "fmin = " << std::setprecision(17) << new_res_fresh.nll_hat << "\n";
399
400 std::vector<double> theta_prof_fresh(theta_hat.size(), std::numeric_limits<double>::quiet_NaN());
401 theta_prof_fresh[0] = x;
402 theta_prof_fresh[1] = y;
403 for (const auto& [idx, val] : new_res_fresh.theta_hat) {
404 theta_prof_fresh[idx] = val;
405 }
406 print_theta("theta_hat_prof_fresh", theta_prof_fresh);
407
408 // Old-style direct profiling
409 auto old_res = old_profile_at_fixed_xy(
410 *backend,
411 bp.like,
412 bp.names,
413 theta_hat,
414 bp.scale_hints,
415 bp.limits,
416 0, 1,
417 x, y
418 );
419
420 std::cout << "\n[OLD DIRECT minimize_with_fixed]\n";
421 std::cout << "ok = " << old_res.ok << "\n";
422 std::cout << "fmin = " << std::setprecision(17) << old_res.fmin << "\n";
423 std::cout << "nfcn = " << old_res.raw.diagnostics.nfcn << "\n";
424 std::cout << "edm = " << old_res.raw.diagnostics.edm << "\n";
425 print_theta("x_hat", old_res.x_hat);
426
427 // New-style current profiler
428 ProfileRequest pr = slice.build_request(x, y, current);
429 auto new_res = profiler.profile(bp.like, pr);
430
431 std::cout << "\n[NEW Profiler::profile]\n";
432 std::cout << "ok/converged = " << new_res.converged << "\n";
433 std::cout << "fmin = " << std::setprecision(17) << new_res.nll_hat << "\n";
434
435 std::vector<double> theta_prof(theta_hat.size(), std::numeric_limits<double>::quiet_NaN());
436 theta_prof[0] = x;
437 theta_prof[1] = y;
438 for (const auto& [idx, val] : new_res.theta_hat) {
439 theta_prof[idx] = val;
440 }
441 print_theta("theta_hat_prof", theta_prof);
442
443 if (new_res.converged) {
444 current = new_res.theta_hat;
445 }
446
447 std::cout << "\n[COMPARE]\n";
448 std::cout << "delta_f = " << std::setprecision(17) << (new_res.nll_hat - old_res.fmin) << "\n";
449
450 std::vector<double> theta_seed = theta_hat;
451 theta_seed[0] = x;
452 theta_seed[1] = y;
453 double f_seed = bp.like->nll(theta_seed);
454
455 std::cout << "\n[SEED ONLY]\n";
456 std::cout << "f_seed = " << std::setprecision(17) << f_seed << "\n";
457
458 std::cout << "\n[COMPARE]\n";
459 std::cout << "old_f = " << old_res.fmin << "\n";
460 std::cout << "new_f = " << new_res.nll_hat << "\n";
461 std::cout << "seed_f = " << f_seed << "\n";
462 std::cout << "delta_new_old = " << (new_res.nll_hat - old_res.fmin) << "\n";
463 std::cout << "delta_old_seed = " << (old_res.fmin - f_seed) << "\n";
464 std::cout << "delta_new_seed = " << (new_res.nll_hat - f_seed) << "\n";
465 }
466
467 return 0;
468}
Concrete profileable likelihood built from a model and joint distributions.
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
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.
Generic likelihood profiling engine.
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.
Minimizes a likelihood over free parameters subject to fixed constraints.
Definition Profiler.h:64
ProfileResult profile(std::shared_ptr< ILikelihood > base, const ProfileRequest &pr) const
Profiles a likelihood according to a request.
Definition Profiler.cpp:93
Builds slice-profile requests with fixed parameters of interest.
std::map< std::size_t, double > init_warm_start() const override
Creates the initial warm-start map for the strategy.
ProfileRequest build_request(double px, double py, const std::map< std::size_t, double > &current_argmin) const override
Builds a profiler request for one 2D scan point.
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.
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
int main()
Definition jpp.cpp:314
std::string to_string_any(const T &x)
Hash specialization for SymbolId<Tag>.
Definition BlockName.h:353
double MLE_tol
Minimizer tolerance passed to the backend.
std::size_t MLE_max_iter
Maximum number of minimizer function calls/iterations.
Configuration object controlling model, input flags and optional MARTY resources.
Definition Config.h:24
Model model
Current model.
Definition Config.h:33
Container for a computed observable value, optionally binned.
Composite identifier for a single parameter.
Definition ParamID.h:57
Input specification for a constrained profile minimization.
Definition Profiler.h:38
bool converged
True when the profiler accepted the result as reliable.
Definition Profiler.h:51
AdvancedStatisticConfig advanced
Advanced fit/pruning/covariance configuration.
std::size_t MC_draws
Number of accepted MC draws used for uncertainty propagation.
std::vector< double > scale_hints
Definition jpp.cpp:48
std::vector< ExperimentObs > obs_ids
Definition jpp.cpp:41
std::shared_ptr< BaseLikelihood > like
Definition jpp.cpp:44
std::vector< double > x0
Definition jpp.cpp:47
std::vector< std::string > names
Definition jpp.cpp:46
std::vector< ParamLimit > limits
Definition jpp.cpp:49
std::shared_ptr< LikelihoodContext > ctx
Definition jpp.cpp:43
std::vector< ParamId > eta_ids
Definition jpp.cpp:40
std::vector< ParamId > p_ids
Definition jpp.cpp:39
std::vector< double > x_hat
Definition jpp.cpp:251
fit_app::BackendFitResult raw
Definition jpp.cpp:252
std::size_t idx
Definition jpp.cpp:33
std::optional< std::pair< double, double > > limits