autfilt add support for --partial-degeneralize

* bin/autfilt.cc: Add a --partial-degeneralize option.
* NEWS: Mention it.
* spot/twaalgos/degen.cc: Do not restrict partial_degeneralize() to
deterministic automata.
* spot/twaalgos/degen.hh: Adjust documentation.
* tests/core/pdegen.test: New test case.
* tests/Makefile.am: Add it.
* tests/python/pdegen.py: Adjust.
This commit is contained in:
Alexandre Duret-Lutz 2020-02-15 10:07:59 +01:00
parent bf42b19eff
commit b5e464e05a
7 changed files with 94 additions and 45 deletions

9
NEWS
View file

@ -17,6 +17,10 @@ New in spot 2.8.5.dev (not yet released)
- When running translators ltlcross will now display {names} when - When running translators ltlcross will now display {names} when
supplied. supplied.
- autfilt learned the --partial-degeneralize option, to remove
conjunctions of Inf, or disjunction of Fin that appears in
arbitrary conditions.
Library: Library:
- Historically, Spot only supports LTL with infinite semantics - Historically, Spot only supports LTL with infinite semantics
@ -58,9 +62,8 @@ New in spot 2.8.5.dev (not yet released)
parity_min_even(n) = parity_min(false, n) parity_min_even(n) = parity_min(false, n)
- partial_degeneralize() is a new function performing partial - partial_degeneralize() is a new function performing partial
degeneralization to get rid of conjunctions of Inf terms in degeneralization to get rid of conjunctions of Inf terms, or
acceptance conditions. This also works with disjunctions of disjunctions of Fin terms in acceptance conditions.
Fin terms in deterministic automata.
- simplify_acceptance_here() and simplify_acceptance() learned to - simplify_acceptance_here() and simplify_acceptance() learned to
simplify subformulas like Fin(i)&Fin(j) or Inf(i)|Inf(j), or some simplify subformulas like Fin(i)&Fin(j) or Inf(i)|Inf(j), or some

View file

@ -1,5 +1,5 @@
// -*- coding: utf-8 -*- // -*- coding: utf-8 -*-
// Copyright (C) 2013-2019 Laboratoire de Recherche et Développement // Copyright (C) 2013-2020 Laboratoire de Recherche et Développement
// de l'Epita (LRDE). // de l'Epita (LRDE).
// //
// This file is part of Spot, a model checking library. // This file is part of Spot, a model checking library.
@ -133,6 +133,7 @@ enum {
OPT_MASK_ACC, OPT_MASK_ACC,
OPT_MERGE, OPT_MERGE,
OPT_NONDET_STATES, OPT_NONDET_STATES,
OPT_PARTIAL_DEGEN,
OPT_PRODUCT_AND, OPT_PRODUCT_AND,
OPT_PRODUCT_OR, OPT_PRODUCT_OR,
OPT_RANDOMIZE, OPT_RANDOMIZE,
@ -363,6 +364,10 @@ static const argp_option options[] =
{ "sum-and", OPT_SUM_AND, "FILENAME", 0, { "sum-and", OPT_SUM_AND, "FILENAME", 0,
"build the sum with the automaton in FILENAME " "build the sum with the automaton in FILENAME "
"to intersect languages", 0 }, "to intersect languages", 0 },
{ "partial-degeneralize", OPT_PARTIAL_DEGEN, "NUM1,NUM2,...",
OPTION_ARG_OPTIONAL, "Degeneralize automata according to sets "
"NUM1,NUM2,... If no sets are given, partial degeneralization "
"is performed for all conjunctions of Inf and disjunctions of Fin.", 0 },
{ "separate-sets", OPT_SEP_SETS, nullptr, 0, { "separate-sets", OPT_SEP_SETS, nullptr, 0,
"if both Inf(x) and Fin(x) appear in the acceptance condition, replace " "if both Inf(x) and Fin(x) appear in the acceptance condition, replace "
"Fin(x) by a new Fin(y) and adjust the automaton", 0 }, "Fin(x) by a new Fin(y) and adjust the automaton", 0 },
@ -619,6 +624,8 @@ static bool opt_complement = false;
static bool opt_complement_acc = false; static bool opt_complement_acc = false;
static char* opt_decompose_scc = nullptr; static char* opt_decompose_scc = nullptr;
static bool opt_dualize = false; static bool opt_dualize = false;
static bool opt_partial_degen_set = false;
static spot::acc_cond::mark_t opt_partial_degen = {};
static spot::acc_cond::mark_t opt_mask_acc = {}; static spot::acc_cond::mark_t opt_mask_acc = {};
static std::vector<bool> opt_keep_states = {}; static std::vector<bool> opt_keep_states = {};
static unsigned int opt_keep_states_initial = 0; static unsigned int opt_keep_states_initial = 0;
@ -1005,6 +1012,23 @@ parse_opt(int key, char* arg, struct argp_state*)
opt_nondet_states = parse_range(arg, 0, std::numeric_limits<int>::max()); opt_nondet_states = parse_range(arg, 0, std::numeric_limits<int>::max());
opt_nondet_states_set = true; opt_nondet_states_set = true;
break; break;
case OPT_PARTIAL_DEGEN:
{
opt_partial_degen_set = true;
if (arg)
for (auto res : to_longs(arg))
{
if (res < 0)
error(2, 0, "acceptance sets should be non-negative:"
" --partial-degeneralize=%ld", res);
if (static_cast<unsigned long>(res) >=
spot::acc_cond::mark_t::max_accsets())
error(2, 0, "this implementation does not support that many"
" acceptance sets: --partial-degeneralize=%ld", res);
opt_partial_degen.set(res);
}
break;
}
case OPT_PRODUCT_AND: case OPT_PRODUCT_AND:
{ {
auto a = read_automaton(arg, opt->dict); auto a = read_automaton(arg, opt->dict);
@ -1478,6 +1502,19 @@ namespace
else if (opt_rem_unreach) else if (opt_rem_unreach)
aut->purge_unreachable_states(); aut->purge_unreachable_states();
if (opt_partial_degen_set)
{
if (opt_partial_degen)
{
auto sets = opt_partial_degen & aut->acc().all_sets();
aut = spot::partial_degeneralize(aut, sets);
}
else
{
aut = spot::partial_degeneralize(aut);
}
}
if (opt->product_and) if (opt->product_and)
aut = ::product(std::move(aut), opt->product_and); aut = ::product(std::move(aut), opt->product_and);
if (opt->product_or) if (opt->product_or)

View file

@ -750,8 +750,7 @@ namespace spot
namespace namespace
{ {
static acc_cond::mark_t static acc_cond::mark_t
to_strip(const acc_cond::acc_code& code, acc_cond::mark_t todegen, to_strip(const acc_cond::acc_code& code, acc_cond::mark_t todegen)
bool also_fin)
{ {
if (code.empty()) if (code.empty())
return todegen; return todegen;
@ -768,14 +767,6 @@ namespace spot
break; break;
case acc_cond::acc_op::Fin: case acc_cond::acc_op::Fin:
case acc_cond::acc_op::FinNeg: case acc_cond::acc_op::FinNeg:
if (!also_fin)
{
pos -= 2;
tostrip -= code[pos].mark;
break;
}
// Handle Fin and Inf in the same way
SPOT_FALLTHROUGH;
case acc_cond::acc_op::Inf: case acc_cond::acc_op::Inf:
case acc_cond::acc_op::InfNeg: case acc_cond::acc_op::InfNeg:
{ {
@ -796,8 +787,7 @@ namespace spot
update_acc_for_partial_degen(acc_cond::acc_code& code, update_acc_for_partial_degen(acc_cond::acc_code& code,
acc_cond::mark_t todegen, acc_cond::mark_t todegen,
acc_cond::mark_t tostrip, acc_cond::mark_t tostrip,
acc_cond::mark_t accmark, acc_cond::mark_t accmark)
bool also_fin)
{ {
if (!todegen || code.empty()) if (!todegen || code.empty())
{ {
@ -817,14 +807,6 @@ namespace spot
break; break;
case acc_cond::acc_op::Fin: case acc_cond::acc_op::Fin:
case acc_cond::acc_op::FinNeg: case acc_cond::acc_op::FinNeg:
if (!also_fin)
{
pos -= 2;
code[pos].mark = code[pos].mark.strip(tostrip);
break;
}
// Handle Fin and Inf in the same way
SPOT_FALLTHROUGH;
case acc_cond::acc_op::Inf: case acc_cond::acc_op::Inf:
case acc_cond::acc_op::InfNeg: case acc_cond::acc_op::InfNeg:
{ {
@ -869,9 +851,6 @@ namespace spot
if (code.empty()) if (code.empty())
return {}; return {};
if (allow_fin)
allow_fin = is_deterministic(aut);
acc_cond::mark_t res = {}; acc_cond::mark_t res = {};
unsigned res_sz = -1U; unsigned res_sz = -1U;
auto update = [&](const acc_cond::mark_t& m) auto update = [&](const acc_cond::mark_t& m)
@ -926,16 +905,11 @@ namespace spot
res->copy_ap_of(a); res->copy_ap_of(a);
acc_cond::acc_code acc = a->get_acceptance(); acc_cond::acc_code acc = a->get_acceptance();
// We can also degeneralize disjunctions of Fin if the input is acc_cond::mark_t tostrip = to_strip(acc, todegen);
// deterministic.
bool also_fin = a->acc().uses_fin_acceptance() && is_deterministic(a);
acc_cond::mark_t tostrip = to_strip(acc, todegen, also_fin);
acc_cond::mark_t keep = a->acc().all_sets() - tostrip; acc_cond::mark_t keep = a->acc().all_sets() - tostrip;
acc_cond::mark_t degenmark = {keep.count()}; acc_cond::mark_t degenmark = {keep.count()};
if (!update_acc_for_partial_degen(acc, todegen, tostrip, degenmark, if (!update_acc_for_partial_degen(acc, todegen, tostrip, degenmark))
also_fin))
report_invalid_partial_degen_arg(todegen, acc); report_invalid_partial_degen_arg(todegen, acc);
res->set_acceptance(acc); res->set_acceptance(acc);

View file

@ -107,13 +107,12 @@ namespace spot
/// ///
/// Cases where the sets listed in \a todegen also occur outside /// Cases where the sets listed in \a todegen also occur outside
/// of the Inf-conjunction are also supported. Subformulas that /// of the Inf-conjunction are also supported. Subformulas that
/// are disjunctions of Fin(.) terms (e.g., Fin(1)|Fin(2)) can /// are disjunctions of Fin(.) terms (e.g., Fin(1)|Fin(2)) will
/// also be degeneralized if the input automaton is deterministic. /// be degeneralized as well.
/// ///
/// If this functions is called with a value of \a todegen that does /// If this functions is called with a value of \a todegen that does
/// not match a conjunction of Inf(.), or in a deterministic /// not match a conjunction of Inf(.), or a disjunction of Fin(.),
/// automaton a disjunction of Fin(.), an std::runtime_error /// an std::runtime_error exception is thrown.
/// exception is thrown.
/// ///
/// The version of the function that has no \a todegen argument will /// The version of the function that has no \a todegen argument will
/// perform all possible partial degeneralizations, and may return /// perform all possible partial degeneralizations, and may return
@ -132,10 +131,9 @@ namespace spot
/// \brief Is the automaton partially degeneralizable? /// \brief Is the automaton partially degeneralizable?
/// ///
/// Return a mark `M={m₁, m₂, ..., mₙ}` such that either (1) /// Return a mark `M={m₁, m₂, ..., mₙ}` such that either
/// `Inf(m₁)&Inf(m₂)&...&Inf(mₙ)` appears in the acceptance /// `Inf(m₁)&Inf(m₂)&...&Inf(mₙ)` or `Fin(m₁)|Fin(m₂)|...|Fin(mₙ)`
/// condition of \a aut, or (2) \a aut is deterministic and /// appears in the acceptance condition of \a aut.
/// `Inf(m₁)|Inf(m₂)|...|Fin(mₙ)` appear in its conditions.
/// ///
/// If multiple such marks exist the smallest such mark is returned. /// If multiple such marks exist the smallest such mark is returned.
/// (This is important in case of overlapping options. E.g., in the /// (This is important in case of overlapping options. E.g., in the

View file

@ -285,6 +285,7 @@ TESTS_twa = \
core/ltl2dstar4.test \ core/ltl2dstar4.test \
core/ltl2ta.test \ core/ltl2ta.test \
core/ltl2ta2.test \ core/ltl2ta2.test \
core/pdegen.test \
core/randaut.test \ core/randaut.test \
core/randtgba.test \ core/randtgba.test \
core/isomorph.test \ core/isomorph.test \

32
tests/core/pdegen.test Executable file
View file

@ -0,0 +1,32 @@
#!/bin/sh
# -*- coding: utf-8 -*-
# Copyright (C) 2020 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 <http://www.gnu.org/licenses/>.
. ./defs
set -e
randaut -n10 -e.3 -Q7 -A 'generalized-co-Buchi 2..5' 2 |
autcross --verbose --language-preserved \
'autfilt --partial-degen' \
'autfilt --partial-degen=1,2,3'
randaut -n10 -e.3 -Q7 -A 'random 2..5' 2 |
autcross --verbose --language-preserved 'autfilt --partial-degen'

View file

@ -144,10 +144,14 @@ State: 3 "2#0"
assert spot.is_partially_degeneralizable(de) == [] assert spot.is_partially_degeneralizable(de) == []
try:
df = spot.partial_degeneralize(f, [0, 1]) df = spot.partial_degeneralize(f, [0, 1])
df.equivalent_to(f)
assert str(df.acc()) == '(1, Fin(0))'
try:
df = spot.partial_degeneralize(f, [0, 1, 2])
except RuntimeError as e: except RuntimeError as e:
assert 'partial_degeneralize(): {0,1} does not' in str(e) assert 'partial_degeneralize(): {0,1,2} does not' in str(e)
else: else:
raise RuntimeError("missing exception") raise RuntimeError("missing exception")