Hyperiso 1.0.3
Modular flavour-physics calculations, Wilson coefficients and statistical inference
Loading...
Searching...
No Matches
main_bkstarll_thread_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 = "lha/si_input.flha";
24 std::string output = "benchmark_bkstarll_threads.csv";
25 int repeats = 30;
26 int warmups = 3;
27 unsigned int max_threads = 0; // 0 -> hardware_concurrency
28 QCDOrder order = QCDOrder::NLO;
29 std::vector<std::pair<double, double>> bins {{17.0, 19.0}};
30 bool include_unbinned = false;
31};
32
33QCDOrder parse_order(std::string value) {
34 std::transform(value.begin(), value.end(), value.begin(), [](unsigned char c) { return static_cast<char>(std::toupper(c)); });
35 if (value == "LO") return QCDOrder::LO;
36 if (value == "NLO") return QCDOrder::NLO;
37 if (value == "NNLO") return QCDOrder::NNLO;
38 if (value == "NONE") return QCDOrder::NONE;
39 LOG_ERROR("ArgumentError", "Unknown QCD order:", value, "expected LO, NLO, NNLO or NONE.");
40 return QCDOrder::NONE;
41}
42
43void print_usage(const char* exe) {
44 std::cerr
45 << "Usage: " << exe << " [options]\n"
46 << "\nOptions:\n"
47 << " --input PATH Input FLHA/LHA file (default: default/lha/testInput.flha)\n"
48 << " --out PATH Output CSV file (default: benchmark_bkstarll_threads.csv)\n"
49 << " --repeats N Timed repetitions per thread count (default: 30)\n"
50 << " --warmups N Untimed warm-up calls per thread count (default: 3)\n"
51 << " --max-threads N Maximum tested thread count (default: hardware_concurrency)\n"
52 << " --order ORDER QCD order: LO, NLO, NNLO or NONE (default: NLO)\n"
53 << " --bin QMIN:QMAX Add a q^2 bin for binned observables; can be repeated (default: 1:6)\n"
54 << " --include-unbinned Also benchmark non-binned BKstarll observables such as q0(A_FB)\n"
55 << " --help Show this message\n";
56}
57
58std::pair<double, double> parse_bin(const std::string& text) {
59 const auto pos = text.find(':');
60 if (pos == std::string::npos) {
61 LOG_ERROR("ArgumentError", "Invalid --bin value:", text, "expected QMIN:QMAX.");
62 }
63 double qmin = std::stod(text.substr(0, pos));
64 double qmax = std::stod(text.substr(pos + 1));
65 if (qmin >= qmax) {
66 LOG_ERROR("ArgumentError", "Invalid --bin value:", text, "expected QMIN < QMAX.");
67 }
68 return {qmin, qmax};
69}
70
71std::string format_bins(const std::vector<std::pair<double, double>>& bins) {
72 std::string out;
73 for (size_t i = 0; i < bins.size(); ++i) {
74 if (i != 0) out += ";";
75 out += std::to_string(bins[i].first) + ":" + std::to_string(bins[i].second);
76 }
77 return out;
78}
79
80Options parse_args(int argc, char** argv) {
81 Options opt;
82 for (int i = 1; i < argc; ++i) {
83 const std::string arg = argv[i];
84 auto require_value = [&](const std::string& name) -> std::string {
85 if (i + 1 >= argc) {
86 LOG_ERROR("ArgumentError", "Missing value after", name);
87 }
88 return argv[++i];
89 };
90
91 if (arg == "--input") opt.input = require_value(arg);
92 else if (arg == "--out") opt.output = require_value(arg);
93 else if (arg == "--repeats") opt.repeats = std::stoi(require_value(arg));
94 else if (arg == "--warmups") opt.warmups = std::stoi(require_value(arg));
95 else if (arg == "--max-threads") opt.max_threads = static_cast<unsigned int>(std::stoul(require_value(arg)));
96 else if (arg == "--order") opt.order = parse_order(require_value(arg));
97 else if (arg == "--include-unbinned") opt.include_unbinned = true;
98 else if (arg == "--bin") {
99 if (opt.bins.size() == 1 && opt.bins.front() == std::pair<double, double>{1.0, 6.0}) {
100 opt.bins.clear();
101 }
102 opt.bins.push_back(parse_bin(require_value(arg)));
103 }
104 else if (arg == "--help" || arg == "-h") {
105 print_usage(argv[0]);
106 std::exit(0);
107 } else {
108 LOG_ERROR("ArgumentError", "Unknown option:", arg);
109 }
110 }
111
112 if (opt.repeats <= 0) LOG_ERROR("ArgumentError", "--repeats must be positive.");
113 if (opt.warmups < 0) LOG_ERROR("ArgumentError", "--warmups must be non-negative.");
114 if (opt.bins.empty()) LOG_ERROR("ArgumentError", "At least one --bin is required.");
115
116 if (opt.max_threads == 0) {
117 opt.max_threads = std::thread::hardware_concurrency();
118 if (opt.max_threads == 0) opt.max_threads = 1;
119 }
120 return opt;
121}
122
123double checksum(const std::map<ObservableId, std::vector<ObservableValue>>& results) {
124 double sum = 0.0;
125 for (const auto& [_, values] : results) {
126 for (const auto& value : values) {
127 sum += value.value;
128 }
129 }
130 return sum;
131}
132
133void add_decay_observables_with_bins(ObservableInterface& interface,
134 Decays decay,
135 QCDOrder order,
136 const std::vector<std::pair<double, double>>& bins,
137 bool include_unbinned)
138{
139 const auto observables = DecayMapper::get_observables(decay);
140 if (observables.empty()) {
141 LOG_ERROR("ValueError", "Decay", DecayMapper::str(decay), "has no observable to benchmark.");
142 }
143
144 bool added_any = false;
145 for (const auto& obs : observables) {
146 if (interface.is_observable_binned(obs)) {
147 for (const auto& bin : bins) {
148 interface.add_observable(BinnedObservableId(obs, bin), order, false);
149 }
150 added_any = true;
151 } else if (include_unbinned) {
152 interface.add_observable(obs, order, false);
153 added_any = true;
154 }
155 }
156
157 if (!added_any) {
158 LOG_ERROR("ValueError", "Decay", DecayMapper::str(decay),
159 "has no selected observable after applying benchmark filters.");
160 }
161}
162
163struct Stats {
164 double mean_ms = 0.0;
165 double stddev_ms = 0.0;
166 double min_ms = 0.0;
167 double max_ms = 0.0;
168};
169
170Stats compute_stats(const std::vector<double>& samples) {
171 Stats stats;
172 stats.mean_ms = std::accumulate(samples.begin(), samples.end(), 0.0) / static_cast<double>(samples.size());
173 stats.min_ms = *std::min_element(samples.begin(), samples.end());
174 stats.max_ms = *std::max_element(samples.begin(), samples.end());
175
176 double variance = 0.0;
177 for (double x : samples) {
178 variance += (x - stats.mean_ms) * (x - stats.mean_ms);
179 }
180 variance /= static_cast<double>(samples.size());
181 stats.stddev_ms = std::sqrt(variance);
182 return stats;
183}
184
185} // namespace
186
187int main(int argc, char** argv) {
189 const Options opt = parse_args(argc, argv);
190
191 HyperisoMaster hyperiso;
192 HyperisoConfig config;
193 config.model = Model::SM;
194 hyperiso.init(opt.input, config);
195
196 std::ofstream csv(opt.output);
197 if (!csv) {
198 LOG_ERROR("IOError", "Cannot open output CSV:", opt.output);
199 }
200
201 csv << "threads,repeats,warmups,order,bins,include_unbinned,n_observables,mean_ms,stddev_ms,min_ms,max_ms,checksum\n";
202 ObservableInterface interface;
203 add_decay_observables_with_bins(interface, Decays::B__K_l_l, opt.order, opt.bins, opt.include_unbinned);
204
205 for (unsigned int threads = 1; threads <= opt.max_threads; ++threads) {
206 interface.set_bkll_threads(threads);
207
208 for (int i = 0; i < opt.warmups; ++i) {
209 std::cout << "warmup : " << i << std::endl;
210 volatile double sink = checksum(interface.compute_all());
211 (void)sink;
212 }
213
214 std::vector<double> samples_ms;
215 samples_ms.reserve(static_cast<size_t>(opt.repeats));
216 double last_checksum = 0.0;
217 size_t n_observables = 0;
218
219 for (int i = 0; i < opt.repeats; ++i) {
220 std::cout << "repeat : " << i << std::endl;
221 const auto start = std::chrono::steady_clock::now();
222 auto results = interface.compute_all();
223 const auto stop = std::chrono::steady_clock::now();
224
225 n_observables = results.size();
226 last_checksum = checksum(results);
227 samples_ms.push_back(std::chrono::duration<double, std::milli>(stop - start).count());
228 }
229
230 const Stats stats = compute_stats(samples_ms);
231 csv << threads << ','
232 << opt.repeats << ','
233 << opt.warmups << ','
234 << OrderMapper::str(opt.order) << ','
235 << format_bins(opt.bins) << ','
236 << (opt.include_unbinned ? 1 : 0) << ','
237 << n_observables << ','
238 << stats.mean_ms << ','
239 << stats.stddev_ms << ','
240 << stats.min_ms << ','
241 << stats.max_ms << ','
242 << last_checksum << '\n';
243 csv.flush();
244
245 std::cout << "threads=" << threads
246 << " mean_ms=" << stats.mean_ms
247 << " stddev_ms=" << stats.stddev_ms
248 << " n_observables=" << n_observables << '\n';
249 }
250
251 std::cout << "Wrote " << opt.output << '\n';
252 return 0;
253}
QCDOrder
Decays
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
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).
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