From e327f6ea11ac43e5de222c512f308328dd60fb00 Mon Sep 17 00:00:00 2001 From: Thibaud Michaud Date: Fri, 12 Sep 2014 15:39:01 +0200 Subject: [PATCH] Adding ltlgrind as a command-line tool * src/bin/ltlgrind.cc: New file, command-line tool to get mutations of a formula. * src/bin/Makefile.am: Add it. * src/ltlvisit/mutation.hh, src/ltlvisit/mutation.cc: New files providing the get_mutations function. * src/ltlvisit/Makefile.am: Add it. * src/ltltest/ltlgrind.test: Test it. * src/ltltest/Makefile.am: Add it. * src/bin/man/ltlgrind.x: Document it. * src/bin/man/Makefile.am: Add it. * doc/org/ltlgrind.org: Document it. * doc/org/tools.org: Add link to ltlgrind documentation page. --- doc/org/ltlgrind.org | 82 ++++++++ doc/org/tools.org | 2 + src/bin/Makefile.am | 3 +- src/bin/ltlgrind.cc | 207 ++++++++++++++++++++ src/bin/man/Makefile.am | 4 + src/bin/man/ltlgrind.x | 6 + src/ltltest/Makefile.am | 1 + src/ltltest/ltlgrind.test | 166 ++++++++++++++++ src/ltlvisit/Makefile.am | 2 + src/ltlvisit/mutation.cc | 399 ++++++++++++++++++++++++++++++++++++++ src/ltlvisit/mutation.hh | 52 +++++ 11 files changed, 923 insertions(+), 1 deletion(-) create mode 100644 doc/org/ltlgrind.org create mode 100644 src/bin/ltlgrind.cc create mode 100644 src/bin/man/ltlgrind.x create mode 100755 src/ltltest/ltlgrind.test create mode 100644 src/ltlvisit/mutation.cc create mode 100644 src/ltlvisit/mutation.hh diff --git a/doc/org/ltlgrind.org b/doc/org/ltlgrind.org new file mode 100644 index 000000000..4eb85bcd5 --- /dev/null +++ b/doc/org/ltlgrind.org @@ -0,0 +1,82 @@ +#+TITLE: =ltlgrind= +#+EMAIL spot@lrde.epita.fr +#+OPTIONS: H:2 num:nil toc:t +#+LINK_UP: tools.html + +This tool lists formulas that are similar to but simpler than a given +formula by applying simple mutations to it, like removing operands or +operators. This is meant to be used with ltlcross to simplify a +formula that caused a problem before trying to debug it (see also +=ltlcross --grind=FILENAME=). + +Here is a list of the different kind of mutations that can be applied: + +#+BEGIN_SRC sh :results verbatim :exports results +ltlgrind --help | sed -n '/Transformation rules:/,/^$/p' | sed '1d;$d' +#+END_SRC + +#+RESULTS: +#+begin_example + --ap-to-const atomic propositions are replaced with true/false + --remove-multop-operands remove one operand from multops + --remove-one-ap all occurrences of an atomic proposition are + replaced with another atomic proposition used in + the formula + --remove-ops replace unary/binary operators with one of their + operands + --rewrite-ops rewrite operators that have a semantically simpler + form: a U b becomes a W b, etc. + --simplify-bounds on a bounded unary operator, decrement one of the + bounds, or set min to 0 or max to unbounded. + --split-ops when an operator can be expressed as a + conjunction/disjunction using simpler operators, + each term of the conjunction/disjunction is a + mutation. e.g. a <-> b can be written as ((a & b) + | (!a & !b)) or as ((a -> b) & (b -> a)) so those + four terms can be a mutation of a <-> b +#+end_example + +By default, all rules are applied. But if one or more rules are +specified, only those will be applied. + +#+BEGIN_SRC sh :results verbatim :exports both +ltlgrind -f 'a U G(b <-> c)' --split-ops --rewrite-ops --remove-ops +#+END_SRC + +#+RESULTS: +#+begin_example +a +G(b <-> c) +a W G(b <-> c) +a U (b <-> c) +a U Gb +a U Gc +a U G(b -> c) +a U G(c -> b) +a U G(b & c) +a U G(!b & !c) +#+end_example + +The idea behind this tool is that when a bogus algorithm is found with +=ltlcross=, you probably want to debug it using a smaller formula than +the one found by =ltlcross=. So you would give the formula found by +=ltlcross= as an argument to =ltlgrind= and then use the resulting +mutations as an new input for =ltlcross=. It might report an error on +one of the mutation, which is guaranteed to be simpler than the +initial formula. The process can then be repeated until no error is +reported by =ltlcross=. + +Since the whole process can become quite tedious, it can be done +automatically by calling =ltlcross= with the =--grind=FILENAME= +argument. + +* Miscellaneous options +** =--sort= + Formulas are outputted from the shortest to the longest one. The + length of a formula is the number of atomic propositions, constants + and operators occuring in the formula. +** =-m N=, =--mutations=N= + This option is used to specify the number of mutations to be + applied to the formula. This is like calling ltlgrind on its own + output several times, except for the fact that, when called on + several formulas, ltlgrind doesn't handle duplicates. diff --git a/doc/org/tools.org b/doc/org/tools.org index 26dd29f2b..56963c9aa 100644 --- a/doc/org/tools.org +++ b/doc/org/tools.org @@ -43,6 +43,8 @@ corresponding commands are hidden. - [[file:ltlcross.org][=ltlcross=]] Cross-compare LTL/PSL-to-Büchi translators. - [[file:dstar2tgba.org][=dstar2tgba=]] Convert deterministic Rabin or Streett automata into Büchi automata. +- [[file:ltlgrind.org][=ltlgrind=]] List formulas similar to but simpler than a given LTL/PSL + formula * Advanced uses diff --git a/src/bin/Makefile.am b/src/bin/Makefile.am index 97972dcf0..616cc0256 100644 --- a/src/bin/Makefile.am +++ b/src/bin/Makefile.am @@ -43,7 +43,7 @@ libcommon_a_SOURCES = \ common_sys.hh bin_PROGRAMS = ltlfilt genltl randltl ltl2tgba ltl2tgta ltlcross \ - dstar2tgba + dstar2tgba ltlgrind # Dummy program used just to generate man/spot-x.7 in a way that is # consistent with the other man pages (e.g., with a version number that @@ -56,6 +56,7 @@ randltl_SOURCES = randltl.cc ltl2tgba_SOURCES = ltl2tgba.cc ltl2tgta_SOURCES = ltl2tgta.cc ltlcross_SOURCES = ltlcross.cc +ltlgrind_SOURCES = ltlgrind.cc dstar2tgba_SOURCES = dstar2tgba.cc spot_x_SOURCES = spot-x.cc ltlcross_LDADD = $(LDADD) $(LIB_GETHRXTIME) diff --git a/src/bin/ltlgrind.cc b/src/bin/ltlgrind.cc new file mode 100644 index 000000000..10f800b33 --- /dev/null +++ b/src/bin/ltlgrind.cc @@ -0,0 +1,207 @@ +// -*- coding: utf-8 -*- +// Copyright (C) 2012, 2013, 2014 Laboratoire de Recherche et +// Développement de l'Epita (LRDE). +// +// 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 "common_sys.hh" +#include +#include +#include "common_setup.hh" +#include "common_finput.hh" +#include "common_output.hh" +#include "error.h" +#include "ltlast/allnodes.hh" +#include "ltlvisit/clone.hh" +#include "ltlvisit/apcollect.hh" +#include "ltlvisit/length.hh" +#include "ltlvisit/mutation.hh" + +#define OPT_AP2CONST 1 +#define OPT_SIMPLIFY_BOUNDS 2 +#define OPT_REMOVE_MULTOP_OPERANDS 3 +#define OPT_REMOVE_OPS 4 +#define OPT_SPLIT_OPS 5 +#define OPT_REWRITE_OPS 6 +#define OPT_REMOVE_ONE_AP 7 +#define OPT_SORT 8 + +static unsigned mutation_nb = 1; +static int max_output = std::numeric_limits::max(); + +static unsigned opt_all = 0xfff; +static unsigned mut_opts = 0; +static bool opt_sort = false; + +const char * argp_program_doc = "List formulas that are similar to but " \ + "simpler than a given formula."; + +static const argp_option options[] = { + {"mutations", 'm', "N", 0, "number of mutations to apply to the " \ + "formulae (default: 1)", -1}, + {"sort", OPT_SORT, 0, 0, "sort the result by formula size", 0}, + {0, 0, 0, 0, "Transformation rules:", 15}, + {"ap-to-const", OPT_AP2CONST, 0, 0, + "atomic propositions are replaced with true/false", 15}, + {"remove-one-ap", OPT_REMOVE_ONE_AP, 0, 0, + "all occurrences of an atomic proposition are replaced with another" \ + "atomic proposition used in the formula", 15}, + {"remove-multop-operands", OPT_REMOVE_MULTOP_OPERANDS, 0, 0, + "remove one operand from multops", 15}, + {"remove-ops", OPT_REMOVE_OPS, 0, 0, + "replace unary/binary operators with one of their operands", + 15}, + {"split-ops", OPT_SPLIT_OPS, 0, 0, + "when an operator can be expressed as a conjunction/disjunction using " \ + "simpler operators, each term of the conjunction/disjunction is a " \ + "mutation. e.g. a <-> b can be written as ((a & b) | (!a & !b)) or as " \ + "((a -> b) & (b -> a)) so those four terms can be a mutation of a <-> b", 0}, + {"rewrite-ops", OPT_REWRITE_OPS, 0, 0, + "rewrite operators that have a semantically simpler form: a U b becomes " \ + "a W b, etc.", 0}, + {"simplify-bounds", OPT_SIMPLIFY_BOUNDS, 0, 0, + "on a bounded unary operator, decrement one of the bounds, or set min to " \ + "0 or max to " \ + "unbounded.", 15}, + {0, 0, 0, 0, "Output options:", 20}, + {"max-output", 'n', "N", 0, "maximum number of mutations to output", 20}, + {0, 0, 0, 0, "Miscellaneous options:", -1}, + {0, 0, 0, 0, 0, 0} +}; + +static const argp_child children[] = { + {&finput_argp, 0, 0, 10}, + {&output_argp, 0, 0, 20}, + {&misc_argp, 0, 0, -1}, + {0, 0, 0, 0} +}; + +static int +to_int(const char *s) +{ + char* endptr; + unsigned res = strtol(s, &endptr, 10); + if (*endptr) + error(2, 0, "failed to parse '%s' as an unsigned integer.", s); + return res; +} + +static unsigned +to_unsigned (const char *s) +{ + char* endptr; + unsigned res = strtoul(s, &endptr, 10); + if (*endptr) + error(2, 0, "failed to parse '%s' as an unsigned integer.", s); + return res; +} + +namespace +{ + using namespace spot::ltl; + + class mutate_processor: + public job_processor + { + public: + int + process_formula(const formula* f, const char *filename = 0, + int linenum = 0) + { + std::vector mutations = + get_mutations(f, mut_opts, opt_sort, max_output, mutation_nb); + + f->destroy(); + std::vector::iterator it; + for (it = mutations.begin(); it != mutations.end(); ++it) + { + output_formula_checked(*it, filename, linenum); + (*it)->destroy(); + } + return 0; + } + }; +} + +int +parse_opt(int key, char* arg, struct argp_state*) +{ + switch (key) + { + case 'm': + mutation_nb = to_unsigned(arg); + break; + case 'n': + max_output = to_int(arg); + break; + case OPT_AP2CONST: + opt_all = 0; + mut_opts |= MUT_AP2CONST; + break; + case OPT_REMOVE_ONE_AP: + opt_all = 0; + mut_opts |= MUT_REMOVE_ONE_AP; + break; + case OPT_REMOVE_MULTOP_OPERANDS: + opt_all = 0; + mut_opts |= MUT_REMOVE_MULTOP_OPERANDS; + break; + case OPT_REMOVE_OPS: + opt_all = 0; + mut_opts |= MUT_REMOVE_OPS; + break; + case OPT_SPLIT_OPS: + opt_all = 0; + mut_opts |= MUT_SPLIT_OPS; + break; + case OPT_REWRITE_OPS: + opt_all = 0; + mut_opts |= MUT_REWRITE_OPS; + break; + case OPT_SIMPLIFY_BOUNDS: + opt_all = 0; + mut_opts |= MUT_SIMPLIFY_BOUNDS; + break; + case OPT_SORT: + opt_sort = true; + break; + default: + return ARGP_ERR_UNKNOWN; + } + return 0; +} + +int +main(int argc, char* argv[]) +{ + setup(argv); + + const argp ap = { options, parse_opt, 0, argp_program_doc, children, 0, 0 }; + + if (int err = argp_parse(&ap, argc, argv, 0, 0, 0)) + exit(err); + + mut_opts |= opt_all; + + if (jobs.empty()) + jobs.push_back(job("-", 1)); + + mutate_processor processor; + if (processor.run()) + return 2; + return 0; +} diff --git a/src/bin/man/Makefile.am b/src/bin/man/Makefile.am index 4ad43e7cd..c77d653ec 100644 --- a/src/bin/man/Makefile.am +++ b/src/bin/man/Makefile.am @@ -29,6 +29,7 @@ dist_man1_MANS = \ ltl2tgta.1 \ ltlcross.1 \ ltlfilt.1 \ + ltlgrind.1 \ randltl.1 dist_man7_MANS = \ spot-x.7 @@ -59,3 +60,6 @@ randltl.1: $(common_dep) $(srcdir)/randltl.x $(srcdir)/../randltl.cc spot-x.7: $(common_dep) $(srcdir)/spot-x.x $(srcdir)/../spot-x.cc $(convman) ../spot-x$(EXEEXT) $(srcdir)/spot-x.x $@ + +ltlgrind.1: $(common_dep) $(srcdir)/ltlgrind.x $(srcdir)/../ltlgrind.cc + $(convman) ../ltlgrind$(EXEEXT) $(srcdir)/ltlgrind.x $@ diff --git a/src/bin/man/ltlgrind.x b/src/bin/man/ltlgrind.x new file mode 100644 index 000000000..ef05873b1 --- /dev/null +++ b/src/bin/man/ltlgrind.x @@ -0,0 +1,6 @@ +[NAME] +ltlgrind \- list mutations of a formula. +[DESCRIPTION] +.\" Add any additional description here +[SEE ALSO] +.BR ltlcross (1), diff --git a/src/ltltest/Makefile.am b/src/ltltest/Makefile.am index e4c17523b..a0331659c 100644 --- a/src/ltltest/Makefile.am +++ b/src/ltltest/Makefile.am @@ -98,6 +98,7 @@ TESTS = \ kind.test \ remove_x.test \ ltlrel.test \ + ltlgrind.test \ ltlfilt.test \ latex.test \ lbt.test \ diff --git a/src/ltltest/ltlgrind.test b/src/ltltest/ltlgrind.test new file mode 100755 index 000000000..58644a5e1 --- /dev/null +++ b/src/ltltest/ltlgrind.test @@ -0,0 +1,166 @@ +#! /bin/sh +# Copyright (C) 2013 Laboratoire de Recherche et Developement to +# l'Epita (LRDE). +# +# 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 . + +. ./defs || exit 1 + +set -e + +checkopt() +{ + cat >exp + run 0 ../../bin/ltlgrind "$@" > out + diff exp out +} + +checkopt -f 'Xp1 U (p4 | (p3 xor (p4 W p0)))' <-> G(d <-> e) xor f' --split-ops \ +<-> (f & !G(d <-> e)) +{{{a | b}}[*4] & {{{a | b}}[*];c}}<>-> (!f & G(d <-> e)) +{{{a | b}}[*4] & {{{a | b}}[*];c}}<>-> (f xor G(d -> e)) +{{{a | b}}[*4] & {{{a | b}}[*];c}}<>-> (f xor G(e -> d)) +{{{a | b}}[*4] & {{{a | b}}[*];c}}<>-> (f xor G(d & e)) +{{{a | b}}[*4] & {{{a | b}}[*];c}}<>-> (f xor G(!d & !e)) +{{{{a | b}}[*];c} && {{{a | b}}[*4];[*]}}<>-> (f xor G(d <-> e)) +{{{a | b}}[*4] && {{{a | b}}[*];c;[*]}}<>-> (f xor G(d <-> e)) +EOF + + +checkopt -f '!(!XXp1 M X(p4 U p2))' --rewrite-ops < 0' --simplify-bounds < 0 +{p2[*3..5] | p3[*6..] | p1[*]}[]-> 0 +{p1[*0..2] | p3[*6..] | p2[*2..5]}[]-> 0 +{p1[*0..2] | p3[*6..] | p2[*0..5]}[]-> 0 +{p1[*0..2] | p3[*6..] | p2[*3..4]}[]-> 0 +{p1[*0..2] | p3[*6..] | p2[*3..]}[]-> 0 +{p1[*0..2] | p2[*3..5] | p3[*5..]}[]-> 0 +{p1[*0..2] | p2[*3..5] | p3[*]}[]-> 0 +EOF + +checkopt -f '!F(!X(Xp1 R p2) -> p4)' --remove-one-ap < p4) +!F(!X(Xp4 R p2) -> p4) +!F(!X(Xp1 R p1) -> p4) +!F(!X(Xp1 R p4) -> p4) +!F(!X(Xp1 R p2) -> p1) +!F(!X(Xp1 R p2) -> p2) +EOF + +checkopt -f '!p4 & (p2 | {{!p1}[*]})' --ap-to-const < p4)' -m 2 < p4 +p4 -> p3 +p3 & p4 +!p4 +!p3 +!p3 & !p4 +1 U p3 +1 U p4 +1 U !p3 +1 U !p4 +1 U (p3 & !p4) +1 U (!p3 & p4) +EOF diff --git a/src/ltlvisit/Makefile.am b/src/ltlvisit/Makefile.am index 1e32b7c3d..e1417b284 100644 --- a/src/ltlvisit/Makefile.am +++ b/src/ltlvisit/Makefile.am @@ -34,6 +34,7 @@ ltlvisit_HEADERS = \ lbt.hh \ length.hh \ lunabbrev.hh \ + mutation.hh \ nenoform.hh \ postfix.hh \ randomltl.hh \ @@ -58,6 +59,7 @@ libltlvisit_la_SOURCES = \ lunabbrev.cc \ mark.cc \ mark.hh \ + mutation.cc \ nenoform.cc \ postfix.cc \ randomltl.cc \ diff --git a/src/ltlvisit/mutation.cc b/src/ltlvisit/mutation.cc new file mode 100644 index 000000000..d5a1e68f6 --- /dev/null +++ b/src/ltlvisit/mutation.cc @@ -0,0 +1,399 @@ +// Copyright (C) 2013 Laboratoire de Recherche et Developpement de +// l'Epita (LRDE). +// +// 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 +#include + +#include "ltlast/allnodes.hh" +#include "ltlvisit/apcollect.hh" +#include "ltlvisit/clone.hh" +#include "ltlvisit/mutation.hh" +#include "ltlvisit/length.hh" +#include "misc/hash.hh" + +#define Implies_(x, y) \ + spot::ltl::binop::instance(spot::ltl::binop::Implies, (x), (y)) +#define And_(x, y) \ + spot::ltl::multop::instance(spot::ltl::multop::And, (x), (y)) +#define AndRat_(x, y) \ + spot::ltl::multop::instance(spot::ltl::multop::AndRat, (x), (y)) +#define AndNLM_(x) \ + spot::ltl::multop::instance(spot::ltl::multop::AndNLM, (x)) +#define Concat_(x, y) \ + spot::ltl::multop::instance(spot::ltl::multop::Concat, (x), (y)) +#define Not_(x) \ + spot::ltl::unop::instance(spot::ltl::unop::Not, (x)) + +namespace spot +{ + namespace ltl + { + namespace + { + class replace_visitor : public clone_visitor + { + public: + void visit(const atomic_prop* ap) + { + if (ap == (*ap1_)) + result_ = (*ap2_)->clone(); + else + result_ = ap->clone(); + } + + const formula* + replace(const formula* f, + atomic_prop_set::iterator ap1, atomic_prop_set::iterator ap2) + { + ap1_ = ap1; + ap2_ = ap2; + return recurse(f); + } + + private: + atomic_prop_set::iterator ap1_; + atomic_prop_set::iterator ap2_; + }; + + typedef std::vector vec; + class mutation_visitor : public clone_visitor + { + public: + mutation_visitor(const formula* f, unsigned opts) : f_(f), opts_(opts) + { + } + + void visit(const atomic_prop* ap) + { + result_ = 0; + if (opts_ & MUT_AP2CONST) + { + if (mutation_counter_-- == 0) + result_ = constant::true_instance(); + if (mutation_counter_-- == 0) + result_ = constant::false_instance(); + } + if (!result_) + result_ = ap->clone(); + } + + void visit(const unop* uo) + { + result_ = 0; + if (opts_ & MUT_REMOVE_OPS) + { + if ((uo->op() == unop::G + || uo->op() == unop::F + || uo->op() == unop::X + || uo->op() == unop::Not) + && mutation_counter_-- == 0) + result_ = uo->child()->clone(); + } + if (!result_) + { + if (mutation_counter_ < 0) + result_ = uo->clone(); + else + { + result_ = unop::instance(uo->op(), recurse(uo->child())); + } + } + } + + void visit(const binop* bo) + { + const formula* first = bo->first(); + const formula* second = bo->second(); + result_ = 0; + + if (opts_ & MUT_REMOVE_OPS && mutation_counter_-- == 0) + result_ = first->clone(); + if (opts_ & MUT_REMOVE_OPS && mutation_counter_-- == 0) + result_ = second->clone(); + if (opts_ & MUT_REWRITE_OPS) + { + switch (bo->op()) + { + case binop::U: + if (mutation_counter_-- == 0) + result_ = binop::instance(binop::W, first->clone(), + second->clone()); + break; + case binop::M: + if (mutation_counter_-- == 0) + result_ = binop::instance(binop::R, first->clone(), + second->clone()); + if (mutation_counter_-- == 0) + result_ = binop::instance(binop::U, second->clone(), + first->clone()); + break; + case binop::R: + if (mutation_counter_-- == 0) + result_ = binop::instance(binop::W, second->clone(), + first->clone()); + break; + default: + break; + } + } + if (opts_ & MUT_SPLIT_OPS) + { + switch (bo->op()) + { + case binop::Equiv: + if (mutation_counter_-- == 0) + result_ = Implies_(first->clone(), second->clone()); + if (mutation_counter_-- == 0) + result_ = Implies_(second->clone(), first->clone()); + if (mutation_counter_-- == 0) + result_ = And_(first->clone(), second->clone()); + if (mutation_counter_-- == 0) + result_ = And_(Not_(first->clone()), + Not_(second->clone())); + break; + case binop::Xor: + if (mutation_counter_-- == 0) + result_ = And_(first->clone(), Not_(second->clone())); + if (mutation_counter_-- == 0) + result_ = And_(Not_(first->clone()), second->clone()); + break; + default: + break; + } + } + if (!result_) + { + if (mutation_counter_ < 0) + result_ = bo->clone(); + else + result_ = + binop::instance(bo->op(), recurse(first), recurse(second)); + } + } + + void visit(const bunop* bu) + { + const formula* c = bu->child()->clone(); + result_ = 0; + + if (opts_ & MUT_REMOVE_OPS && mutation_counter_-- == 0) + result_ = c->clone(); + if (opts_ & MUT_SIMPLIFY_BOUNDS) + { + if (bu->min() > 0 && mutation_counter_-- == 0) + result_ = + bunop::instance(bu->op(), c, bu->min() - 1, bu->max()); + if (bu->min() > 0 && mutation_counter_-- == 0) + result_ = bunop::instance(bu->op(), c, 0, bu->max()); + if (bu->max() != bunop::unbounded && bu->max() > bu->min() + && mutation_counter_-- == 0) + result_ = + bunop::instance(bu->op(), c, bu->min(), bu->max() - 1); + if (bu->max() != bunop::unbounded && mutation_counter_-- == 0) + result_ = bunop::instance(bu->op(), c, bu->min(), + bunop::unbounded); + } + if (!result_) + { + c->destroy(); + if (mutation_counter_ < 0) + result_ = bu->clone(); + else + result_ = bunop::instance(bu->op(), recurse(c), bu->min(), + bu->max()); + } + } + + void visit(const multop* mo) + { + int mos = mo->size(); + int i; + result_ = 0; + + if (opts_ & MUT_REMOVE_MULTOP_OPERANDS) + { + for (i = 0; i < mos; ++i) + if (mutation_counter_-- == 0) + result_ = mo->all_but(i); + } + + if (opts_ & MUT_SPLIT_OPS && mo->op() == multop::AndNLM) + { + if (mutation_counter_ >= 0 && mutation_counter_ < 2 * (mos - 1)) + { + vec* v1 = new vec(); + vec* v2 = new vec(); + v1->push_back(mo->nth(0)->clone()); + bool reverse = false; + i = 1; + while (i < mos) + { + if (mutation_counter_-- == 0) + break; + if (mutation_counter_-- == 0) + { + reverse = true; + break; + } + v1->push_back(mo->nth(i++)->clone()); + } + for (; i < mos; ++i) + v2->push_back(mo->nth(i)->clone()); + const formula* tstar = + bunop::instance(bunop::Star, constant::true_instance(), + 0, + bunop::unbounded); + const formula* first = AndNLM_(v1); + const formula* second = AndNLM_(v2); + if (!reverse) + result_ = AndRat_(Concat_(first, tstar), second); + else + result_ = AndRat_(Concat_(second, tstar), first); + } + else + mutation_counter_ -= 2 * (mos - 1); + } + + if (!result_) + { + if (mutation_counter_ < 0) + result_ = mo->clone(); + else + { + vec* v = new vec(); + for (i = 0; i < mos; ++i) + v->push_back(recurse(mo->nth(i))); + result_ = multop::instance(mo->op(), v); + } + } + } + + const formula* + recurse(const formula* f) + { + f->accept(*this); + return result_; + } + + const formula* + get_mutation(int n) + { + mutation_counter_ = n; + const formula* mut = recurse(f_); + if (mut == f_) + { + mut->destroy(); + return 0; + } + return mut; + } + + private: + const formula* f_; + int mutation_counter_ = 0; + unsigned opts_; + }; + + bool + formula_length_less_than(const formula* left, const formula* right) + { + assert(left); + assert(right); + if (left == right) + return false; + return length(left) < length(right); + } + + typedef std::set fset_t; + + void + single_mutation_rec(const formula* f, fset_t& mutations, unsigned opts, + int& n, unsigned m) + { + if (m == 0) + { + if (mutations.insert(f).second) + { + f->clone(); + --n; + } + } + else + { + const formula* mut(nullptr); + int i = 0; + mutation_visitor mv(f, opts); + while (n > 0 && (mut = mv.get_mutation(i++))) + { + single_mutation_rec(mut, mutations, opts, n, m - 1); + mut->destroy(); + } + } + } + + void + replace_ap_rec(const formula* f, fset_t& mutations, unsigned opts, + int& n, unsigned m) + { + if (m == 0) + { + if (mutations.insert(f).second) + { + f->clone(); + --n; + } + } + else + { + const formula* mut; + replace_visitor rv; + std::unique_ptr aps = + std::unique_ptr(atomic_prop_collect(f)); + atomic_prop_set::iterator ap1; + atomic_prop_set::iterator ap2; + for (ap1 = aps->begin(); ap1 != aps->end() && n > 0; ++ap1) + for (ap2 = aps->begin(); ap2 != aps->end() && n > 0; ++ap2) + { + if (ap1 == ap2) + continue; + mut = rv.replace(f, ap1, ap2); + replace_ap_rec(mut, mutations, opts, n, m - 1); + mut->destroy(); + } + } + } + } + + vec get_mutations(const formula* f, + unsigned opts, + bool sort, + int n, + unsigned m) + { + fset_t mutations; + single_mutation_rec(f, mutations, opts, n, m); + if (opts & MUT_REMOVE_ONE_AP) + replace_ap_rec(f, mutations, opts, n, m); + + vec res(mutations.begin(), mutations.end()); + if (sort) + std::sort(res.begin(), res.end(), formula_length_less_than); + return res; + } + } +} diff --git a/src/ltlvisit/mutation.hh b/src/ltlvisit/mutation.hh new file mode 100644 index 000000000..f27cf4b86 --- /dev/null +++ b/src/ltlvisit/mutation.hh @@ -0,0 +1,52 @@ +// -*- coding: utf-8 -*- +// Copyright (C) 2009, 2010, 2012, 2013, 2014 Laboratoire de Recherche et +// Développement de l'Epita (LRDE). +// Copyright (C) 2003, 2004 Laboratoire d'Informatique de Paris 6 (LIP6), +// département Systèmes Répartis Coopératifs (SRC), Université Pierre +// et Marie Curie. +// +// 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 . + +#ifndef SPOT_LTLVISIT_MUTATION_HH +# define SPOT_LTLVISIT_MUTATION_HH + +#include "ltlast/formula.hh" +#include +#include +#include + +#define MUT_AP2CONST 0x1 +#define MUT_SIMPLIFY_BOUNDS 0x2 +#define MUT_REMOVE_MULTOP_OPERANDS 0x4 +#define MUT_REMOVE_OPS 0x8 +#define MUT_SPLIT_OPS 0x10 +#define MUT_REWRITE_OPS 0x20 +#define MUT_REMOVE_ONE_AP 0x40 + +namespace spot +{ + namespace ltl + { + typedef std::vector vec; + SPOT_API + vec get_mutations(const formula*, + unsigned opts = 0xfff, + bool sort = true, + int n = std::numeric_limits::max(), + unsigned m = 1); + } +} +#endif