Commit 59e1f6a3 authored by Alexandre Duret-Lutz's avatar Alexandre Duret-Lutz
Browse files

ltlfilt: implement --reject-word and --accept-word

* bin/common_range.hh: Store the common definition of words.
* bin/autfilt.cc: Use it.
* bin/ltlfilt.cc: Likewise, and implement those two options.
* tests/core/acc_word.test: Test them.
* doc/org/autfilt.org: Augment the last example to point out
that it can now be done with ltlfilt.
* NEWS: Mention the new options.
parent 901f2870
New in spot 1.99.9a (not yet released)
Nothing yet.
Command-line tools:
* ltlfilt now also support the --accept-word=WORD and
--reject-word=WORD options that were introduced in autfilt in the
previous version.
New in spot 1.99.9 (2016-03-14)
......
......@@ -232,10 +232,7 @@ static const argp_option options[] =
{ "reject-word", OPT_REJECT_WORD, "WORD", 0,
"keep automata that reject WORD", 0 },
RANGE_DOC_FULL,
{ nullptr, 0, nullptr, 0,
"WORD is lasso-shaped and written as 'BF;BF;...;BF;cycle{BF;...;BF}' "
"where BF are arbitrary Boolean formulas. The 'cycle{...}' part is "
"mandatory, but the prefix can be omitted.", 0 },
WORD_DOC,
{ nullptr, 0, nullptr, 0,
"If any option among --small, --deterministic, or --any is given, "
"then the simplification level defaults to --high unless specified "
......
// -*- coding: utf-8 -*-
// Copyright (C) 2012, 2014, 2015 Laboratoire de Recherche et
// Copyright (C) 2012, 2014, 2015, 2016 Laboratoire de Recherche et
// Développement de l'Epita (LRDE).
//
// This file is part of Spot, a model checking library.
......@@ -19,17 +19,23 @@
#pragma once
#define RANGE_DOC \
{ nullptr, 0, nullptr, 0, \
"RANGE may have one of the following forms: 'INT', " \
#define RANGE_DOC \
{ nullptr, 0, nullptr, 0, \
"RANGE may have one of the following forms: 'INT', " \
"'INT..INT', or '..INT'.\nIn the latter case, the missing number " \
"is assumed to be 1.", 0 }
#define RANGE_DOC_FULL \
{ nullptr, 0, nullptr, 0, \
"RANGE may have one of the following forms: 'INT', " \
#define RANGE_DOC_FULL \
{ nullptr, 0, nullptr, 0, \
"RANGE may have one of the following forms: 'INT', " \
"'INT..INT', '..INT', or 'INT..'", 0 }
#define WORD_DOC \
{ nullptr, 0, nullptr, 0, \
"WORD is lasso-shaped and written as 'BF;BF;...;BF;cycle{BF;...;BF}' " \
"where BF are arbitrary Boolean formulas. The 'cycle{...}' part is " \
"mandatory, but the prefix can be omitted.", 0 }
struct range
{
int min;
......
......@@ -48,6 +48,8 @@
#include <spot/twaalgos/minimize.hh>
#include <spot/twaalgos/strength.hh>
#include <spot/twaalgos/stutter.hh>
#include <spot/twaalgos/word.hh>
#include <spot/twaalgos/product.hh>
const char argp_program_doc[] ="\
Read a list of formulas and output them back after some optional processing.\v\
......@@ -57,7 +59,8 @@ Exit status:\n\
2 if any error has been reported";
enum {
OPT_AP_N = 256,
OPT_ACCEPT_WORD = 256,
OPT_AP_N,
OPT_BOOLEAN,
OPT_BOOLEAN_TO_ISOP,
OPT_BSIZE,
......@@ -76,6 +79,7 @@ enum {
OPT_NEGATE,
OPT_NNF,
OPT_OBLIGATION,
OPT_REJECT_WORD,
OPT_RELABEL,
OPT_RELABEL_BOOL,
OPT_REMOVE_WM,
......@@ -199,7 +203,12 @@ static const argp_option options[] =
{ "invert-match", 'v', nullptr, 0, "select non-matching formulas", 0},
{ "unique", 'u', nullptr, 0,
"drop formulas that have already been output (not affected by -v)", 0 },
{ "accept-word", OPT_ACCEPT_WORD, "WORD", 0,
"keep formulas that accept WORD", 0 },
{ "reject-word", OPT_REJECT_WORD, "WORD", 0,
"keep formulas that reject WORD", 0 },
RANGE_DOC_FULL,
WORD_DOC,
/**************************************************/
{ nullptr, 0, nullptr, 0, "Output options:", -20 },
{ "count", 'c', nullptr, 0, "print only a count of matched formulas", 0 },
......@@ -275,11 +284,14 @@ static long int match_count = 0;
// accessing deleted stuff.
static struct opt_t
{
spot::bdd_dict_ptr dict = spot::make_bdd_dict();
spot::exclusive_ap excl_ap;
std::unique_ptr<output_file> output_define = nullptr;
spot::formula implied_by = nullptr;
spot::formula imply = nullptr;
spot::formula equivalent_to = nullptr;
std::vector<spot::twa_graph_ptr> acc_words;
std::vector<spot::twa_graph_ptr> rej_words;
}* opt;
static std::string unabbreviate;
......@@ -322,6 +334,18 @@ parse_opt(int key, char* arg, struct argp_state*)
// FIXME: use stat() to distinguish filename from string?
jobs.emplace_back(arg, true);
break;
case OPT_ACCEPT_WORD:
try
{
opt->acc_words.push_back(spot::parse_word(arg, opt->dict)
->as_automaton());
}
catch (const spot::parse_error& e)
{
error(2, 0, "failed to parse the argument of --accept-word:\n%s",
e.what());
}
break;
case OPT_BOOLEAN:
boolean = true;
break;
......@@ -388,6 +412,18 @@ parse_opt(int key, char* arg, struct argp_state*)
case OPT_OBLIGATION:
obligation = true;
break;
case OPT_REJECT_WORD:
try
{
opt->rej_words.push_back(spot::parse_word(arg, opt->dict)
->as_automaton());
}
catch (const spot::parse_error& e)
{
error(2, 0, "failed to parse the argument of --reject-word:\n%s",
e.what());
}
break;
case OPT_RELABEL:
case OPT_RELABEL_BOOL:
relabeling = (key == OPT_RELABEL_BOOL ? BseRelabeling : ApRelabeling);
......@@ -603,24 +639,46 @@ namespace
|| simpl.are_equivalent(f, opt->equivalent_to);
matched &= !stutter_insensitive || spot::is_stutter_invariant(f);
// Match obligations and subclasses using WDBA minimization.
// Because this is costly, we compute it later, so that we don't
// have to compute it on formulas that have been discarded for
// other reasons.
if (matched && obligation)
if (matched && (obligation
|| !opt->acc_words.empty()
|| !opt->rej_words.empty()))
{
auto aut = ltl_to_tgba_fm(f, simpl.get_dict());
auto min = minimize_obligation(aut, f);
assert(min);
if (aut == min)
{
// Not an obligation
matched = false;
}
else
if (matched && !opt->acc_words.empty())
for (auto& word_aut: opt->acc_words)
if (spot::product(aut, word_aut)->is_empty())
{
matched = false;
break;
}
if (matched && !opt->rej_words.empty())
for (auto& word_aut: opt->rej_words)
if (!spot::product(aut, word_aut)->is_empty())
{
matched = false;
break;
}
// Match obligations and subclasses using WDBA minimization.
// Because this is costly, we compute it later, so that we don't
// have to compute it on formulas that have been discarded for
// other reasons.
if (matched && obligation)
{
matched &= !guarantee || is_terminal_automaton(min);
matched &= !safety || is_safety_mwdba(min);
auto min = minimize_obligation(aut, f);
assert(min);
if (aut == min)
{
// Not an obligation
matched = false;
}
else
{
matched &= !guarantee || is_terminal_automaton(min);
matched &= !safety || is_safety_mwdba(min);
}
}
}
......@@ -676,9 +734,9 @@ main(int argc, char** argv)
if (boolean_to_isop && simplification_level == 0)
simplification_level = 1;
spot::tl_simplifier_options opt(simplification_level);
opt.boolean_to_isop = boolean_to_isop;
spot::tl_simplifier simpl(opt);
spot::tl_simplifier_options tlopt(simplification_level);
tlopt.boolean_to_isop = boolean_to_isop;
spot::tl_simplifier simpl(tlopt, opt->dict);
ltl_processor processor(simpl);
if (processor.run())
......
......@@ -756,3 +756,25 @@ Fa U !b
Fb R F!b
XF!b U (!b & (!a | G!b))
#+end_example
Note that the above example could be simplified using the
=--accept-word= and =--reject-word= options of =ltlfilt= directly.
However this demonstrates that using =--stats=%M=, it is possible to
filter formulas based on some properties of automata that have been
generated by from them. The translator needs not be =ltl2tgba=: other
tools can be wrapped with [[file:ltldo.org][=ltldo --name=%f=]] to ensure they work well
in a pipeline and preserve the formula name in the HOA output. For
example Here is a list of 5 LTL formulas that =ltl2dstar= converts to
Rabin automata that have exactly 4 states:
#+BEGIN_SRC sh :results verbatim :exports both
randltl -n -1 a b | ltlfilt --simplify --remove-wm |
ltldo ltl2dstar --name=%f | autfilt --states=4 --stats=%M -n 5
#+END_SRC
#+RESULTS:
: Gb | G!b
: b R (a | b)
: (a & !b & (b | (F!a U (!b & F!a)))) | (!a & (b | (!b & (Ga R (b | Ga)))))
: (a & (a U !b)) | (!a & (!a R b))
: a | G((a & GFa) | (!a & FG!a))
......@@ -42,6 +42,12 @@ F(!a & !b)
EOF
diff out expect
# The same, without using automata explicitly
randltl -n -1 a b | ltlfilt --simplify --uniq \
--accept-word='a&!b;cycle{!a&!b}' --accept-word='!a&!b;cycle{a&b}' \
--reject-word='cycle{b}' -n 3 > out
diff out expect
# Test syntax errors
autfilt --reject='foobar' </dev/null 2>error && exit 1
autfilt --accept='cycle{foo' </dev/null 2>>error && exit 1
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment