40 for (std::size_t i = 0; i < vec.size(); ++i) {
41 std::cout << std::setprecision(17) << vec[i]
42 << (i + 1 == vec.size() ?
" " :
", ");
54 const std::vector<std::string>& names,
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] <<
","
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";
75 for (
const auto& p : c68) {
76 out <<
"0.683," << std::setprecision(17) << p.first <<
"," << p.second <<
"\n";
78 for (
const auto& p : c95) {
79 out <<
"0.95," << std::setprecision(17) << p.first <<
"," << p.second <<
"\n";
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();
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);
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";
133 double fmin = std::numeric_limits<double>::quiet_NaN();
134 double edm = std::numeric_limits<double>::quiet_NaN();
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
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");
157 std::vector<ParameterDefinition> parameters;
158 parameters.reserve(names.size());
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));
168 for (
const auto& lim : limits) {
169 if (lim.idx < parameters.size()) {
170 parameters[lim.idx].limits = std::make_pair(lim.low, lim.high);
177static FitOptions to_backend_fit_options(
const MinuitFitOptions& opt) {
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;
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,
199 const auto parameters = make_parameter_definitions(names, x0, scale_hints, limits);
202 return extract_result(backend_fit, opt.
verbose);
225 if (verbose && !out.
cov_eigs.empty()) {
226 std::cout <<
"Cov eigen min/max = "
227 << std::setprecision(6) << out.
cov_eigs.front()
235 const IFitBackend& backend_;
244 std::vector<double>
x0;
247 std::function<double(
const std::vector<double>&)>
f_joint;
267 , like_(
std::move(ctx))
268 , model_(
std::move(model))
270 , tolerance_(tolerance)
271 , strategy_(strategy) {}
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());
282 const std::vector<ParamId>& eta_ids,
284 const std::size_t p_dim = p0.size();
287 eta0.emplace_back(eta_def.value);
291 if (eta0.size() != eta_scales.size()) {
292 throw std::runtime_error(
"eta central values and eta stds do not have same size");
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());
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;
306 for (
double s : eta_scales) {
307 problem.
scale_hints.push_back(std::max(1e-12, std::fabs(s)));
310 problem.
names.reserve(problem.
x0.size());
314 for (std::size_t i = 0; i < p_dim; ++i) {
315 if (problem.
names[i].find(
"FCONST") != std::string::npos) {
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]));
325 if (nm.find(
"SMINPUTS:3") != std::string::npos) {
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});
342 opt.
max_fcn =
static_cast<unsigned>(max_fcn_);
363 for (std::size_t i = 0; i < p_dim; ++i) {
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)));
372 (di > 0.0 && dj > 0.0) ? (mj.
cov.
at(i, j) / (di * dj)) : 0.0;
376 for (std::size_t i = 0; i < p_dim && i < mj.
x_err.size(); ++i) {
387 Vector pred = model_(p, eta);
390 for (std::size_t i = 0; i < pred.size(); ++i) {
396 return -(ell_obs + ell_eta);
399 const IFitBackend& backend_;
402 std::size_t max_fcn_;
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;
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,
455 const auto parameters = make_parameter_definitions(names, x_start, scale_hints, limits);
477 if (out.ok && fit.
values.size() == x_start.size()) {
480 out.x_hat[px] = xval;
481 out.x_hat[py] = yval;
482 out.fmin = f_joint(out.x_hat);
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);
523 std::vector<std::pair<double, double>>& out_points)
const {
525 const double scale_factor = std::sqrt(up_contour / 0.5);
527 for (std::size_t i = 0; i < refit_scales.size() && i < input.
best_fit.
x_err.size(); ++i) {
529 if (std::isfinite(err) && err > 0.0) {
530 refit_scales[i] = std::max(refit_scales[i], err * scale_factor);
534 const auto refit_parameters = make_parameter_definitions(input.
problem.
names,
539 const LambdaObjectiveFunction objective(input.
problem.
f_joint, up_contour);
541 FitOptions refit_opt;
542 refit_opt.up = up_contour;
546 refit_opt.run_hesse =
true;
547 refit_opt.verbose =
false;
549 const BackendFitResult refit = input.
backend->
minimize(objective, refit_parameters, refit_opt);
550 if (!refit.diagnostics.ok || !refit.state) {
554 ContourOptionsBackEnd contour_opt;
555 contour_opt.up = up_contour;
556 contour_opt.npoints = opt_.
npoints;
557 contour_opt.strategy = opt_.
strategy;
561 const BackendContourResult contour = input.
backend->
contour(objective,
566 out_points = contour.
points;
567 return contour.success;
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));
603 if (!(xhi > xlo)) { xlo = 0.10; xhi = 0.30; }
604 if (!(yhi > ylo)) { ylo = 0.10; yhi = 0.30; }
608 result.
z.assign(opt_.
nx * opt_.
ny, 1e300);
612 for (std::size_t iy = 0; iy < opt_.
ny; ++iy) {
613 const bool reverse = (iy % 2 == 1);
616 for (std::size_t ix = 0; ix < opt_.
nx; ++ix) {
617 auto pr = profile_at_fixed_xy(input,
621 result.
z[iy * opt_.
nx + ix] = pr.fmin - input.
best_fval;
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,
631 result.
z[iy * opt_.
nx + ix] = pr.fmin - input.
best_fval;
643 const std::vector<double>& seed,
646 return profiled_fit_at_fixed_xy(*input.
backend,
667 std::unique_ptr<IContourStrategy> fallback)
668 : primary_(
std::move(primary)), fallback_(
std::move(fallback)) {}
673 return primary_result;
676 std::cerr <<
"[WARN] MnContours failed; fallback to grid scan.\n";
677 return fallback_->compute(input);
681 std::unique_ptr<IContourStrategy> primary_;
682 std::unique_ptr<IContourStrategy> fallback_;
690 std::vector<ParamId>
p_ids;
692 std::vector<ExperimentObs>
obs_ids;
694 std::shared_ptr<ObservableInterfaceProxy>
model;
700 const std::shared_ptr<ObservableInterfaceProxy>& model, std::vector<ParamId> p_specs) {
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";
715 for (
const auto& [pid, _] : p_specs_map) eta_specs_real.erase(pid);
718 auto unz_p =
unzip(p_specs_map);
719 auto unz_eta =
unzip(eta_specs_real);
720 auto unz_obs =
unzip(exp_obs_map);
725 if (nuisance_dist->get_stds().size() != unz_eta.vals.size()) {
726 throw std::runtime_error(
"nuisance std size and eta central size mismatch");
728 if (exp_obs_dist->dim() != unz_obs.vals.size()) {
729 throw std::runtime_error(
"exp obs dim and exp obs values size mismatch");
751int main(
int argc,
char** argv) {
757 hyp.
init(
"lha/si_input.flha", config_hyp);
759 auto oint = std::make_shared<ObservableInterface>();
767 std::vector<ParamId> p_specs = {
772 std::shared_ptr<IStatParamOptimizerProxy> spop = std::make_shared<StatParamOptimizerProxy>();
773 auto model = std::make_shared<ObservableInterfaceProxy>(oint, spop);
775 std::shared_ptr<INuisancePathsProvider> npp = std::make_shared<DefaultNuisancePathsProvider>();
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),
788 BuiltProblem built = build_problem(stat, config, model, p_specs);
789 std::unique_ptr<IFitBackend> backend = make_minuit_backend();
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));
796 out.reserve(obs_ids.size());
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;
805 if (it == vec.end()) {
806 throw std::runtime_error(
"Missing predicted observable/bin");
808 out.push_back(it->value);
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();
819 const auto& fr = fit_out.
fr;
820 const auto& mj = fit_out.
mj;
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";
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";
831 std::cerr <<
"[ERROR] Minuit fit invalid.\n";
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";
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";
848 if (built.p_ids.size() == 2) {
852 contour_input.
px = 0;
853 contour_input.
py = 1;
855 contour_input.
p_hat = fr.p_hat;
856 contour_input.
p_std = fr.p_hat_std;
859 contour_input.
backend = backend.get();
862 std::make_unique<MnContoursStrategy>(),
863 std::make_unique<GridProfileContourStrategy>()
869 std::cerr <<
"[ERROR] Contour computation failed.\n";
874 CsvExporter::save_grid(
"grid.csv",
880 std::cout <<
"[INFO] Wrote grid.csv\n";
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();
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) {
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";
904 CsvExporter::save_contours(
"contours.csv",
909 std::cout <<
"[INFO] Wrote contours.csv\n";
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.
UnzipResult1D< T, U > unzip(const std::map< T, U > &indexed)
Splits a map into parallel id and value vectors.
#define LOG_INFO(...)
Macro for logging informational messages.
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.
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)
GridProfileContourStrategy()=default
GridProfileContourStrategy(const Options &options)
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 > ¶meters, 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 > ¶meters, 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)
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>.
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.
Model model
Current model.
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.
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
FitDiagnostics diagnostics
std::vector< ExperimentObs > obs_ids
std::shared_ptr< ObservableInterfaceProxy > model
std::vector< ParamId > eta_ids
std::vector< ParamId > p_ids
std::vector< std::pair< double, double > > c95
std::vector< std::pair< double, double > > c68
std::vector< double > cov_eigs
JointLikelihoodProblem problem
std::vector< std::string > names
std::vector< double > scale_hints
std::function< double(const std::vector< double > &)> f_joint
std::vector< ParamLimit > limits
BackendFitResult backend_fit
std::vector< double > cov_eigs
std::vector< double > x_err
std::vector< double > x_hat
std::vector< double > Vec