// -*- coding: utf-8 -*- // Copyright (C) by the Spot authors, see the AUTHORS file for details. // // This file is part of Spot, a model checking library. // // Spot is free software; you can redistribute it and/or modify it // under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 3 of the License, or // (at your option) any later version. // // Spot is distributed in the hope that it will be useful, but WITHOUT // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY // or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public // License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include struct ec_algo { std::string name; spot::emptiness_check_instantiator_ptr inst; }; const char* default_algos[] = { "Cou99(!poprem)", "Cou99(!poprem shy !group)", "Cou99(!poprem shy group)", "Cou99(poprem)", "Cou99(poprem shy !group)", "Cou99(poprem shy group)", "Cou99new", "Cou99abs", "CVWY90", "CVWY90(bsh=4K)", "CVWY90(ar:from_stack)", "GV04", "SE05", "SE05(bsh=4K)", "SE05(ar:from_stack)", "Tau03", "Tau03_opt", "Tau03_opt(condstack)", "Tau03_opt(condstack ordering)", "Tau03_opt(condstack ordering !weights)", nullptr }; std::vector ec_algos; static spot::emptiness_check_ptr cons_emptiness_check(int num, spot::const_twa_graph_ptr a, const spot::const_twa_graph_ptr& degen, unsigned int n_acc) { auto inst = ec_algos[num].inst; if (n_acc < inst->min_sets() || n_acc > inst->max_sets()) a = degen; if (a) return inst->instantiate(a); return nullptr; } static void syntax(char* prog) { std::cerr << "Usage: " << prog << (" [OPTIONS...] PROPS...\n\n" "General Options:\n" " -0 suppress default output, just generate the graph" " in memory\n" " -1 produce minimal output (for our paper)\n" " -g output graph in dot format\n" " -s N seed for the random number generator\n" " -z display statistics about emptiness-check algorithms\n" " -Z like -z, but print extra statistics after the run" " of each algorithm\n\n" "Graph Generation Options:\n" " -a N F number of acceptance conditions and probability that" " one is true\n" " [0 0.0]\n" " -d F density of the graph [0.2]\n" " -n N number of nodes of the graph [20]\n" " -t F probability of the atomic propositions to be true" " [0.5]\n" " -det generate a deterministic and complete graph [false]\n\n" "Emptiness-Check Options:\n" " -A FILE use all algorithms listed in FILE\n" " -D degeneralize TGBA for emptiness-check algorithms that" " would\n" " otherwise be skipped (implies -e)\n" " -e N compare result of all " "emptiness checks on N randomly generated graphs\n" " -m try to reduce runs, in a second pass (implies -r)\n" " -R N repeat each emptiness-check and accepting run " "computation N times\n" " -r compute and replay accepting runs (implies -e)\n" " ar:MODE select the mode MODE for accepting runs computation " "(implies -r)\n\n" "Where:\n" " F are floats between 0.0 and 1.0 inclusive\n" " E are floating values\n" " S are `KEY=E, KEY=E, ...' strings\n" " N are positive integers\n" " PROPS are the atomic properties to use on transitions\n" "Use -dp to see the list of KEYs.\n\n" "When -i is used, a random graph a synchronized with" " each formula.\nIf -e N is additionally used" " N random graphs are generated for each formula.\n"); exit(2); } static int to_int(const char* s) { char* endptr; int res = strtol(s, &endptr, 10); if (*endptr) { std::cerr << "Failed to parse `" << s << "' as an integer.\n"; exit(1); } return res; } static int to_int_pos(const char* s, const char* arg) { int res = to_int(s); if (res <= 0) { std::cerr << "argument of " << arg << " (" << res << ") must be positive\n"; exit(1); } return res; } static int to_int_nonneg(const char* s, const char* arg) { int res = to_int(s); if (res < 0) { std::cerr << "argument of " << arg << " (" << res << ") must be nonnegative\n"; exit(1); } return res; } static float to_float(const char* s) { char* endptr; // Do not use strtof(), it does not exist on Solaris 9. float res = strtod(s, &endptr); if (*endptr) { std::cerr << "Failed to parse `" << s << "' as a float.\n"; exit(1); } return res; } static float to_float_nonneg(const char* s, const char* arg) { float res = to_float(s); if (res < 0) { std::cerr << "argument of " << arg << " (" << res << ") must be nonnegative\n"; exit(1); } return res; } // Convertors using for statistics: template T id(const char*, unsigned x) { return static_cast(x); } spot::twa_statistics prod_stats; static float prod_conv(const char* name, unsigned x) { float y = static_cast(x); if (!strcmp(name, "transitions")) return y / prod_stats.edges * 100.0; return y / prod_stats.states * 100.0; } template > struct stat_collector { struct one_stat { T min; T max; T tot; unsigned n; one_stat() : n(0) { } void count(T val) { if (n++) { min = std::min(min, val); max = std::max(max, val); tot += val; } else { max = min = tot = val; } } }; typedef std::map alg_1stat_map; typedef std::map stats_alg_map; stats_alg_map stats; bool empty() { return stats.empty(); } void count(const std::string& algorithm, const spot::unsigned_statistics* s) { if (!s) return; spot::unsigned_statistics::stats_map::const_iterator i; for (i = s->stats.begin(); i != s->stats.end(); ++i) { auto u = (s->*i->second)(); auto t = convertor(i->first, u); stats[i->first][algorithm].count(t); } } std::ostream& display(std::ostream& os, const alg_1stat_map& m, const std::string& title, bool total = true) const { std::ios::fmtflags old = os.flags(); os << std::setw(25) << "" << " | " << std::setw(30) << std::left << title << std::right << '|' << std::endl << std::setw(25) << "algorithm" << " | min < mean < max | total | n" << std::endl << std::setw(64) << std::setfill('-') << "" << std::setfill(' ') << std::endl; os << std::right << std::fixed << std::setprecision(1); for (typename alg_1stat_map::const_iterator i = m.begin(); i != m.end(); ++i) { os << std::setw(25) << i->first << " |" << std::setw(6) << i->second.min << ' ' << std::setw(8) << static_cast(i->second.tot) / i->second.n << ' ' << std::setw(6) << i->second.max << " |"; if (total) os << std::setw(6) << i->second.tot; else os << " "; os << " |" << std::setw(4) << i->second.n << std::endl; } os << std::setw(64) << std::setfill('-') << "" << std::setfill(' ') << std::endl; os << std::setiosflags(old); return os; } std::ostream& display(std::ostream& os, bool total = true) const { typename stats_alg_map::const_iterator i; for (i = stats.begin(); i != stats.end(); ++i) display(os, i->second, i->first, total); return os; } }; struct ar_stat { int min_prefix; int max_prefix; int tot_prefix; int min_cycle; int max_cycle; int tot_cycle; int min_run; int max_run; int n; ar_stat() : n(0) { } void count(const spot::const_twa_run_ptr& run) { int p = run->prefix.size(); int c = run->cycle.size(); if (n++) { min_prefix = std::min(min_prefix, p); max_prefix = std::max(max_prefix, p); tot_prefix += p; min_cycle = std::min(min_cycle, c); max_cycle = std::max(max_cycle, c); tot_cycle += c; min_run = std::min(min_run, c + p); max_run = std::max(max_run, c + p); } else { min_prefix = max_prefix = tot_prefix = p; min_cycle = max_cycle = tot_cycle = c; min_run = max_run = c + p; } } }; stat_collector sc_ec; stat_collector sc_arc; typedef stat_collector ec_ratio_stat_type; ec_ratio_stat_type glob_ec_ratio_stats; typedef std::map ec_ratio_stats_type; ec_ratio_stats_type ec_ratio_stats; ec_ratio_stat_type arc_ratio_stats; typedef std::map ar_stats_type; ar_stats_type ar_stats; // Statistics about accepting runs. ar_stats_type mar_stats; // ... about minimized accepting runs. static void print_ar_stats(ar_stats_type& ar_stats, const std::string& s) { std::ios::fmtflags old = std::cout.flags(); std::cout << std::endl << s << std::endl; std::cout << std::right << std::fixed << std::setprecision(1); std::cout << std::setw(25) << "" << " | prefix | cycle |" << std::endl << std::setw(25) << "algorithm" << " | min < mean < max | min < mean < max | n" << std::endl << std::setw(79) << std::setfill('-') << "" << std::setfill(' ') << std::endl; for (ar_stats_type::const_iterator i = ar_stats.begin(); i != ar_stats.end(); ++i) std::cout << std::setw(25) << i->first << " |" << std::setw(6) << i->second.min_prefix << ' ' << std::setw(8) << static_cast(i->second.tot_prefix) / i->second.n << ' ' << std::setw(6) << i->second.max_prefix << " |" << std::setw(6) << i->second.min_cycle << ' ' << std::setw(8) << static_cast(i->second.tot_cycle) / i->second.n << ' ' << std::setw(6) << i->second.max_cycle << " |" << std::setw(4) << i->second.n << std::endl; std::cout << std::setw(79) << std::setfill('-') << "" << std::setfill(' ') << std::endl << std::setw(25) << "" << " | runs | total |" << std::endl << std::setw(25) << "algorithm" << " | min < mean < max | pre. cyc. runs | n" << std::endl << std::setw(79) << std::setfill('-') << "" << std::setfill(' ') << std::endl; for (ar_stats_type::const_iterator i = ar_stats.begin(); i != ar_stats.end(); ++i) std::cout << std::setw(25) << i->first << " |" << std::setw(6) << i->second.min_run << ' ' << std::setw(8) << static_cast(i->second.tot_prefix + i->second.tot_cycle) / i->second.n << ' ' << std::setw(6) << i->second.max_run << " |" << std::setw(6) << i->second.tot_prefix << ' ' << std::setw(6) << i->second.tot_cycle << ' ' << std::setw(8) << i->second.tot_prefix + i->second.tot_cycle << " |" << std::setw(4) << i->second.n << std::endl; std::cout << std::setiosflags(old); } int main(int argc, char** argv) { bool opt_paper = false; int opt_n_acc = 0; float opt_a = 0.0; float opt_d = 0.2; int opt_n = 20; float opt_t = 0.5; bool opt_det = false; bool opt_0 = false; bool opt_z = false; bool opt_Z = false; int opt_R = 0; bool opt_dot = false; int opt_ec = 0; int opt_ec_seed = 0; bool opt_reduce = false; bool opt_replay = false; bool opt_degen = false; int argn = 0; int exit_code = 0; spot::twa_graph_ptr product = nullptr; spot::option_map options; auto& env = spot::default_environment::instance(); spot::atomic_prop_set* ap = new spot::atomic_prop_set; auto dict = spot::make_bdd_dict(); spot::tl_simplifier_options simpopt(true, true, true, true, true); spot::tl_simplifier simp(simpopt); if (argc <= 1) syntax(argv[0]); while (++argn < argc) { if (!strcmp(argv[argn], "-0")) { opt_0 = true; } else if (!strcmp(argv[argn], "-1")) { opt_paper = true; opt_z = true; } else if (!strcmp(argv[argn], "-a")) { if (argc < argn + 3) syntax(argv[0]); opt_n_acc = to_int_nonneg(argv[++argn], "-a"); opt_a = to_float_nonneg(argv[++argn], "-a"); } else if (!strcmp(argv[argn], "-A")) { if (argc < argn + 2) syntax(argv[0]); if (!opt_ec) opt_ec = 1; std::istream* in; if (strcmp(argv[++argn], "-")) { in = new std::ifstream(argv[argn]); if (!*in) { delete in; std::cerr << "Failed to open " << argv[argn] << '\n'; exit(2); } } else { in = &std::cin; } while (in->good()) { std::string input; if (std::getline(*in, input, '\n').fail()) break; else if (input == "") break; ec_algo a = { input, nullptr }; ec_algos.push_back(a); } if (in != &std::cin) delete in; } else if (!strncmp(argv[argn], "ar:", 3)) { if (options.parse_options(argv[argn])) { std::cerr << "Failed to parse " << argv[argn] << '\n'; exit(2); } } else if (!strcmp(argv[argn], "-d")) { if (argc < argn + 2) syntax(argv[0]); opt_d = to_float_nonneg(argv[++argn], "-d"); } else if (!strcmp(argv[argn], "-D")) { opt_degen = true; if (!opt_ec) opt_ec = 1; } else if (!strcmp(argv[argn], "-det")) { opt_det = true; } else if (!strcmp(argv[argn], "-e")) { if (argc < argn + 2) syntax(argv[0]); opt_ec = to_int_nonneg(argv[++argn], "-e"); } else if (!strcmp(argv[argn], "-g")) { opt_dot = true; } else if (!strcmp(argv[argn], "-m")) { opt_reduce = true; opt_replay = true; if (!opt_ec) opt_ec = 1; } else if (!strcmp(argv[argn], "-n")) { if (argc < argn + 2) syntax(argv[0]); opt_n = to_int_pos(argv[++argn], "-n"); } else if (!strcmp(argv[argn], "-r")) { opt_replay = true; if (!opt_ec) opt_ec = 1; } else if (!strcmp(argv[argn], "-R")) { if (argc < argn + 2) syntax(argv[0]); opt_R = to_int_pos(argv[++argn], "-R"); } else if (!strcmp(argv[argn], "-s")) { if (argc < argn + 2) syntax(argv[0]); opt_ec_seed = to_int_nonneg(argv[++argn], "-s"); spot::srand(opt_ec_seed); } else if (!strcmp(argv[argn], "-t")) { if (argc < argn + 2) syntax(argv[0]); opt_t = to_float_nonneg(argv[++argn], "-t"); } else if (!strcmp(argv[argn], "-z")) { opt_z = true; } else if (!strcmp(argv[argn], "-Z")) { opt_Z = opt_z = true; } else { ap->insert(env.require(argv[argn])); } } if (ec_algos.empty()) { const char** i = default_algos; while (*i) { ec_algo a = { *(i++), nullptr }; ec_algos.push_back(a); } } spot::timer_map tm_ec; spot::timer_map tm_ar; std::set failed_seeds; spot::atomic_prop_set* apf = new spot::atomic_prop_set; for (auto i: *ap) apf->insert(i); if (opt_ec) { for (unsigned i = 0; i < ec_algos.size(); ++i) { const char* err; ec_algos[i].inst = spot::make_emptiness_check_instantiator(ec_algos[i].name.c_str(), &err); if (!ec_algos[i].inst) { std::cerr << "Parse error after `" << err << "'\n"; exit(1); } ec_algos[i].inst->options().set(options); } } do { if (opt_ec && !opt_paper) std::cout << "seed: " << opt_ec_seed << std::endl; spot::srand(opt_ec_seed); spot::twa_graph_ptr a = spot::random_graph(opt_n, opt_d, apf, dict, opt_n_acc, opt_a, opt_t, opt_det); int real_n_acc = a->acc().num_sets(); if (opt_dot) { print_dot(std::cout, a); } if (!opt_ec) { if (!opt_0 && !opt_dot) print_hoa(std::cout, a, nullptr); } else { spot::twa_graph_ptr degen = nullptr; if (opt_degen && real_n_acc > 1) degen = degeneralize_tba(a); int n_alg = ec_algos.size(); int n_ec = 0; int n_empty = 0; int n_non_empty = 0; int n_maybe_empty = 0; for (int i = 0; i < n_alg; ++i) { auto ec = cons_emptiness_check(i, a, degen, real_n_acc); if (!ec) continue; ++n_ec; const std::string algo = ec_algos[i].name; if (!opt_paper) { std::cout.width(32); std::cout << algo << ": "; } tm_ec.start(algo); spot::emptiness_check_result_ptr res; for (int count = opt_R;;) { res = ec->check(); if (count-- <= 0) break; ec = cons_emptiness_check(i, a, degen, real_n_acc); } tm_ec.stop(algo); auto ecs = ec->statistics(); if (opt_z && res) { // Notice that ratios are computed w.r.t. the // generalized automaton a. prod_stats = spot::stats_reachable(a); } else { // To trigger a division by 0 if used erroneously. prod_stats.states = 0; prod_stats.edges = 0; } if (opt_z && ecs) { sc_ec.count(algo, ecs); if (res) { ec_ratio_stats[real_n_acc].count(algo, ecs); glob_ec_ratio_stats.count(algo, ecs); } } if (res) { if (!opt_paper) std::cout << "acc. run"; ++n_non_empty; if (opt_replay) { spot::twa_run_ptr run; tm_ar.start(algo); for (int count = opt_R;;) { run = res->accepting_run(); if (count-- <= 0 || !run) break; } if (!run) tm_ar.cancel(algo); else tm_ar.stop(algo); const spot::unsigned_statistics* s = res->statistics(); if (opt_z) { // Count only the last run (the // other way would be to divide // the stats by opt_R). sc_arc.count(algo, s); arc_ratio_stats.count(algo, s); } if (!run) { if (!opt_paper) std::cout << " exists, not computed"; } else { std::ostringstream s; if (!run->replay(s)) { if (!opt_paper) std::cout << ", but could not replay " << "it (ERROR!)"; failed_seeds.insert(opt_ec_seed); } else { if (!opt_paper) std::cout << ", computed"; if (opt_z) ar_stats[algo].count(run); } if (opt_z && !opt_paper) std::cout << " [" << run->prefix.size() << '+' << run->cycle.size() << ']'; if (opt_reduce) { auto redrun = run->reduce(); if (!redrun->replay(s)) { if (!opt_paper) std::cout << ", but could not replay " << "its minimization (ERROR!)"; failed_seeds.insert(opt_ec_seed); } else { if (!opt_paper) std::cout << ", reduced"; if (opt_z) mar_stats[algo].count(redrun); } if (opt_z && !opt_paper) { std::cout << " [" << redrun->prefix.size() << '+' << redrun->cycle.size() << ']'; } } } } if (!opt_paper) std::cout << std::endl; } else { if (ec->safe()) { if (!opt_paper) std::cout << "empty language" << std::endl; ++n_empty; } else { if (!opt_paper) std::cout << "maybe empty language" << std::endl; ++n_maybe_empty; } } if (opt_Z && !opt_paper) ec->print_stats(std::cout); } assert(n_empty + n_non_empty + n_maybe_empty == n_ec); if ((n_empty == 0 && (n_non_empty + n_maybe_empty) != n_ec) || (n_empty != 0 && n_non_empty != 0)) { std::cout << "ERROR: not all algorithms agree" << std::endl; failed_seeds.insert(opt_ec_seed); } } if (opt_ec) { --opt_ec; ++opt_ec_seed; } } while (opt_ec); apf->clear(); if (!opt_paper && opt_z) { if (!sc_ec.empty()) { std::cout << std::endl << "Statistics about emptiness checks:" << std::endl; sc_ec.display(std::cout); } if (!sc_arc.empty()) { std::cout << std::endl << "Statistics about accepting run computations:" << std::endl; sc_arc.display(std::cout); } if (!glob_ec_ratio_stats.empty()) { std::cout << std::endl << "Emptiness check ratios for non-empty automata:" << std::endl << "all tests" << std::endl; glob_ec_ratio_stats.display(std::cout, false); if (ec_ratio_stats.size() > 1) for (ec_ratio_stats_type::const_iterator i = ec_ratio_stats.begin(); i != ec_ratio_stats.end(); ++i) { std::cout << "tests with " << i->first << " acceptance conditions" << std::endl; i->second.display(std::cout, false); } } if (!ar_stats.empty()) print_ar_stats(ar_stats, "Statistics about accepting runs:"); if (!mar_stats.empty()) print_ar_stats(mar_stats, "Statistics about reduced accepting runs:"); if (!arc_ratio_stats.empty()) { std::cout << std::endl << "Accepting run ratios:" << std::endl; arc_ratio_stats.display(std::cout, false); } if (!tm_ec.empty()) { std::cout << std::endl << "emptiness checks cumulated timings:" << std::endl; tm_ec.print(std::cout); } if (!tm_ar.empty()) { std::cout << std::endl << "accepting runs cumulated timings:" << std::endl; tm_ar.print(std::cout); } } else if (opt_paper) { std::cout << "Emptiness check ratios" << std::endl; std::cout << std::right << std::fixed << std::setprecision(1); ec_ratio_stat_type::stats_alg_map& stats = glob_ec_ratio_stats.stats; typedef ec_ratio_stat_type::alg_1stat_map::const_iterator ec_iter; for (unsigned ai = 0; ai < ec_algos.size(); ++ai) { const std::string algo = ec_algos[ai].name; int n = -1; std::cout << std::setw(25) << algo << ' ' << std::setw(8); ec_iter i = stats["states"].find(algo); if (i != stats["states"].end()) { std::cout << i->second.tot / i->second.n; n = i->second.n; } else std::cout << ""; std::cout << ' ' << std::setw(8); i = stats["transitions"].find(algo); if (i != stats["transitions"].end()) { std::cout << i->second.tot / i->second.n; n = i->second.n; } else std::cout << ""; std::cout << ' ' << std::setw(8); i = stats["max. depth"].find(algo); if (i != stats["max. depth"].end()) { std::cout << i->second.tot / i->second.n; n = i->second.n; } else std::cout << ""; if (n >= 0) std::cout << ' ' << std::setw(8) << n; std::cout << std::endl; } std::cout << std::endl << "Accepting run ratios" << std::endl; std::cout << std::right << std::fixed << std::setprecision(1); ec_ratio_stat_type::stats_alg_map& stats2 = arc_ratio_stats.stats; for (unsigned ai = 0; ai < ec_algos.size(); ++ai) { const std::string algo = ec_algos[ai].name; std::cout << std::setw(25) << algo << ' ' << std::setw(8); ec_iter i = stats2["search space states"].find(algo); if (i != stats2["search space states"].end()) std::cout << i->second.tot / i->second.n; else std::cout << ""; std::cout << ' ' << std::setw(8); i = stats2["(non unique) states for cycle"].find(algo); if (i != stats2["(non unique) states for cycle"].end()) std::cout << i->second.tot / i->second.n; else std::cout << ""; std::cout << std::endl; } } if (!failed_seeds.empty()) { exit_code = 1; std::cout << "The check failed for the following seeds:"; for (std::set::const_iterator i = failed_seeds.begin(); i != failed_seeds.end(); ++i) std::cout << ' ' << *i; std::cout << std::endl; } delete ap; delete apf; return exit_code; }