13#include <unordered_map>
27#define MINI_ACC_CONCAT_INNER(x, y) x##y
28#define MINI_ACC_CONCAT(x, y) MINI_ACC_CONCAT_INNER(x, y)
29#define MINI_ACC_UNIQUE_NAME(prefix) MINI_ACC_CONCAT(prefix, __COUNTER__)
32#define MINI_ACC_BENCHMARK_REGISTER_SIZES(FUNC, SIZER) \
33 static int MINI_ACC_UNIQUE_NAME(_reg_sizes_) = ([]() { \
34 SIZER(miniacc::RegisterBenchmark(#FUNC, FUNC)); \
57 std::unordered_map<std::string, double>
metrics;
58 std::unordered_map<std::string, std::string>
tags;
65 double mean = std::numeric_limits<double>::quiet_NaN();
66 double p95 = std::numeric_limits<double>::quiet_NaN();
67 double max = std::numeric_limits<double>::quiet_NaN();
77 std::unordered_map<std::string, MetricSummary>
summary;
82 std::vector<double> vals;
83 if (str.empty())
return vals;
85 if (str.find(
':') != std::string::npos) {
86 std::vector<std::string> parts;
87 std::stringstream ss(str);
89 while (std::getline(ss, item,
':')) parts.push_back(item);
90 if (parts.size() >= 2) {
91 const double start = std::stod(parts[0]);
92 const double end = std::stod(parts[1]);
93 size_t num = parts.size() >= 3 ? std::stoul(parts[2]) : 5;
95 for (
size_t i = 0; i < num; ++i) {
96 const double t =
static_cast<double>(i) /
static_cast<double>(num - 1);
97 vals.push_back(start + (end - start) * t);
103 if (str.find(
',') != std::string::npos) {
104 std::stringstream ss(str);
106 while (std::getline(ss, item,
',')) {
107 if (!item.empty()) vals.push_back(std::stod(item));
112 vals.push_back(std::stod(str));
116inline std::vector<std::string>
parse_list(
const std::string& str) {
117 std::vector<std::string> vals;
118 std::stringstream ss(str);
120 while (std::getline(ss, item,
',')) {
121 if (!item.empty()) vals.push_back(item);
127 double eps = 1e-12) {
128 std::vector<double> out;
129 out.reserve(input.size());
130 for (
double v : input) {
132 for (
double u : out) {
133 if (std::abs(u - v) <= eps) {
138 if (!seen) out.push_back(v);
151 : args_(std::move(args)),
152 cfg_(std::move(cfg)),
156 double arg(
size_t idx)
const {
157 return idx < args_.size() ? args_[idx] : 0.0;
161 return static_cast<int>(std::lround(
arg(idx)));
164 size_t samples()
const {
return cfg_.samples; }
165 unsigned int seed()
const {
return cfg_.seed; }
169 void SetTag(
const std::string& key,
const std::string& value) {
170 case_tags_[key] = value;
173 void RecordSample(
const std::unordered_map<std::string, double>& metrics,
175 const std::string& failure_reason =
"",
177 const std::unordered_map<std::string, std::string>& tags = {}) {
184 row.
tags = case_tags_;
185 for (
const auto& kv : tags) {
186 row.
tags[kv.first] = kv.second;
188 rows_.push_back(std::move(row));
191 const std::vector<SampleRow>&
rows()
const {
return rows_; }
194 std::vector<double> args_;
196 double target_log10_cond_;
197 std::string benchmark_name_;
198 std::unordered_map<std::string, std::string> case_tags_;
199 std::vector<SampleRow> rows_;
219 if (num < 2) num = 2;
220 for (
size_t i = 0; i < num; ++i) {
221 const double t =
static_cast<double>(i) /
static_cast<double>(num - 1);
222 args_list.push_back({start + (end - start) * t});
228 std::vector<std::vector<double>> combos{{}};
229 for (
const auto& r : ranges) {
230 std::vector<std::vector<double>> next;
231 for (
const auto& c : combos) {
235 next.push_back(std::move(tmp));
238 combos = std::move(next);
240 for (
auto& c : combos)
args_list.push_back(std::move(c));
246 static std::vector<Benchmark> bench_list;
251 registry().emplace_back(name, std::move(func));
256 const std::string& metric_name) {
258 out.
count = rows.size();
260 std::vector<double> finite_values;
261 finite_values.reserve(rows.size());
263 for (
const auto& r : rows) {
264 auto it = r.metrics.find(metric_name);
265 if (it == r.metrics.end() || !std::isfinite(it->second)) {
269 finite_values.push_back(it->second);
273 if (finite_values.empty())
return out;
275 std::sort(finite_values.begin(), finite_values.end());
277 for (
double v : finite_values) sum += v;
278 out.
mean = sum /
static_cast<double>(finite_values.size());
279 out.
max = finite_values.back();
281 const size_t n = finite_values.size();
282 const size_t p95_idx = (n == 1) ? 0 :
static_cast<size_t>(std::ceil(0.95 *
static_cast<double>(n - 1)));
283 out.
p95 = finite_values[p95_idx];
289 const std::vector<double>& args,
291 double target_log10_cond,
293 State state(args, cfg, target_log10_cond, b.
name);
302 for (
const auto& row : state.
rows()) {
309 std::unordered_map<std::string, bool> seen_metrics;
310 std::vector<std::string> metric_names;
311 for (
const auto& row : state.
rows()) {
312 for (
const auto& kv : row.
metrics) {
313 if (!seen_metrics[kv.first]) {
314 metric_names.push_back(kv.first);
315 seen_metrics[kv.first] =
true;
319 for (
const auto& m : metric_names) {
331 const std::string& benchmark_filter,
332 const std::vector<std::string>& backends,
333 const std::vector<std::string>& types) {
334 if (!benchmark_filter.empty()) {
335 const auto glob_match = [](
const std::string& pattern,
const std::string& text) {
338 size_t star = std::string::npos;
340 while (t < text.size()) {
341 if (p < pattern.size() && (pattern[p] ==
'?' || pattern[p] == text[t])) {
344 }
else if (p < pattern.size() && pattern[p] ==
'*') {
347 }
else if (star != std::string::npos) {
354 while (p < pattern.size() && pattern[p] ==
'*') ++p;
355 return p == pattern.size();
358 if (!glob_match(benchmark_filter, b.
name))
return false;
361 if (!backends.empty()) {
363 for (
const auto& be : backends) {
364 if (b.
name.find(
"Backend::" + be) != std::string::npos) {
369 if (!ok)
return false;
372 if (!types.empty()) {
374 for (
const auto& t : types) {
376 b.
name.find(
"<float") != std::string::npos &&
377 b.
name.find(
"complex<float>") == std::string::npos)
379 else if (t ==
"double" &&
380 b.
name.find(
"<double") != std::string::npos &&
381 b.
name.find(
"complex<double>") == std::string::npos)
383 else if ((t ==
"cfloat" || t ==
"complex<float>") &&
384 b.
name.find(
"complex<float>") != std::string::npos)
386 else if ((t ==
"cdouble" || t ==
"complex<double>") &&
387 b.
name.find(
"complex<double>") != std::string::npos)
391 if (!ok)
return false;
398 if (!std::isfinite(v))
return "";
399 std::ostringstream oss;
400 oss << std::setprecision(6) << std::showpoint << v;
405 const std::initializer_list<const char*>& keys) {
406 for (
const char* k : keys) {
408 if (it != r.
summary.end() && std::isfinite(it->second.mean)) {
409 return it->second.mean;
412 return std::numeric_limits<double>::quiet_NaN();
430 const std::initializer_list<const char*>& keys) {
436 for (
const auto& r : results) {
456 << std::left << std::setw(static_cast<int>(name_width)) <<
"Name";
457 for (
size_t i = 0; i < max_args; ++i) {
458 std::ostringstream col;
460 std::cout << std::setw(10) << col.str();
462 std::cout << std::setw(12) <<
"log10cond"
463 << std::setw(10) <<
"Samples"
464 << std::setw(10) <<
"Fail%";
465 if (cols.
show_r) std::cout << std::setw(28) <<
"||AZ - Z\\Lambda||_F / n";
466 if (cols.
show_o) std::cout << std::setw(24) <<
"||Z^T Z - I||_F / n";
467 if (cols.
show_relerr) std::cout << std::setw(16) <<
"max_relerr";
483 const auto arg_at = [&](
size_t i) ->
double {
484 return i < r.
args.size() ? r.
args[i] : std::numeric_limits<double>::quiet_NaN();
492 const double mean_total_time_sec =
metric_mean_or_nan(r, {
"total_time_sec",
"solve_time_sec"});
493 const double mean_final_best_max =
metric_mean_or_nan(r, {
"final_best_max",
"final_best_mean"});
498 const double mean_residual_ratio =
metric_mean_or_nan(r, {
"residual_ratio_vs_baseline"});
501 std::cout << kColorName << std::left << std::setw(static_cast<int>(name_width)) << r.
name <<
kColorReset
503 for (
size_t i = 0; i < max_args; ++i) {
524 const std::vector<Result>& results) {
525 std::ofstream f(path);
527 std::cerr <<
"Failed to open CSV file: " << path <<
'\n';
532 std::vector<std::string> metric_names;
533 std::unordered_map<std::string, bool> metric_seen;
534 std::vector<std::string> tag_names;
535 std::unordered_map<std::string, bool> tag_seen;
537 for (
const auto& r : results) {
538 max_args = std::max(max_args, r.args.size());
539 for (
const auto& row : r.sample_rows) {
540 for (
const auto& kv : row.
metrics) {
541 if (!metric_seen[kv.first]) {
542 metric_names.push_back(kv.first);
543 metric_seen[kv.first] =
true;
546 for (
const auto& kv : row.
tags) {
547 if (!tag_seen[kv.first]) {
548 tag_names.push_back(kv.first);
549 tag_seen[kv.first] =
true;
556 for (
size_t i = 0; i < max_args; ++i) f <<
",arg" << i;
557 f <<
",target_log10_cond,sample,ok,info_code,failure_reason";
558 for (
const auto& t : tag_names) f <<
",tag_" << t;
559 for (
const auto& m : metric_names) f <<
',' << m;
562 for (
const auto& r : results) {
563 for (
const auto& row : r.sample_rows) {
564 f <<
'"' << r.name <<
'"';
565 for (
size_t i = 0; i < max_args; ++i) {
566 if (i < r.args.size()) f <<
',' << r.args[i];
569 f <<
',' << r.target_log10_cond
571 <<
',' << (row.
ok ? 1 : 0)
575 for (
const auto& t : tag_names) {
576 auto it = row.
tags.find(t);
577 if (it != row.
tags.end()) f <<
',' <<
'"' << it->second <<
'"';
581 for (
const auto& m : metric_names) {
583 if (it != row.
metrics.end()) f <<
',' << it->second;
592 const std::string& csv_path,
593 const std::string& benchmark_filter,
594 const std::vector<std::string>& backends,
595 const std::vector<std::string>& types,
596 const std::vector<std::vector<double>>& args_override) {
597 std::vector<Result> results;
600 if (cond_targets.empty()) {
601 cond_targets.push_back(std::numeric_limits<double>::quiet_NaN());
604 const bool keep_samples = !csv_path.empty();
608 if (!
match_filter(b, benchmark_filter, backends, types))
continue;
610 const auto* active_args = &b.args_list;
611 if (!args_override.empty()) active_args = &args_override;
613 const bool has_args = !active_args->empty();
614 const std::vector<std::vector<double>> empty_case{{}};
615 const auto& to_run = has_args ? *active_args : empty_case;
617 for (
const auto& args : to_run) {
618 for (
double log10_cond : cond_targets) {
620 max_args = std::max(max_args, r.
args.size());
621 results.push_back(std::move(r));
627 size_t name_width = 50;
628 for (
const auto& r : results) {
629 name_width = std::max(name_width, r.name.size() + 2);
633 for (
const auto& r : results) {
637 if (!csv_path.empty()) {
660 std::vector<std::vector<double>> arg_values;
662 for (
int i = 1; i < argc; ++i) {
663 std::string s(argv[i]);
664 if (s.rfind(
"--samples=", 0) == 0) {
666 }
else if (s.rfind(
"--seed=", 0) == 0) {
667 opt.
cfg.
seed =
static_cast<unsigned int>(std::stoul(s.substr(7)));
668 }
else if (s.rfind(
"--log10-cond=", 0) == 0) {
670 }
else if (s.rfind(
"--csv=", 0) == 0) {
672 }
else if (s.rfind(
"--benchmark_filter=", 0) == 0) {
674 }
else if (s.rfind(
"--backend=", 0) == 0) {
676 }
else if (s.rfind(
"--type=", 0) == 0) {
678 }
else if (s ==
"--help" || s ==
"-h") {
679 std::cout <<
"Usage: miniacc [options] [ARGS...]\n";
680 std::cout <<
"Options:\n";
681 std::cout <<
" --samples=N number of samples available to each case (default 1024)\n";
682 std::cout <<
" --seed=N base RNG seed (default 1234)\n";
683 std::cout <<
" --log10-cond=SPEC scalar, v1,v2,... or start:end:num\n";
684 std::cout <<
" --csv=FILE emit per-sample rows to CSV (summary always to terminal)\n";
685 std::cout <<
" --benchmark_filter=GLOB benchmark-name filter with * and ? wildcards\n";
686 std::cout <<
" --backend=LIST comma separated backends to run\n";
687 std::cout <<
" --type=LIST comma separated floating point types\n";
688 std::cout <<
" ARGS can be scalars, comma lists or start:end:num ranges\n";
697 if (!arg_values.empty()) {
698 std::vector<std::vector<double>> combos{{}};
699 for (
const auto& vals : arg_values) {
700 std::vector<std::vector<double>> next;
701 for (
const auto& c : combos) {
702 for (
double v : vals) {
705 next.push_back(std::move(tmp));
708 combos = std::move(next);
731#define MINI_ACC_MAIN() \
732 int main(int argc, char** argv) { \
733 return miniacc::MiniAccMain(argc, argv); \
Per-case context handed to a benchmark: its arguments, the run settings, and the sample sink.
double arg(size_t idx) const
double target_log10_cond() const
const std::vector< SampleRow > & rows() const
void SetTag(const std::string &key, const std::string &value)
State(std::vector< double > args, Config cfg, double target_log10_cond, std::string benchmark_name)
unsigned int seed() const
void RecordSample(const std::unordered_map< std::string, double > &metrics, bool ok=true, const std::string &failure_reason="", int info_code=0, const std::unordered_map< std::string, std::string > &tags={})
int arg_int(size_t idx) const
const std::string & benchmark_name() const
int MiniAccMain(int argc, char **argv)
Parses the command line and runs every registered case that matches; the body of MINI_ACC_MAIN().
double metric_mean_or_nan(const Result &r, const std::initializer_list< const char * > &keys)
bool has_any_metric(const Result &r, const std::initializer_list< const char * > &keys)
constexpr const char * kColorName
MetricSummary summarize_metric(const std::vector< SampleRow > &rows, const std::string &metric_name)
void write_samples_csv(const std::string &path, const std::vector< Result > &results)
SummaryMetricColumns detect_summary_columns(const std::vector< Result > &results)
std::function< void(State &)> BenchFunc
std::vector< double > dedup_nearly_equal(const std::vector< double > &input, double eps=1e-12)
Result run_benchmark(const Benchmark &b, const std::vector< double > &args, const Config &cfg, double target_log10_cond, bool keep_samples)
constexpr const char * kColorReset
CliOptions ParseCommandLine(int argc, char **argv)
std::vector< std::string > parse_list(const std::string &str)
constexpr const char * kColorHeader
void print_summary_header(size_t name_width, size_t max_args, const SummaryMetricColumns &cols)
void print_summary_row(const Result &r, size_t name_width, size_t max_args, const SummaryMetricColumns &cols)
Benchmark * RegisterBenchmark(const std::string &name, BenchFunc func)
std::string format_value(double v)
bool match_filter(const Benchmark &b, const std::string &benchmark_filter, const std::vector< std::string > &backends, const std::vector< std::string > &types)
std::vector< double > parse_range_or_list_double(const std::string &str)
int RunRegisteredBenchmarks(const Config &cfg, const std::string &csv_path, const std::string &benchmark_filter, const std::vector< std::string > &backends, const std::vector< std::string > &types, const std::vector< std::vector< double > > &args_override)
std::vector< Benchmark > & registry()
A registered case: name, function, and its argument lists (Args, ArgRange, ArgsProduct).
Benchmark * ArgRange(double start, double end, size_t num=5)
std::vector< std::vector< double > > args_list
Benchmark(const std::string &n, BenchFunc f)
Benchmark * ArgsProduct(const std::vector< std::vector< double > > &ranges)
Benchmark * Args(const std::vector< double > &a)
std::vector< std::string > backends
std::vector< std::string > types
bool help_requested
--help was seen: usage has been printed and there is nothing to run.
std::vector< std::vector< double > > args_list
std::string benchmark_filter
Run-wide settings parsed from the command line.
std::vector< double > log10_cond_values
std::unordered_map< std::string, MetricSummary > summary
std::vector< SampleRow > sample_rows
std::vector< double > args
std::unordered_map< std::string, double > metrics
std::string failure_reason
std::unordered_map< std::string, std::string > tags
bool show_converged_fraction
bool show_iterations_done