ltlcheck: add options --json and --csv

* src/bin/ltlcheck.cc: Add option --json and --csv.
* src/bin/man/ltlcheck.x: Adjust examples.
* src/tgbatest/ltlcheck.test: Do not output any statistic.
* src/tgbatest/ltlcheck2.test: Output both JSON and CSV stats.
This commit is contained in:
Alexandre Duret-Lutz 2012-10-15 17:31:43 +02:00
parent fc374c0790
commit b2a5d5aa62
4 changed files with 170 additions and 55 deletions

View file

@ -51,6 +51,7 @@
#include "misc/formater.hh" #include "misc/formater.hh"
#include "tgbaalgos/stats.hh" #include "tgbaalgos/stats.hh"
#include "tgbaalgos/isdet.hh" #include "tgbaalgos/isdet.hh"
#include "misc/escape.hh"
// Disable handling of timeout on systems that miss kill() or alarm(). // Disable handling of timeout on systems that miss kill() or alarm().
// For instance MinGW. // For instance MinGW.
@ -74,6 +75,8 @@ Exit status:\n\
#define OPT_STATES 1 #define OPT_STATES 1
#define OPT_DENSITY 2 #define OPT_DENSITY 2
#define OPT_JSON 3
#define OPT_CSV 4
static const argp_option options[] = static const argp_option options[] =
{ {
@ -104,6 +107,12 @@ static const argp_option options[] =
"probability, between 0.0 and 1.0, to add a transition between " "probability, between 0.0 and 1.0, to add a transition between "
"two states (0.1 by default)", 0 }, "two states (0.1 by default)", 0 },
/**************************************************/ /**************************************************/
{ 0, 0, 0, 0, "Statistics ouput:", 5 },
{ "json", OPT_JSON, "FILENAME", OPTION_ARG_OPTIONAL,
"output statistics as JSON in FILENAME or on standard output", 0 },
{ "csv", OPT_CSV, "FILENAME", OPTION_ARG_OPTIONAL,
"output statistics as CSV in FILENAME or on standard output", 0 },
/**************************************************/
{ 0, 0, 0, 0, "Miscellaneous options:", -1 }, { 0, 0, 0, 0, "Miscellaneous options:", -1 },
{ 0, 0, 0, 0, 0, 0 } { 0, 0, 0, 0, 0, 0 }
}; };
@ -118,6 +127,9 @@ spot::bdd_dict dict;
unsigned states = 200; unsigned states = 200;
float density = 0.1; float density = 0.1;
unsigned timeout = 0; unsigned timeout = 0;
const char* json_output = 0;
const char* csv_output = 0;
bool want_stats = false;
std::vector<char*> translators; std::vector<char*> translators;
bool global_error_flag = false; bool global_error_flag = false;
@ -147,16 +159,16 @@ struct statistics
static void static void
fields(std::ostream& os) fields(std::ostream& os)
{ {
os << (" \"states\",\n" os << (" \"states\","
" \"edges\",\n" " \"edges\","
" \"transitions\",\n" " \"transitions\","
" \"acc\",\n" " \"acc\","
" \"scc\",\n" " \"scc\","
" \"nondetstates\",\n" " \"nondetstates\","
" \"nondeterministic\",\n" " \"nondeterministic\","
" \"time\",\n" " \"time\","
" \"product_states\",\n" " \"product_states\","
" \"product_transitions\",\n" " \"product_transitions\","
" \"product_scc\""); " \"product_scc\"");
} }
@ -239,9 +251,17 @@ parse_opt(int key, char* arg, struct argp_state*)
<< "on your platform" << std::endl; << "on your platform" << std::endl;
#endif #endif
break; break;
case OPT_CSV:
want_stats = true;
csv_output = arg ? arg : "-";
break;
case OPT_DENSITY: case OPT_DENSITY:
density = to_probability(arg); density = to_probability(arg);
break; break;
case OPT_JSON:
want_stats = true;
json_output = arg ? arg : "-";
break;
case OPT_STATES: case OPT_STATES:
states = to_pos_int(arg); states = to_pos_int(arg);
break; break;
@ -540,7 +560,7 @@ namespace
case printable_result_filename::Lbtt: case printable_result_filename::Lbtt:
{ {
std::string error; std::string error;
std::fstream f(output.val().c_str()); std::ifstream f(output.val().c_str());
if (!f) if (!f)
{ {
global_error() << "Cannot open " << output.val() global_error() << "Cannot open " << output.val()
@ -562,7 +582,7 @@ namespace
} }
} }
// Compute statistics. // Compute statistics.
if (res) if (res && want_stats)
{ {
statistics* st = &(*fstats)[translator_num]; statistics* st = &(*fstats)[translator_num];
st->ok = true; st->ok = true;
@ -788,11 +808,14 @@ namespace
pos_map[i] = sm; pos_map[i] = sm;
// Statistics // Statistics
if (want_stats)
{
(*pstats)[i].product_scc = sm->scc_count(); (*pstats)[i].product_scc = sm->scc_count();
spot::tgba_statistics s = spot::stats_reachable(p); spot::tgba_statistics s = spot::stats_reachable(p);
(*pstats)[i].product_states = s.states; (*pstats)[i].product_states = s.states;
(*pstats)[i].product_transitions = s.transitions; (*pstats)[i].product_transitions = s.transitions;
} }
}
for (size_t i = 0; i < m; ++i) for (size_t i = 0; i < m; ++i)
if (neg[i]) if (neg[i])
{ {
@ -803,11 +826,14 @@ namespace
neg_map[i] = sm; neg_map[i] = sm;
// Statistics // Statistics
if (want_stats)
{
(*nstats)[i].product_scc = sm->scc_count(); (*nstats)[i].product_scc = sm->scc_count();
spot::tgba_statistics s = spot::stats_reachable(p); spot::tgba_statistics s = spot::stats_reachable(p);
(*nstats)[i].product_states = s.states; (*nstats)[i].product_states = s.states;
(*nstats)[i].product_transitions = s.transitions; (*nstats)[i].product_transitions = s.transitions;
} }
}
// cross-comparison test // cross-comparison test
cross_check(pos_map, 'P'); cross_check(pos_map, 'P');
@ -848,37 +874,95 @@ namespace
} }
static void static void
print_stats_json() print_stats_csv(const char* filename)
{ {
unsigned rounds = vstats.size(); std::ofstream* outfile = 0;
if (!rounds) std::ostream* out;
return; if (!strncmp(filename, "-", 2))
{
out = &std::cout;
}
else
{
out = outfile = new std::ofstream(filename);
if (!outfile)
error(2, errno, "cannot open '%s'", filename);
}
unsigned ntrans = translators.size(); unsigned ntrans = translators.size();
unsigned rounds = vstats.size();
assert(rounds = formulas.size()); assert(rounds = formulas.size());
std::cout << "{\n tools: [\n \"" << translators[0]; *out << "\"formula\", \"tool\", ";
statistics::fields(*out);
*out << "\n";
for (unsigned r = 0; r < rounds; ++r)
for (unsigned t = 0; t < ntrans; ++t)
if (vstats[r][t].ok)
{
*out << "\"";
spot::escape_str(*out, formulas[r]);
*out << "\", \"";
spot::escape_str(*out, translators[t]);
*out << "\", ";
vstats[r][t].to_csv(*out);
*out << "\n";
}
delete outfile;
}
static void
print_stats_json(const char* filename)
{
std::ofstream* outfile = 0;
std::ostream* out;
if (!strncmp(filename, "-", 2))
{
out = &std::cout;
}
else
{
out = outfile = new std::ofstream(filename);
if (!outfile)
error(2, errno, "cannot open '%s'", filename);
}
unsigned ntrans = translators.size();
unsigned rounds = vstats.size();
assert(rounds = formulas.size());
*out << "{\n \"tools\": [\n \"";
spot::escape_str(*out, translators[0]);
for (unsigned t = 1; t < ntrans; ++t) for (unsigned t = 1; t < ntrans; ++t)
std::cout << "\",\n \"" << translators[t]; {
std::cout << "\"\n ],\n inputs: [\n \"" << formulas[0]; *out << "\",\n \"";
spot::escape_str(*out, translators[t]);
}
*out << "\"\n ],\n \"inputs\": [\n \"";
spot::escape_str(*out, formulas[0]);
for (unsigned r = 1; r < rounds; ++r) for (unsigned r = 1; r < rounds; ++r)
std::cout << "\",\n \"" << formulas[r]; {
std::cout << ("\"\n ],\n fields: [\n \"input\",\n" *out << "\",\n \"";
" \"tool\",\n"); spot::escape_str(*out, formulas[r]);
statistics::fields(std::cout); }
std::cout << "\n ],\n results: ["; *out << ("\"\n ],\n \"fields\": [\n \"input\", \"tool\",");
statistics::fields(*out);
*out << "\n ],\n \"results\": [";
bool notfirst = false; bool notfirst = false;
for (unsigned r = 0; r < rounds; ++r) for (unsigned r = 0; r < rounds; ++r)
for (unsigned t = 0; t < ntrans; ++t) for (unsigned t = 0; t < ntrans; ++t)
if (vstats[r][t].ok) if (vstats[r][t].ok)
{ {
if (notfirst) if (notfirst)
std::cout << ","; *out << ",";
notfirst = true; notfirst = true;
std::cout << "\n [ " << r << ", " << t << ", "; *out << "\n [ " << r << ", " << t << ", ";
vstats[r][t].to_csv(std::cout); vstats[r][t].to_csv(*out);
std::cout << " ]"; *out << " ]";
} }
std::cout << "\n ]\n}\n"; *out << "\n ]\n}\n";
delete outfile;
} }
int int
@ -905,6 +989,25 @@ main(int argc, char** argv)
if (p.run()) if (p.run())
return 2; return 2;
print_stats_json(); if (formulas.empty())
{
error(2, 0, "no formula to translate");
}
else
{
if (global_error_flag)
std::cerr
<< ("error: some error was detected during the above runs,\n"
" please search for 'error:' messages in the above trace.")
<< std::endl;
else
std::cerr << "no problem detected" << std::endl;
}
if (json_output)
print_stats_json(json_output);
if (csv_output)
print_stats_csv(csv_output);
return global_error_flag; return global_error_flag;
} }

View file

@ -5,21 +5,34 @@
The following commands compare never claims produced by ltl2tgba(1) The following commands compare never claims produced by ltl2tgba(1)
and spin(1) and 100 random formulas, using a timeout of 2 minutes. A and spin(1) and 100 random formulas, using a timeout of 2 minutes. A
trace of the execution of the two tools, including any potential issue trace of the execution of the two tools, including any potential issue
detected, is reported on standard error, while statistics gathered detected, is reported on standard error, while statistics are
(normally sent to standard output) are redicted to \f(CWresults.json\fR. written to \f(CWresults.json\fR.
\f(CW% randltl -n100 --tree-size=20..30 a b c | \e .nf
.br % randltl \-n100 \-\-tree\-size=20..30 a b c | \e
ltlcheck -T120 'ltl2tgba -s %f > %N' 'spin -f %s > %N' > results.json\fR ltlcheck \-T120 'ltl2tgba \-s %f >%N' 'spin \-f %s >%N' \-\-json=results.json
.fi
.LP
The next command compare lbt, ltl3ba, and ltl2tgba(1) on a set of The next command compare lbt, ltl3ba, and ltl2tgba(1) on a set of
formulas saved in file \f(CWinput.ltl\fR. Statistis are again formulas saved in file \f(CWinput.ltl\fR. Statistics are again writen
redirected to \f(CWresults.json\fR. Note the use of \f(CW%L\fR to as CSV into \f(CWresults.csv\fR. Note the use of \f(CW%L\fR to
indicate that the LBT formula should be written into a file, and indicate that the formula passed to lbt should be written into a file
\f(CW%T\fR to read the output in LBTT's format (which is a superset of in LBT's format, and \f(CW%T\fR to read the output in LBTT's format
the format output by LBT). (which is a superset of the format output by LBT).
\f(CW% ltlcheck -F input.ltl 'lbt < %L > %T' 'ltl2tgba --lbtt %f > %T'\fR .nf
% ltlcheck \-F input.ltl \-\-csv=results.csv \e
'lbt <%L >%T' \e
'ltl3ba \-f %s >%N' \e
'ltl2tgba \-\-lbtt %f >%T'
.fi
.LP
If you use ltlcheck in an automated testsuite just to check for
potential problems, avoid the \fB\-\-csv\fR and \fB\-\-json\fR
options: ltlcheck is faster when it does not have to compute these
statistics.
[SEE ALSO] [SEE ALSO]
randltl(1), genltl(1), ltlfilt(1), ltl2tgba(1) randltl(1), genltl(1), ltlfilt(1), ltl2tgba(1)
@ -28,7 +41,7 @@ randltl(1), genltl(1), ltlfilt(1), ltl2tgba(1)
ltlcheck is a Spot-based reimplementation of a tool called LBTT. LBTT ltlcheck is a Spot-based reimplementation of a tool called LBTT. LBTT
was developped by Heikki Tauriainen at the Helsinki University of was developped by Heikki Tauriainen at the Helsinki University of
Technology. The main motivation for the reimplementation was to Technology. The main motivation for the reimplementation was to
support PSL, and output more statistics about the translation. support PSL, and output more statistics about the translations.
The sanity checks performed on the result of each translator (by The sanity checks performed on the result of each translator (by
either LBTT or ltlcheck) are described in the following paper. Our either LBTT or ltlcheck) are described in the following paper. Our

View file

@ -45,7 +45,7 @@ ltl2tgba=../ltl2tgba
"$ltl2tgba -t -f -x -p -L -D %f > %T" \ "$ltl2tgba -t -f -x -p -L -D %f > %T" \
"$ltl2tgba -t -taa -r4 %f > %T" \ "$ltl2tgba -t -taa -r4 %f > %T" \
"$ltl2tgba -t -taa -r4 -c %f > %T" \ "$ltl2tgba -t -taa -r4 -c %f > %T" \
"$ltl2tgba -t -taa -r4 -R3 -RDS %f > %T" > output.json "$ltl2tgba -t -taa -r4 -R3 -RDS %f > %T"
# Disabled because too slow, and too big automata produced. # Disabled because too slow, and too big automata produced.
# "$ltl2tgba -t -lo -r4 %f > %T" # "$ltl2tgba -t -lo -r4 %f > %T"

View file

@ -23,7 +23,6 @@
ltl2tgba=../../bin/ltl2tgba ltl2tgba=../../bin/ltl2tgba
../../bin/randltl -P -n 100 p1 p2 p3 p4 p5 p6 --tree-size 5..15 | ../../bin/randltl -P -n 100 p1 p2 p3 p4 p5 p6 --tree-size 5..15 |
../../bin/ltlfilt --remove-wm |
../../bin/ltlcheck \ ../../bin/ltlcheck \
"$ltl2tgba --lbtt --any --small %f > %T" \ "$ltl2tgba --lbtt --any --small %f > %T" \
"$ltl2tgba --lbtt --any --medium %f > %T" \ "$ltl2tgba --lbtt --any --medium %f > %T" \
@ -33,7 +32,7 @@ ltl2tgba=../../bin/ltl2tgba
"$ltl2tgba --lbtt --deterministic --high %f > %T" \ "$ltl2tgba --lbtt --deterministic --high %f > %T" \
"$ltl2tgba --lbtt --high --small %f > %T" \ "$ltl2tgba --lbtt --high --small %f > %T" \
"$ltl2tgba --lbtt --high --medium %f > %T" \ "$ltl2tgba --lbtt --high --medium %f > %T" \
"$ltl2tgba --lbtt --high --high %f > %T" > output.json "$ltl2tgba --lbtt --high --high %f > %T" \
--json=output.json --csv=output.csv