19 std::cout <<
"[FIT] Minuit diagnostics: ok=" <<
d.ok
20 <<
", has_valid_parameters=" <<
d.has_valid_parameters
21 <<
", has_valid_covar=" <<
d.has_valid_covar
22 <<
", has_posdef_covar=" <<
d.has_posdef_covar
23 <<
", has_accurate_covar=" <<
d.has_accurate_covar
24 <<
", made_posdef=" <<
d.made_posdef
25 <<
", hesse_failed=" <<
d.hesse_failed
26 <<
", reached_call_limit=" <<
d.reached_call_limit
27 <<
", above_max_edm=" <<
d.above_max_edm
28 <<
", fmin=" <<
d.fmin
30 <<
", nfcn=" <<
d.nfcn
33 if (!
d.cov_eigs.empty()) {
34 double min_eig = std::numeric_limits<double>::infinity();
35 double max_eig = -std::numeric_limits<double>::infinity();
36 std::size_t non_pos = 0;
38 for (
double eig :
d.cov_eigs) {
39 min_eig = std::min(min_eig, eig);
40 max_eig = std::max(max_eig, eig);
41 if (!(eig > 0.0)) ++non_pos;
44 std::cout <<
"[FIT] Minuit covariance eigenvalues: min=" << min_eig
45 <<
", max=" << max_eig
46 <<
", non_positive=" << non_pos
47 <<
", cond=" <<
d.cond_number
52void log_matrix_diagnostics(
const std::string& label,
const RealMatrix& M) {
53 std::cout <<
"[FIT] Matrix " << label
54 <<
" shape=" << M.
rows() <<
"x" << M.
cols()
57 if (M.
rows() == 0 || M.
cols() == 0) {
58 std::cout <<
"[FIT] Matrix " << label <<
" is empty" << std::endl;
63 std::cout <<
"[FIT] Matrix " << label <<
" is not square" << std::endl;
71 double min_eig = std::numeric_limits<double>::infinity();
72 double max_eig = -std::numeric_limits<double>::infinity();
73 std::size_t non_pos = 0;
76 for (std::size_t i = 0; i < eig.
D.
rows(); ++i) {
77 const double lambda = eig.
D.
at(i, i);
78 min_eig = std::min(min_eig, lambda);
79 max_eig = std::max(max_eig, lambda);
80 if (!(lambda > 0.0)) ++non_pos;
81 if (std::abs(lambda) < 1e-12) ++tiny;
84 std::cout <<
"[FIT] Matrix " << label
85 <<
" eig_min=" << min_eig
86 <<
", eig_max=" << max_eig
87 <<
", non_positive=" << non_pos
88 <<
", tiny(|eig|<1e-12)=" << tiny
90 }
catch (
const std::exception& e) {
91 std::cout <<
"[FIT] Matrix " << label
92 <<
" diagnostics failed: " << e.what()
98 log_matrix_diagnostics(label, M);
101 }
catch (
const std::exception& e) {
102 std::ostringstream oss;
103 oss <<
"Failed to invert " << label <<
": " << e.what();
104 throw std::runtime_error(oss.str());
123std::vector<std::size_t> make_fixed_p_indices(std::size_t p_dim) {
124 std::vector<std::size_t> idx(p_dim);
125 std::iota(idx.begin(), idx.end(), 0);
129void fill_nan_profile_errors(
FitResult& fr, std::size_t p_dim) {
130 const double qnan = std::numeric_limits<double>::quiet_NaN();
133 for (std::size_t i = 0; i < p_dim; ++i) {
134 for (std::size_t j = 0; j < p_dim; ++j) {
140void fill_profile_result_from_cov(
const RealMatrix& cov_prof,
FitResult& fr, std::size_t p_dim) {
144 for (std::size_t i = 0; i < p_dim; ++i) {
145 fr.
p_hat_std[i] = std::sqrt(std::max(0.0, cov_prof.
at(i, i)));
148 for (std::size_t i = 0; i < p_dim; ++i) {
149 for (std::size_t j = 0; j < p_dim; ++j) {
153 (si > 0.0 && sj > 0.0) ? cov_prof.
at(i, j) / (si * sj) : 0.0;
164 if (def.
limits.has_value()) {
165 const auto [lo, hi] = def.
limits.value();
166 const double dist_lo = x - lo;
167 const double dist_hi = hi - x;
168 const double max_sym = 0.45 * std::max(0.0, std::min(dist_lo, dist_hi));
170 h = std::min(h, max_sym);
174 if (!std::isfinite(h) || h <= 0.0) {
184 double max_pos_eig = 0.0;
185 double min_eig = std::numeric_limits<double>::infinity();
187 for (std::size_t i = 0; i < eig.
D.
rows(); ++i) {
188 const double ev = eig.
D.
at(i, i);
189 min_eig = std::min(min_eig, ev);
191 max_pos_eig = std::max(max_pos_eig, ev);
195 if (!(max_pos_eig > 0.0)) {
196 std::ostringstream oss;
197 oss <<
"Numerical profile Hessian is not locally convex "
198 <<
"(all eigenvalues <= 0, min_eig=" << min_eig <<
").";
199 throw std::runtime_error(oss.str());
202 const double floor = std::max(1e-10, rel_floor * max_pos_eig);
205 for (std::size_t i = 0; i < eig.
D.
rows(); ++i) {
206 const double ev = eig.
D.
at(i, i);
207 Dreg.at(i, i) = (ev > floor) ? ev : floor;
215 const std::vector<fit_app::ParameterDefinition>& defs,
216 const std::vector<double>& theta_anchor,
218 const std::vector<double>& p_fixed,
221 if (defs.size() == p_dim) {
222 if (p_fixed.size() != p_dim) {
223 throw std::invalid_argument(
224 "Profile point dimension does not match the fit-parameter dimension."
228 const double direct = objective(p_fixed);
229 if (!std::isfinite(direct)) {
230 throw std::runtime_error(
231 "Direct profile evaluation returned a non-finite value."
237 std::vector<fit_app::ParameterDefinition> local_defs = defs;
238 for (std::size_t i = 0; i < local_defs.size(); ++i) {
239 local_defs[i].value = theta_anchor[i];
241 for (std::size_t i = 0; i < p_dim; ++i) {
242 local_defs[i].value = p_fixed[i];
245 const std::vector<std::size_t> fixed_idx = make_fixed_p_indices(p_dim);
251 throw std::runtime_error(
"Profile minimization failed while building fallback Hessian.");
259 const std::vector<fit_app::ParameterDefinition>& defs,
260 const std::vector<double>& theta_hat,
268 const std::vector<double> p_hat(theta_hat.begin(), theta_hat.begin() + p_dim);
270 for (
int iter = 0; iter < 8; ++iter) {
272 auto p_minus = p_hat;
276 const double f_plus =
277 profiled_nll_at(minimizer, objective, defs, theta_hat, p_dim, p_plus, profile_opt);
278 const double f_minus =
279 profiled_nll_at(minimizer, objective, defs, theta_hat, p_dim, p_minus, profile_opt);
281 const double d2 = (f_plus - 2.0 * f0 + f_minus) / (h * h);
286 if (d2 > 0.0 && f_plus >= f0 - 1e-8 && f_minus >= f0 - 1e-8) {
291 if (!(h > 0.0))
break;
299 const std::vector<fit_app::ParameterDefinition>& defs,
300 const std::vector<double>& theta_hat,
308 std::vector<double> p_hat(theta_hat.begin(), theta_hat.begin() + p_dim);
309 std::vector<double>
h(p_dim, 0.0);
311 for (std::size_t i = 0; i < p_dim; ++i) {
312 double h0 = choose_profile_fd_step(defs[i], p_hat[i], step_scale);
314 std::ostringstream oss;
315 oss <<
"Cannot build profile Hessian: initial step collapsed for parameter "
317 throw std::runtime_error(oss.str());
320 h[i] = stabilize_profile_step_1d(
321 minimizer, objective, defs, theta_hat, p_dim, i, h0, profile_opt, f0
325 std::ostringstream oss;
326 oss <<
"Cannot build profile Hessian: stabilized step collapsed for parameter "
328 throw std::runtime_error(oss.str());
332 for (std::size_t i = 0; i < p_dim; ++i) {
334 auto p_minus = p_hat;
338 const double f_plus =
339 profiled_nll_at(minimizer, objective, defs, theta_hat, p_dim, p_plus, profile_opt);
340 const double f_minus =
341 profiled_nll_at(minimizer, objective, defs, theta_hat, p_dim, p_minus, profile_opt);
343 H.at(i, i) = (f_plus - 2.0 * f0 + f_minus) / (h[i] * h[i]);
345 for (std::size_t j = i + 1; j < p_dim; ++j) {
351 p_pp[i] +=
h[i]; p_pp[j] +=
h[j];
352 p_pm[i] +=
h[i]; p_pm[j] -=
h[j];
353 p_mp[i] -=
h[i]; p_mp[j] +=
h[j];
354 p_mm[i] -=
h[i]; p_mm[j] -=
h[j];
357 profiled_nll_at(minimizer, objective, defs, theta_hat, p_dim, p_pp, profile_opt);
359 profiled_nll_at(minimizer, objective, defs, theta_hat, p_dim, p_pm, profile_opt);
361 profiled_nll_at(minimizer, objective, defs, theta_hat, p_dim, p_mp, profile_opt);
363 profiled_nll_at(minimizer, objective, defs, theta_hat, p_dim, p_mm, profile_opt);
365 const double hij = (f_pp - f_pm - f_mp + f_mm) / (4.0 * h[i] * h[j]);
371 return 0.5 * (
H +
H.transpose());
377 : fit_options_(options)
379 const std::size_t p_dim = ctx->fp_defs.size();
380 this->like_ = std::make_shared<BaseLikelihood>(model, ctx, p_dim);
384 : like_(
std::move(like)),
385 fit_options_(options)
388 throw std::invalid_argument(
"MLFitter: null likelihood");
393 const auto defs = like_->get_param_defs();
394 const std::size_t p_dim = p0.size();
395 const std::size_t dim = defs.size();
397 std::vector<fit_app::ParameterDefinition> theta0 = defs;
398 for (std::size_t i = 0; i < p_dim; ++i) {
399 theta0[i].value = p0[i];
403 [
this](
const std::vector<double>& theta) {
404 return like_->nll(theta);
411 opt.verbose = fit_options_.
verbose;
413 opt.strategy = fit_options_.
strategy;
415 if (fit_options_.
max_fcn > 0) {
416 opt.max_fcn = fit_options_.
max_fcn;
422#ifdef FIT_APP_HAS_RUN_MINOS
426 std::cout <<
"[FIT] MINOS was requested, but fit_app::FitOptions has no run_minos flag in this build.\n"
427 <<
"[FIT] Define FIT_APP_HAS_RUN_MINOS only if your backend exposes opt.run_minos.\n";
434 like_->disable_debug_trace();
441 log_matrix_diagnostics(
"Minuit covariance", res.
covariance);
448 bool have_profile_covariance =
false;
456 RealMatrix H_eta_eta(dim - p_dim, dim - p_dim);
458 for (std::size_t i = 0; i < p_dim; ++i) {
459 for (std::size_t j = 0; j < p_dim; ++j) {
460 H_p_p.
at(i, j) = H.at(i, j);
464 for (std::size_t i = 0; i < p_dim; ++i) {
465 for (std::size_t j = p_dim; j < dim; ++j) {
466 H_p_eta.
at(i, j - p_dim) = H.at(i, j);
470 for (std::size_t i = p_dim; i < dim; ++i) {
471 for (std::size_t j = p_dim; j < dim; ++j) {
472 H_eta_eta.
at(i - p_dim, j - p_dim) = H.at(i, j);
478 cov_prof = invert_or_throw(H_p_p,
"H_p_p (no nuisance block)");
480 RealMatrix H_eta_eta_inv = invert_or_throw(H_eta_eta,
"H_eta_eta");
482 log_matrix_diagnostics(
"H_prof", H_prof);
483 cov_prof = invert_or_throw(H_prof,
"H_prof");
486 fill_profile_result_from_cov(cov_prof, fr, p_dim);
487 have_profile_covariance =
true;
488 }
catch (
const std::exception& e) {
489 std::cout <<
"[FIT] Failed to build profiled covariance from Minuit covariance: "
490 << e.what() << std::endl;
496 std::cout <<
"[FIT] Falling back to numerical profile Hessian on fit parameters.\n";
502 RealMatrix H_prof_num = numerical_profile_hessian(
513 log_matrix_diagnostics(
"Numerical profile Hessian", H_prof_num);
520 log_matrix_diagnostics(
"Regularized numerical profile Hessian", H_prof_reg);
524 "Regularized numerical profile Hessian"
527 fill_profile_result_from_cov(cov_prof, fr, p_dim);
528 have_profile_covariance =
true;
529 }
catch (
const std::exception& e) {
530 std::cout <<
"[FIT] Numerical profile-Hessian fallback failed: "
531 << e.what() << std::endl;
535 if (!have_profile_covariance) {
536 std::cout <<
"[FIT] No covariance could be constructed. Returning MLE with NaN errors.\n";
537 fill_nan_profile_errors(fr, p_dim);
541 this->master_fit_result = fr;
547 if (!this->master_fit_success)
548 LOG_ERROR(
"InvalidState",
"ML fit must have converged before contour computation is available.");
550 const bool bad_errors =
551 std::isnan(this->master_fit_result.
p_hat_std.at(x_id)) ||
552 std::isnan(this->master_fit_result.
p_hat_std.at(y_id));
555 std::cout <<
"[FIT] Minuit contour disabled because local covariance is unavailable; "
556 "using fallback method directly.\n";
563 cc.
fr = this->master_fit_result;
std::function< std::vector< double >(const std::vector< double > &p, const std::vector< double > &eta)> ModelFn
Model function signature used by BaseLikelihood.
@ MINUIT
Minuit contour extractor.
High-level maximum-likelihood fitting and confidence-contour API.
ProfileBackend
Backend used to profile nuisance parameters during contour building.
#define LOG_ERROR(type,...)
Macro for logging error messages and terminating the application.
ProfilerMode
Available algorithms for profiling free parameters.
@ MINUIT
Use the configured numerical minimizer backend.
@ LAPLACE_NUISANCE
Use the Laplace nuisance approximation when possible, with Minuit fallback.
Orchestrates profiled likelihood contour computation.
Contour compute_contour(double z, std::array< double, 4 > bounds, std::size_t resolution)
Computes a profiled 2D contour for a requested significance.
MLFitter(std::shared_ptr< LikelihoodContext > ctx, const ModelFn &model, MLFitOptions options={})
Constructs a fitter from a likelihood context and model function.
FitResult maximum_likelihood_fit(const std::vector< double > &p0)
Runs the global maximum-likelihood fit.
Contour contour(std::size_t x_id, std::size_t y_id, double z, std::array< double, 4 > bounds, ContourOptions options) const
Computes a 2D confidence contour after the global fit.
std::size_t rows() const
Returns the number of rows.
EigenSystem eig() const
Computes the eigensystem of a symmetric matrix.
double & at(size_t i, size_t j)
Returns a mutable reference to element (i,j) with bounds checking.
std::size_t cols() const
Returns the number of columns.
RealMatrix transpose() const
Returns the transpose of the matrix.
RealMatrix inv() const
Computes the inverse of the matrix via LU decomposition.
virtual BackendFitResult minimize_with_fixed(const IObjectiveFunction &objective, const std::vector< ParameterDefinition > ¶meters, const FitOptions &options, const std::vector< std::size_t > &fixed_indices, const std::vector< double > &fixed_values) const =0
complex_t h(double s, double m_q, double mu_b)
complex_t H(double z, double r_P)
std::unique_ptr< IFitBackend > make_minuit_backend()
double safe_step(double value, double scale_hint)
Hash specialization for SymbolId<Tag>.
double f(double x)
Wilson special function f depending on x.
Configuration object for ContourEngine.
ProfilerMode profile_backend
Backend used to profile nuisance/free parameters.
ProfilingMethod profiling_method
Profiling strategy used for non-displayed parameters.
ContourProgressCallback on_progress
Optional progress callback invoked during contour computation.
std::size_t y_id
Indices of the two fit parameters displayed on the contour axes.
std::optional< ContourAlgorithm > fallback_contour_method
Optional fallback algorithm if the primary fails.
ContourAlgorithm primary_contour_method
Primary contour extraction algorithm.
FitResult fr
Global fit result used for central values, uncertainties, and correlations.
Runtime options controlling 2D contour computation.
ContourProgressCallback on_progress
ContourAlgorithm primary_contour_method
ProfilingMethod profiling_method
ProfileBackend profile_backend
std::optional< ContourAlgorithm > fallback_contour_method
Output of a contour extraction algorithm.
Container for an eigendecomposition.
RealMatrix P
Diagonal matrix of eigenvalues.
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.
Runtime options controlling the global maximum-likelihood fit.
double profile_hessian_eig_floor_rel
double profile_hessian_step_scale
std::size_t trace_max_evals
bool allow_profile_hessian_fallback
std::vector< double > values
FitDiagnostics diagnostics
bool has_valid_parameters
std::optional< std::pair< double, double > > limits