Hyperiso 1.0.3
Modular flavour-physics calculations, Wilson coefficients and statistical inference
Loading...
Searching...
No Matches
main_decay_benchmark.cpp
Go to the documentation of this file.
1#include <algorithm>
2#include <chrono>
3#include <cctype>
4#include <cstdlib>
5#include <cmath>
6#include <cstddef>
7#include <fstream>
8#include <iostream>
9#include <map>
10#include <numeric>
11#include <string>
12#include <thread>
13#include <utility>
14#include <vector>
15
16#include "HyperisoMaster.h"
17#include "Logger.h"
18#include "ObservableInterface.h"
19
20namespace {
21
22struct Options {
23 std::string input = "default/lha/testInput.flha";
24 std::string output = "benchmark_decays.csv";
25 int repeats = 30;
26 int warmups = 3;
27 unsigned int threads = 0; // 0 -> hardware_concurrency for decays that support it
28 QCDOrder order = QCDOrder::NLO;
29 std::vector<std::pair<double, double>> bins {{15.0, 17.0}};
30 bool include_unbinned = false;
31};
32
33struct DecayCase {
34 Decays decay;
35 bool supports_threads = false;
36 bool requires_bins = false;
37};
38
39QCDOrder parse_order(std::string value) {
40 std::transform(value.begin(), value.end(), value.begin(), [](unsigned char c) { return static_cast<char>(std::toupper(c)); });
41 if (value == "LO") return QCDOrder::LO;
42 if (value == "NLO") return QCDOrder::NLO;
43 if (value == "NNLO") return QCDOrder::NNLO;
44 if (value == "NONE") return QCDOrder::NONE;
45 LOG_ERROR("ArgumentError", "Unknown QCD order:", value, "expected LO, NLO, NNLO or NONE.");
46 return QCDOrder::NONE;
47}
48
49void print_usage(const char* exe) {
50 std::cerr
51 << "Usage: " << exe << " [options]\n"
52 << "\nOptions:\n"
53 << " --input PATH Input FLHA/LHA file (default: default/lha/testInput.flha)\n"
54 << " --out PATH Output CSV file (default: benchmark_decays.csv)\n"
55 << " --repeats N Timed repetitions per decay (default: 30)\n"
56 << " --warmups N Untimed warm-up calls per decay (default: 3)\n"
57 << " --threads N Threads for decays exposing set_*_threads; 0 uses hardware_concurrency (default: 0)\n"
58 << " --order ORDER QCD order: LO, NLO, NNLO or NONE (default: NLO)\n"
59 << " --bin QMIN:QMAX Add a q^2 bin for binned observables; can be repeated (default: 1:6)\n"
60 << " --include-unbinned Also benchmark non-binned observables inside mixed decays, such as BKstarll q0(A_FB)\n"
61 << " --help Show this message\n";
62}
63
64std::pair<double, double> parse_bin(const std::string& text) {
65 const auto pos = text.find(':');
66 if (pos == std::string::npos) {
67 LOG_ERROR("ArgumentError", "Invalid --bin value:", text, "expected QMIN:QMAX.");
68 }
69 double qmin = std::stod(text.substr(0, pos));
70 double qmax = std::stod(text.substr(pos + 1));
71 if (qmin >= qmax) {
72 LOG_ERROR("ArgumentError", "Invalid --bin value:", text, "expected QMIN < QMAX.");
73 }
74 return {qmin, qmax};
75}
76
77std::string format_bins(const std::vector<std::pair<double, double>>& bins) {
78 std::string out;
79 for (size_t i = 0; i < bins.size(); ++i) {
80 if (i != 0) out += ";";
81 out += std::to_string(bins[i].first) + ":" + std::to_string(bins[i].second);
82 }
83 return out;
84}
85
86Options parse_args(int argc, char** argv) {
87 Options opt;
88 for (int i = 1; i < argc; ++i) {
89 const std::string arg = argv[i];
90 auto require_value = [&](const std::string& name) -> std::string {
91 if (i + 1 >= argc) {
92 LOG_ERROR("ArgumentError", "Missing value after", name);
93 }
94 return argv[++i];
95 };
96
97 if (arg == "--input") opt.input = require_value(arg);
98 else if (arg == "--out") opt.output = require_value(arg);
99 else if (arg == "--repeats") opt.repeats = std::stoi(require_value(arg));
100 else if (arg == "--warmups") opt.warmups = std::stoi(require_value(arg));
101 else if (arg == "--threads") opt.threads = static_cast<unsigned int>(std::stoul(require_value(arg)));
102 else if (arg == "--order") opt.order = parse_order(require_value(arg));
103 else if (arg == "--include-unbinned") opt.include_unbinned = true;
104 else if (arg == "--bin") {
105 if (opt.bins.size() == 1 && opt.bins.front() == std::pair<double, double>{15.0, 17.0}) {
106 opt.bins.clear();
107 }
108 opt.bins.push_back(parse_bin(require_value(arg)));
109 }
110 else if (arg == "--help" || arg == "-h") {
111 print_usage(argv[0]);
112 std::exit(0);
113 } else {
114 LOG_ERROR("ArgumentError", "Unknown option:", arg);
115 }
116 }
117
118 if (opt.repeats <= 0) LOG_ERROR("ArgumentError", "--repeats must be positive.");
119 if (opt.warmups < 0) LOG_ERROR("ArgumentError", "--warmups must be non-negative.");
120 if (opt.bins.empty()) LOG_ERROR("ArgumentError", "At least one --bin is required.");
121
122 if (opt.threads == 0) {
123 opt.threads = std::thread::hardware_concurrency();
124 if (opt.threads == 0) opt.threads = 1;
125 }
126 return opt;
127}
128
129double checksum(const std::map<ObservableId, std::vector<ObservableValue>>& results) {
130 double sum = 0.0;
131 for (const auto& [_, values] : results) {
132 for (const auto& value : values) {
133 sum += value.value;
134 }
135 }
136 return sum;
137}
138
139struct Stats {
140 double mean_ms = 0.0;
141 double stddev_ms = 0.0;
142 double min_ms = 0.0;
143 double max_ms = 0.0;
144};
145
146Stats compute_stats(const std::vector<double>& samples) {
147 Stats stats;
148 stats.mean_ms = std::accumulate(samples.begin(), samples.end(), 0.0) / static_cast<double>(samples.size());
149 stats.min_ms = *std::min_element(samples.begin(), samples.end());
150 stats.max_ms = *std::max_element(samples.begin(), samples.end());
151
152 double variance = 0.0;
153 for (double x : samples) {
154 variance += (x - stats.mean_ms) * (x - stats.mean_ms);
155 }
156 variance /= static_cast<double>(samples.size());
157 stats.stddev_ms = std::sqrt(variance);
158 return stats;
159}
160
161std::vector<DecayCase> default_decay_cases() {
162 return {
163 {Decays::B__D_l_nu, false, false},
164 {Decays::B__Dstar_l_nu, false, false},
165 {Decays::B__Kstar_gamma, false, false},
166 {Decays::B__l_l, false, false},
167 {Decays::B__l_nu, false, false},
168 {Decays::B__Xs_gamma, false, false},
169 {Decays::B__Xs_l_l, false, true},
170 // {Decays::M0_Mix, false, false},
171 {Decays::B__Kstar_l_l, true, true},
172 {Decays::B__K_l_l, true, true},
173 {Decays::Bs__phi_l_l, true, true},
174 {Decays::Lambda_b__Lambda_l_l, false, true},
175 {Decays::K__l_l, false, false},
176 {Decays::K__pi_nu_nu, false, false},
177 {Decays::K__l_nu, false, false},
178 {Decays::D__l_nu, false, false},
179 {Decays::Ds__l_nu, false, false}
180 };
181}
182
183void add_decay_observables_with_bins(ObservableInterface& interface,
184 Decays decay,
185 QCDOrder order,
186 const std::vector<std::pair<double, double>>& bins,
187 bool include_unbinned)
188{
189 const auto observables = DecayMapper::get_observables(decay);
190 if (observables.empty()) {
191 LOG_ERROR("ValueError", "Decay", DecayMapper::str(decay), "has no observable to benchmark.");
192 }
193
194 bool added_any = false;
195 for (const auto& obs : observables) {
196 if (interface.is_observable_binned(obs)) {
197 for (const auto& bin : bins) {
198 interface.add_observable(BinnedObservableId(obs, bin), order, false);
199 }
200 added_any = true;
201 } else if (include_unbinned) {
202 interface.add_observable(obs, order, false);
203 added_any = true;
204 }
205 }
206
207 if (!added_any) {
208 LOG_ERROR("ValueError", "Decay", DecayMapper::str(decay),
209 "has no selected observable after applying benchmark filters.");
210 }
211}
212
213void apply_thread_setting(ObservableInterface& interface, Decays decay, unsigned int threads) {
214 switch (decay) {
216 interface.set_bkstarll_threads(threads);
217 break;
218 case Decays::B__K_l_l:
219 interface.set_bkll_threads(threads);
220 break;
222 interface.set_bsphi_threads(threads);
223 break;
224 default:
225 break;
226 }
227}
228
229} // namespace
230
231int main(int argc, char** argv) {
233 const Options opt = parse_args(argc, argv);
234
235 HyperisoMaster hyperiso;
236 HyperisoConfig config;
237 config.model = Model::SM;
238 hyperiso.init(opt.input, config);
239
240 std::ofstream csv(opt.output);
241 if (!csv) {
242 LOG_ERROR("IOError", "Cannot open output CSV:", opt.output);
243 }
244
245 csv << "decay,order,threads,bins,include_unbinned,repeats,warmups,n_observables,mean_ms,stddev_ms,min_ms,max_ms,checksum\n";
246
247 for (const auto& decay_case : default_decay_cases()) {
248 ObservableInterface interface;
249 if (decay_case.requires_bins) {
250 add_decay_observables_with_bins(interface, decay_case.decay, opt.order, opt.bins, opt.include_unbinned);
251 } else {
252 interface.add_observables(decay_case.decay, opt.order, false);
253 }
254 if (decay_case.supports_threads) {
255 apply_thread_setting(interface, decay_case.decay, opt.threads);
256 }
257
258 for (int i = 0; i < opt.warmups; ++i) {
259 volatile double sink = checksum(interface.compute_all());
260 (void)sink;
261 }
262
263 std::vector<double> samples_ms;
264 samples_ms.reserve(static_cast<size_t>(opt.repeats));
265 double last_checksum = 0.0;
266 size_t n_observables = 0;
267
268 for (int i = 0; i < opt.repeats; ++i) {
269 const auto start = std::chrono::steady_clock::now();
270 auto results = interface.compute_all();
271 const auto stop = std::chrono::steady_clock::now();
272
273 n_observables = results.size();
274 last_checksum = checksum(results);
275 samples_ms.push_back(std::chrono::duration<double, std::milli>(stop - start).count());
276 }
277
278 const Stats stats = compute_stats(samples_ms);
279 const std::string decay_name = DecayMapper::str(decay_case.decay);
280 csv << decay_name << ','
281 << OrderMapper::str(opt.order) << ','
282 << (decay_case.supports_threads ? opt.threads : 1) << ','
283 << (decay_case.requires_bins ? format_bins(opt.bins) : "") << ','
284 << (opt.include_unbinned ? 1 : 0) << ','
285 << opt.repeats << ','
286 << opt.warmups << ','
287 << n_observables << ','
288 << stats.mean_ms << ','
289 << stats.stddev_ms << ','
290 << stats.min_ms << ','
291 << stats.max_ms << ','
292 << last_checksum << '\n';
293 csv.flush();
294
295 std::cout << "decay=" << decay_name
296 << " mean_ms=" << stats.mean_ms
297 << " stddev_ms=" << stats.stddev_ms
298 << " n_observables=" << n_observables << '\n';
299 }
300
301 std::cout << "Wrote " << opt.output << '\n';
302 return 0;
303}
QCDOrder
Decays
@ B__Kstar_l_l
@ K__pi_nu_nu
@ B__Dstar_l_nu
@ Bs__phi_l_l
@ B__Kstar_gamma
@ B__Xs_gamma
@ Lambda_b__Lambda_l_l
High-level helpers for initializing and monitoring the Hyperiso framework.
#define LOG_ERROR(type,...)
Macro for logging error messages and terminating the application.
Definition Logger.h:41
High-level, user-facing entry point to compute flavor observables.
static std::vector< Observables > get_observables(Decays d)
Legacy static lookup of builtin observables for a builtin decay.
static std::string str(const IdOf< QCDOrderTag > &id)
Returns the string representation associated with an identifier.
static std::string str(const IdOf< DecayTag > &id)
Returns the string representation of an identifier.
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.
void setLevel(LogLevel level)
Sets the logging level.
Definition Logger.cpp:38
static Logger * getInstance()
Retrieves the singleton instance of the Logger.
Definition Logger.cpp:5
void add_observables(std::map< Observables, QCDOrder > obss, bool add_dependencies=false)
Add multiple observables at once (enum map).
void set_bkstarll_threads(size_t n_threads)
Set the thread option for the bkstarll decay.
bool is_observable_binned(Observables obs) const
Return whether a specific observable requires q² bins.
void set_bkll_threads(size_t n_threads)
Set the thread option for the bkll decay.
std::map< ObservableId, std::vector< ObservableValue > > compute_all()
Compute all currently registered observables.
ObservableInterface & add_observable(Observables obs, QCDOrder order, bool add_dependencies=false)
Add an observable to the manager (enum API).
void set_bsphi_threads(size_t n_threads)
Set the thread option for the bsphi decay.
Dict[str, float] compute_stats(np.ndarray Y, np.ndarray R)
dict results
Definition test_rng.py:49
Identifies an observable together with a numerical bin.
Configuration object controlling model, input flags and optional MARTY resources.
Definition Config.h:24
Model model
Current model.
Definition Config.h:33