Hyperiso 1.0.3
Modular flavour-physics calculations, Wilson coefficients and statistical inference
Loading...
Searching...
No Matches
Profiler.cpp
Go to the documentation of this file.
1#include "Profiler.h"
2
3#include <algorithm>
4#include <iomanip>
5#include <iostream>
6#include <limits>
7#include <stdexcept>
8#include <string>
9
10namespace {
11
12static bool acceptable_for_profile(const fit_app::BackendFitResult& r) {
13 if (!r.diagnostics.has_valid_parameters) return false;
14 if (!std::isfinite(r.diagnostics.fmin)) return false;
15 if (r.diagnostics.reached_call_limit) return false;
16
17 if (r.diagnostics.ok) return true;
18
22 r.diagnostics.edm < 1.0) {
23 return true;
24 }
25
26 return false;
27}
28
29static ProfileResult full_theta_result_from_split(
30 const std::vector<double>& p,
31 const std::vector<double>& eta,
32 double nll_hat,
33 bool ok
34) {
35 ProfileResult out;
36 out.nll_hat = nll_hat;
37 out.converged = ok;
38
39 for (std::size_t i = 0; i < p.size(); ++i) {
40 out.theta_hat[i] = p[i];
41 }
42
43 for (std::size_t a = 0; a < eta.size(); ++a) {
44 out.theta_hat[p.size() + a] = eta[a];
45 }
46
47 return out;
48}
49
50static ProfileResult direct_profile_no_free(
51 const std::shared_ptr<ILikelihood>& base,
52 const ProfileRequest& pr
53) {
54 ProfileResult out;
55 out.converged = false;
56 out.nll_hat = 1e300;
57
58 std::vector<double> theta = pr.start;
59 if (theta.empty()) {
60 theta.assign(base->dim(), 0.0);
61 }
62 if (theta.size() != base->dim()) {
63 throw std::runtime_error("direct_profile_no_free: bad start dimension");
64 }
65
66 for (const auto& [idx, val] : pr.fixed_params) {
67 if (idx >= theta.size()) {
68 throw std::runtime_error("direct_profile_no_free: fixed index out of range");
69 }
70 theta[idx] = val;
71 }
72
73 out.nll_hat = base->nll(theta);
74 out.converged = std::isfinite(out.nll_hat);
75
76 for (std::size_t i = 0; i < theta.size(); ++i) {
77 out.theta_hat[i] = theta[i];
78 }
79
80 return out;
81}
82
83} // namespace
84
86 std::shared_ptr<fit_app::IFitBackend> minimizer,
87 ProfilerMode mode
88)
89 : minimizer(std::move(minimizer)),
90 mode(mode)
91{}
92
94 std::shared_ptr<ILikelihood> base,
95 const ProfileRequest& pr
96) const {
97 if (pr.free_params.empty()) {
98 return direct_profile_no_free(base, pr);
99 }
100
101 if (mode == ProfilerMode::LAPLACE_NUISANCE) {
102 try {
103 return profile_laplace_nuisance(base, pr);
104 } catch (const std::exception& e) {
105 std::cout << "[LAPLACE PROFILE] failed, falling back to Minuit: "
106 << e.what() << std::endl;
107 return profile_minuit(base, pr);
108 }
109 }
110
111 return profile_minuit(base, pr);
112}
113
114ProfileResult Profiler::profile_laplace_nuisance(
115 std::shared_ptr<ILikelihood> base,
116 const ProfileRequest& pr
117) const {
118 auto like = std::dynamic_pointer_cast<IProfileableLikelihood>(base);
119
120 if (!like) {
121 throw std::runtime_error("Likelihood does not implement IProfileableLikelihood");
122 }
123
124 const std::size_t p_dim = like->p_dimension();
125 const std::size_t eta_dim = like->eta_dimension();
126
127 if (p_dim + eta_dim != like->dim()) {
128 throw std::runtime_error("Inconsistent p/eta dimensions in likelihood");
129 }
130
131 for (std::size_t i : pr.free_params) {
132 if (i < p_dim) {
133 throw std::runtime_error(
134 "Laplace nuisance profiler only supports fixed fit parameters; "
135 "free fit parameters require Minuit fallback"
136 );
137 }
138 }
139
140 std::vector<double> p = like->central_p();
141
142 for (const auto& [idx, val] : pr.fixed_params) {
143 if (idx < p_dim) {
144 p[idx] = val;
145 }
146 }
147
149
150 opts.stationarity_threshold = 5e-2;
151 opts.max_refined_eta = 4;
152 opts.max_refinement_iters = 2;
153 opts.max_newton_step_in_sigma = 1.0;
155
156 const LaplaceProfileComputation comp =
157 laplace_profile_eta_refined(*like, p, opts);
158
159 return full_theta_result_from_split(
160 p,
161 comp.eta_hat,
162 comp.nll_hat,
163 comp.ok
164 );
165}
166
167
168ProfileResult Profiler::profile_minuit(std::shared_ptr<ILikelihood> base, const ProfileRequest& pr) const {
169 if (pr.fixed_params.size() + pr.free_params.size() != base->dim()) {
170 LOG_ERROR("InvalidArgument", "Dimension mismatch in profiler. Fit dimension is", base->dim(),
171 ", found", pr.fixed_params.size(), "fixed params and", pr.free_params.size(), "free.");
172 }
173
174 auto unzipped = unzip(pr.fixed_params);
175 std::vector<std::size_t> fixed_idx = unzipped.ids;
176 std::vector<double> fixed_vals = unzipped.vals;
177
179 [base](std::vector<double> theta) { return base->nll(theta); },
180 0.5
181 );
182
183 auto make_defs_from = [&](const std::vector<double>& start) {
184 auto defs = base->get_param_defs();
185 if (!start.empty()) {
186 if (start.size() != defs.size()) {
187 LOG_ERROR("InvalidArgument", "ProfileRequest::start has wrong dimension.");
188 }
189 for (std::size_t i = 0; i < defs.size(); ++i) {
190 defs[i].value = start[i];
191 }
192 }
193 for (std::size_t k = 0; k < fixed_idx.size(); ++k) {
194 defs[fixed_idx[k]].value = fixed_vals[k];
195 }
196 return defs;
197 };
198
199 auto central_seed = [&]() {
200 auto defs = base->get_param_defs();
201 std::vector<double> out(defs.size());
202 for (std::size_t i = 0; i < defs.size(); ++i) out[i] = defs[i].value;
203 for (std::size_t k = 0; k < fixed_idx.size(); ++k) out[fixed_idx[k]] = fixed_vals[k];
204 return out;
205 }();
206
208 opt.run_hesse = false;
209 opt.verbose = false;
210 opt.strategy = 2;
211 opt.max_fcn = 30000;
212 opt.tolerance = 0.2;
213
215 bool best_raw_set = false;
216
217 auto try_one = [&](const std::vector<double>& seed,
218 unsigned strategy,
219 unsigned max_fcn,
220 double tolerance) -> fit_app::BackendFitResult {
221 auto defs = make_defs_from(seed);
222 fit_app::FitOptions local_opt = opt;
223 local_opt.strategy = strategy;
224 local_opt.max_fcn = max_fcn;
225 local_opt.tolerance = tolerance;
226 return minimizer->minimize_with_fixed(f, defs, local_opt, fixed_idx, fixed_vals);
227 };
228
229 fit_app::BackendFitResult r1 = try_one(pr.start.empty() ? central_seed : pr.start, 2, 30000, 0.2);
230 best_raw = r1;
231 best_raw_set = true;
232
233 if (!acceptable_for_profile(r1)) {
234 fit_app::BackendFitResult r2 = try_one(central_seed, 2, 60000, 0.2);
235 if ((!best_raw_set) || (std::isfinite(r2.diagnostics.fmin) && r2.diagnostics.fmin < best_raw.diagnostics.fmin)) {
236 best_raw = r2;
237 best_raw_set = true;
238 }
239
240 if (!acceptable_for_profile(best_raw)) {
241 fit_app::BackendFitResult r3 = try_one(central_seed, 1, 100000, 0.5);
242 if (std::isfinite(r3.diagnostics.fmin) && r3.diagnostics.fmin < best_raw.diagnostics.fmin) {
243 best_raw = r3;
244 }
245 }
246 }
247
248 const bool accepted = acceptable_for_profile(best_raw);
249
250 if (!accepted) {
251 std::cout << "\n=== [PROFILE FAIL] ===\n";
252 std::cout << "fixed_idx = [ ";
253 for (auto i : fixed_idx) std::cout << i << " ";
254 std::cout << "]\n";
255
256 std::cout << "fixed_vals = [ ";
257 for (auto v : fixed_vals) std::cout << std::setprecision(17) << v << " ";
258 std::cout << "]\n";
259
260 std::cout << "fmin = " << best_raw.diagnostics.fmin << "\n";
261 std::cout << "edm = " << best_raw.diagnostics.edm << "\n";
262 std::cout << "nfcn = " << best_raw.diagnostics.nfcn << "\n";
263 std::cout << "ok = " << best_raw.diagnostics.ok << "\n";
264 std::cout << "has_valid_params = " << best_raw.diagnostics.has_valid_parameters << "\n";
265 std::cout << "has_valid_covar = " << best_raw.diagnostics.has_valid_covar << "\n";
266 std::cout << "has_accurate_covar = " << best_raw.diagnostics.has_accurate_covar << "\n";
267 std::cout << "has_posdef_covar = " << best_raw.diagnostics.has_posdef_covar << "\n";
268 std::cout << "made_posdef = " << best_raw.diagnostics.made_posdef << "\n";
269 std::cout << "hesse_failed = " << best_raw.diagnostics.hesse_failed << "\n";
270 }
271
272 ProfileResult pres;
273 pres.nll_hat = best_raw.diagnostics.fmin;
274 for (std::size_t i : pr.free_params) {
275 pres.theta_hat[i] = best_raw.values[i];
276 }
277 pres.converged = accepted;
278
279 return pres;
280}
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_ERROR(type,...)
Macro for logging error messages and terminating the application.
Definition Logger.h:41
Generic likelihood profiling engine.
ProfilerMode
Available algorithms for profiling free parameters.
Definition Profiler.h:25
@ LAPLACE_NUISANCE
Use the Laplace nuisance approximation when possible, with Minuit fallback.
Profiler(std::shared_ptr< fit_app::IFitBackend > minimizer, ProfilerMode mode=ProfilerMode::MINUIT)
Constructs a profiler.
Definition Profiler.cpp:85
ProfileResult profile(std::shared_ptr< ILikelihood > base, const ProfileRequest &pr) const
Profiles a likelihood according to a request.
Definition Profiler.cpp:93
csl::Expr v
Definition sm.h:110
Hash specialization for SymbolId<Tag>.
Definition BlockName.h:353
double f(double x)
Wilson special function f depending on x.
Result of a Laplace nuisance-profile computation.
bool ok
True when the computation produced a finite, usable result.
double nll_hat
Profiled or approximate profiled NLL value.
std::vector< double > eta_hat
Estimated profiled nuisance vector.
Numerical controls for the hybrid Laplace/Newton nuisance profiler.
std::size_t max_refinement_iters
Maximum number of outer correction cycles.
double max_newton_step_in_sigma
Maximum Newton displacement measured in nuisance standard deviations.
double stationarity_threshold
Threshold on above which a direction is refined.
std::size_t max_refined_eta
Maximum number of nuisance directions corrected by Newton refinement.
bool use_direct_nll_for_final_value
If true, use the direct NLL at the final profiled point as the reported value.
Input specification for a constrained profile minimization.
Definition Profiler.h:38
std::vector< std::size_t > free_params
Global indices of parameters released during profiling.
Definition Profiler.h:39
std::map< std::size_t, double > fixed_params
Fixed parameter values keyed by global parameter index.
Definition Profiler.h:40
std::vector< double > start
Initial full parameter vector used as a warm start.
Definition Profiler.h:41
Output of a constrained profile minimization.
Definition Profiler.h:48
double nll_hat
Minimum or approximate profiled NLL value.
Definition Profiler.h:49
bool converged
True when the profiler accepted the result as reliable.
Definition Profiler.h:51
std::map< std::size_t, double > theta_hat
Profiled parameter values keyed by global parameter index.
Definition Profiler.h:50
std::vector< double > values