Commit 0c4b7016 authored by Alexandre Duret-Lutz's avatar Alexandre Duret-Lutz
Browse files

improve acceptance simplifications using useless colors

This fixes issue #418.

* spot/twa/acc.cc,
spot/twa/acc.hh (acc_cond::acc_code::useless_colors_patterns): New
method to detect patterns of colors allowing other colors to be added
or removed at will.
* spot/twaalgos/cleanacc.cc (simplify_acceptance_here): Use the above
patterns to remove some useless colors from transitions and hope that
this can help simplify the acceptance condition.
* spot/twaalgos/degen.cc (propagate_marks_vector): Use the pattern to
add more colors.
* tests/core/ltl2tgba2.test: Add the test case from issue #418.
* tests/core/ltl2dstar4.test, tests/core/satmin3.test,
tests/core/sccdot.test, tests/core/sim3.test,
tests/python/automata.ipynb, tests/python/decompose.ipynb,
tests/python/merge.py, tests/python/pdegen.py, tests/python/remfin.py,
tests/python/toparity.py, tests/python/tra2tba.py: Adjust all test
cases.
* NEWS: Mention this new feature.
parent c341a3ca
Pipeline #20811 passed with stages
in 230 minutes and 28 seconds
......@@ -56,6 +56,13 @@ New in spot 2.9.3.dev (not yet released)
the form Inf(x)&Inf(y) or Fin(x)|Fin(y) when the presence of x
always implies that of y. (Issue #406.)
- simplifiy_acceptance_here() and propagate_marks_here() learned to
use some patterns of the acceptance condition to remove or add
(depending on the function) colors on edges. As an example, with
a condition such as Inf(0) | Fin(1), an edge labeled by {0,1} can
be simplied to {0}, but the oppose rewriting can be useful as
well. (Issue #418.)
Bugs fixed:
- Handle xor and <-> in a more natural way when translating
......
......@@ -1326,6 +1326,81 @@ namespace spot
return rescode;
}
namespace
{
static acc_cond::mark_t
gather_used_colors(const acc_cond::acc_word* pos)
{
acc_cond::mark_t res{};
auto start = pos - pos->sub.size;
do
{
switch (pos->sub.op)
{
case acc_cond::acc_op::And:
case acc_cond::acc_op::Or:
--pos;
break;
case acc_cond::acc_op::Fin:
case acc_cond::acc_op::Inf:
case acc_cond::acc_op::FinNeg:
case acc_cond::acc_op::InfNeg:
res |= pos[-1].mark;
pos -= 2;
break;
}
}
while (pos > start);
return res;
}
}
std::vector<std::pair<acc_cond::mark_t, acc_cond::mark_t>>
acc_cond::acc_code::useless_colors_patterns() const
{
// [({y₁,y₂,...,yₙ},{x₁,x₂,...,xₙ}),...]
std::vector<std::pair<acc_cond::mark_t, acc_cond::mark_t>>
patterns;
acc_cond::mark_t used_once = used_once_sets();
if (!used_once)
return patterns;
auto pos = &back();
auto end = &front();
while (pos > end)
{
switch (pos->sub.op)
{
case acc_cond::acc_op::And:
case acc_cond::acc_op::Or:
{
auto expect = pos->sub.op == acc_cond::acc_op::Or ?
acc_cond::acc_op::Inf : acc_cond::acc_op::Fin;
for (auto p = pos - 1, pe = pos - pos->sub.size;
p >= pe; p -= p->sub.size + 1)
if (p->sub.op == expect)
{
acc_cond::mark_t rem{};
for (auto q = pos - 1; q >= pe; q -= q->sub.size + 1)
if (p != q)
rem |= gather_used_colors(q);
rem &= used_once;
if (rem)
patterns.emplace_back(p[-1].mark, rem);
}
--pos;
break;
}
case acc_cond::acc_op::Fin:
case acc_cond::acc_op::Inf:
case acc_cond::acc_op::FinNeg:
case acc_cond::acc_op::InfNeg:
pos -= 2;
break;
}
}
return patterns;
}
bool
acc_cond::acc_code::is_parity_max_equiv(std::vector<int>& permut,
unsigned new_color,
......
......@@ -1330,6 +1330,20 @@ namespace spot
/// \brief Return the set of sets appearing in the condition.
acc_cond::mark_t used_sets() const;
/// \brief Find patterns of useless colors.
///
/// If any subformula of the acceptance condition looks like
/// (Inf(y₁)&Inf(y₂)&...&Inf(yₙ)) | f(x₁,...,xₙ)
/// or (Fin(y₁)|Fin(y₂)|...|Fin(yₙ)) & f(x₁,...,xₙ)
/// then for each transition with all colors {y₁,y₂,...,yₙ} we
/// can add or remove all the xᵢ that are used only once in
/// the formula.
///
/// This method returns a vector of pairs:
/// [({y₁,y₂,...,yₙ},{x₁,x₂,...,xₙ}),...]
std::vector<std::pair<acc_cond::mark_t, acc_cond::mark_t>>
useless_colors_patterns() const;
/// \brief Return the sets that appears only once in the
/// acceptance.
///
......
......@@ -617,6 +617,33 @@ namespace spot
}
return aut;
}
// If any subformula of the acceptance condition looks like
// (Inf(y₁)&Inf(y₂)&...&Inf(yₙ)) | f(x₁,...,xₙ)
// then for each transition with all colors {y₁,y₂,...,yₙ} we
// can remove all the xᵢ that are used only once in the formula.
//
// See also issue #418.
static bool
remove_useless_colors_from_edges_here(twa_graph_ptr aut)
{
// [({y₁,y₂,...,yₙ},{x₁,x₂,...,xₙ}),...]
auto patterns = aut->get_acceptance().useless_colors_patterns();
if (patterns.empty())
return false;
bool changed = false;
for (auto& e: aut->edges())
for (auto& p: patterns)
if (p.first.subset(e.acc))
if (auto nacc = e.acc - p.second; nacc != e.acc)
{
e.acc = nacc;
changed = true;
}
return changed;
}
}
......@@ -632,7 +659,8 @@ namespace spot
aut->set_acceptance(aut->acc().unit_propagation());
simplify_complementary_marks_here(aut);
fuse_marks_here(aut);
if (old == aut->get_acceptance())
bool changed = remove_useless_colors_from_edges_here(aut);
if (!changed && old == aut->get_acceptance())
break;
}
cleanup_acceptance_here(aut, true);
......
......@@ -1108,13 +1108,23 @@ namespace spot
else
si = new scc_info(aut);
unsigned ns = aut->num_states();
acc_cond::mark_t allm = aut->acc().all_sets();
unsigned es = aut->edge_vector().size();
auto patterns = aut->get_acceptance().useless_colors_patterns();
std::vector<acc_cond::mark_t> marks(es, acc_cond::mark_t{});
const auto& edges = aut->edge_vector();
for (unsigned e = 1; e < es; ++e)
marks[e] = edges[e].acc;
{
acc_cond::mark_t m = edges[e].acc;
// Add as many useless colors as possible.
for (auto& p: patterns)
if (p.first.subset(m))
m |= p.second;
marks[e] = m;
}
std::vector<acc_cond::mark_t> common_in(ns, allm);
std::vector<acc_cond::mark_t> common_out(ns, allm);
......
......@@ -34,7 +34,7 @@ ltlfilt -f '(GFa -> GFb) & (GFc -> GFd)' -l |
ltl2dstar --ltl2nba=spin:ltl2tgba@-s $STR - - |
autfilt --tgba --stats '%S %E %A %s %e %t %a %d' |
tee out
test "`cat out`" = '9 144 4 18 98 202 2 0'
test "`cat out`" = '9 144 4 16 92 186 2 0'
ltlfilt -f '(GFa -> GFb) & (GFc -> GFd)' -l |
ltl2dstar --ltl2nba=spin:ltl2tgba@-s $STR - - |
......
......@@ -471,4 +471,6 @@ test "4,1" = `ltl2tgba -D -x wdba-minimize=2 "$f" --stats=%s,%d`
test "4,0" = `ltl2tgba -D -x wdba-minimize=0 "$f" --stats=%s,%d`
test "4,1" = `ltl2tgba -D --med "$f" --stats=%s,%d`
:
# Issue #418.
f='(G!a | G!b | G!c) & (FG!a2 | GFb2 | GFc2) & (GFc2 | FG!b2 | GFa2)'
test 28 = `ltl2tgba -D -G -S --stats=%s "$f"`
#!/bin/sh
# -*- coding: utf-8 -*-
# Copyright (C) 2017, 2019 Laboratoire de Recherche et Développement
# Copyright (C) 2017, 2019, 2020 Laboratoire de Recherche et Développement
# de l'Epita (LRDE).
#
# This file is part of Spot, a model checking library.
......@@ -24,37 +24,36 @@ set -e
# Make sure the SPOT_SATSOLVER envar works.
# DRA produced by ltl2dstar for GFp0 -> GFp1, but manually modified
# so that simulation-based reduction do not reduce it to 1 state right away.
# DRA for GFp0 -> GFp1 produced using the SAT-based synthesis (i.e.,
# "minimization with fixed number of states"). We used to take the
# output of ltl2dstar, unfortunately our preprocessing reduced that to
# one state right away, even after some manual touches.
cat >test.hoa <<EOF
HOA: v1
States: 4
properties: implicit-labels trans-labels no-univ-branch deterministic complete
acc-name: Rabin 2
Acceptance: 4 (Fin(0)&Inf(1))|(Fin(2)&Inf(3))
Start: 0
AP: 2 "p0" "p1"
acc-name: Rabin 2
Acceptance: 4 (Fin(0) & Inf(1)) | (Fin(2) & Inf(3))
properties: trans-labels explicit-labels state-acc complete
properties: deterministic
--BODY--
State: 0 {0}
1 {1} /* manual addition */
0
3
2
State: 1 {1}
1
0
3
2
State: 2 {0 3}
1
0
3
2
State: 3 {1 3}
1
0
3
2
State: 0 {1 2}
[!0&!1] 1
[!0&1] 2
[0] 3
State: 1 {0}
[0&!1] 1
[!0&!1] 2
[1] 3
State: 2 {1}
[0&!1] 1
[!0&!1] 2
[1] 3
State: 3 {0 3}
[0 | 1] 2
[!0&!1] 3
--END--
EOF
......@@ -69,7 +68,7 @@ grep 'autfilt: SPOT_SATSOLVER should use %O' err
SPOT_SATSOLVER='false %I %O' \
autfilt --sat-minimize test.hoa --stats=%s >output
test `cat output` = 4
test `cat output` = 3
SPOT_SATSOLVER='this-does-not-exist %I %O' \
autfilt --sat-minimize test.hoa --stats=%s 2>err && exit
......
......@@ -156,7 +156,7 @@ HOA: v1
States: 8
Start: 0
AP: 2 "a" "b"
Acceptance: 3 (Inf(0)&Inf(1)) & Fin(2)
Acceptance: 2 Inf(0) & Fin(1)
properties: trans-labels explicit-labels trans-acc
--BODY--
State: 0
......@@ -165,24 +165,24 @@ State: 0
State: 1
[1] 4
State: 2
[0&1] 0 {0 1}
[0&1] 0 {0}
State: 3
[1] 1
[!1] 3 {2}
[!1] 3 {1}
State: 4
[!0&1] 4 {0 1}
[0&1] 4 {0 2}
[!0&1] 4 {0}
[0&1] 4 {1}
[t] 5
State: 5
[0&1] 5 {0 1}
[!0&1] 5 {0 2}
[0&1] 5 {0}
[!0&1] 5 {1}
[t] 6
State: 6
[!0&1] 6 {0 2}
[0&1] 7 {0 1}
[!0&1] 6 {1}
[0&1] 7 {0}
State: 7
[!0&1] 6 {0 1}
[0&1] 7 {0 2}
[!0&1] 6 {0}
[0&1] 7 {1}
--END--
EOF
diff expected.hoa out.hoa
......
#! /bin/sh
# -*- coding: utf-8 -*-
# Copyright (C) 2015, 2018, 2019 Laboratoire de Recherche et Développement
# Copyright (C) 2015, 2018, 2019, 2020 Laboratoire de Recherche et Développement
# de l'Epita (LRDE).
#
# This file is part of Spot, a model checking library.
......@@ -52,7 +52,7 @@ test "`autfilt --small input --stats=%S,%s`" = 7,1
autfilt -S --high --small input -H > out
cat >expected <<EOF
HOA: v1
States: 5
States: 4
Start: 0
AP: 2 "b" "a"
acc-name: Streett 1
......@@ -61,28 +61,19 @@ properties: trans-labels explicit-labels state-acc complete
properties: deterministic
--BODY--
State: 0
[!1] 1
[1] 3
[t] 1
State: 1 {1}
[0&!1] 1
[0] 1
[!0&!1] 2
[0&1] 3
[!0&1] 4
[!0&1] 3
State: 2
[0&!1] 1
[!0&!1] 2
[0&1] 3
[!0&1] 4
State: 3 {0 1}
[0&!1] 1
[0] 1
[!0&!1] 2
[0&1] 3
[!0&1] 4
State: 4 {0}
[0&!1] 1
[!0&1] 3
State: 3 {0}
[0] 1
[!0&!1] 2
[0&1] 3
[!0&1] 4
[!0&1] 3
--END--
EOF
diff out expected
......
This diff is collapsed.
This diff is collapsed.
......@@ -36,28 +36,28 @@ State: 1
State: 2
[1] 0 {2 3}
--END--""")
spot.simplify_acceptance_here(aut)
hoa = aut.to_str('hoa')
out = spot.simplify_acceptance(aut)
hoa = out.to_str('hoa')
assert hoa == """HOA: v1
States: 3
Start: 0
AP: 2 "a" "b"
acc-name: parity min even 2
Acceptance: 2 Inf(0) | Fin(1)
acc-name: Buchi
Acceptance: 1 Inf(0)
properties: trans-labels explicit-labels trans-acc
--BODY--
State: 0
[0] 1 {0}
State: 1
[0] 1 {1}
[1] 2 {0 1}
[0] 1
[1] 2 {0}
State: 2
[1] 0 {1}
[1] 0
--END--"""
assert spot.are_equivalent(out, aut)
aut = spot.automaton("""
HOA: v1
aut = spot.automaton("""HOA: v1
States: 3
Start: 0
AP: 2 "a" "b"
......@@ -79,17 +79,17 @@ assert hoa == """HOA: v1
States: 3
Start: 0
AP: 2 "a" "b"
acc-name: parity min even 2
Acceptance: 2 Inf(0) | Fin(1)
acc-name: Buchi
Acceptance: 1 Inf(0)
properties: trans-labels explicit-labels trans-acc
--BODY--
State: 0
[0] 1 {0}
State: 1
[0] 1 {1}
[1] 2 {0 1}
[0] 1
[1] 2 {0}
State: 2
[1] 0 {1}
[1] 0
--END--"""
aut = spot.automaton("""
......@@ -128,8 +128,7 @@ State: 2
[1] 0
--END--"""
aut = spot.automaton("""
HOA: v1
aut = spot.automaton("""HOA: v1
States: 3
Start: 0
AP: 2 "a" "b"
......@@ -158,10 +157,10 @@ properties: trans-labels explicit-labels trans-acc
State: 0
[0] 1
State: 1
[0] 1 {0 1}
[0] 1 {0}
[1] 2 {1}
State: 2
[1] 0 {0 1}
[1] 0 {0}
--END--"""
aut = spot.automaton("""
......@@ -355,7 +354,7 @@ State: 1
[0&!1] 0
[!0&1] 3
[0&1] 2
State: 2 {0 1}
State: 2 {1}
[!0&!1] 1
[0&!1] 0
[!0&1] 3
......@@ -386,27 +385,29 @@ State: 2
[0] 2 {0 1 2}
[!0] 1 {0}
--END--""")
spot.simplify_acceptance_here(aut)
hoa = aut.to_str('hoa')
out = spot.simplify_acceptance(aut)
hoa = out.to_str('hoa')
assert hoa == """HOA: v1
States: 3
Start: 0
AP: 2 "p0" "p1"
Acceptance: 3 (Fin(0) | Inf(1)) & Fin(2)
acc-name: co-Buchi
Acceptance: 1 Fin(0)
properties: trans-labels explicit-labels trans-acc complete
properties: deterministic
--BODY--
State: 0
[0] 1
[!0] 0 {2}
[!0] 0 {0}
State: 1
[0] 1 {1 2}
[0] 1 {0}
[!0] 2
State: 2
[0] 2 {0 1 2}
[0] 2 {0}
[!0] 1 {0}
--END--"""
assert spot.are_equivalent(out, aut)
aut = spot.automaton("""HOA: v1
States: 4
......@@ -448,14 +449,14 @@ State: 0
[!0&!1] 0 {0}
[0] 3
State: 1
[0] 0 {1 2 3}
[0] 0 {1 3}
[!0] 3 {0 2}
State: 2
[t] 1 {1 2}
State: 3
[0&1] 0 {1}
[0&!1] 3 {1 2}
[!0] 1 {2 3}
[!0] 1 {3}
--END--"""
aut = spot.automaton("""HOA: v1
......@@ -668,24 +669,26 @@ State: 2 {1 2 3}
[t] 1
--END--
""")
spot.simplify_acceptance_here(aut)
hoa = aut.to_str('hoa')
out = spot.simplify_acceptance(aut)
hoa = out.to_str('hoa')
assert hoa == """HOA: v1
States: 3
Start: 0
AP: 2 "p0" "p1"
Acceptance: 3 (Fin(0) | Inf(1)) & (Fin(1) | Inf(2))
acc-name: generalized-Buchi 2
Acceptance: 2 Inf(0)&Inf(1)
properties: trans-labels explicit-labels state-acc complete
properties: deterministic
--BODY--
State: 0 {0 1}
State: 0 {0}
[t] 2
State: 1 {0 2}
State: 1 {1}
[t] 2
State: 2 {1 2}
State: 2 {0 1}
[t] 1
--END--"""
assert spot.are_equivalent(out, aut)
aut = spot.automaton("""HOA: v1
States: 2
......
......@@ -322,7 +322,7 @@ assert aut12c.num_states() == 9
aut12d = spot.partial_degeneralize(aut12, [0,1,3])
aut12e = spot.partial_degeneralize(aut12d, [0,1])
assert aut12e.equivalent_to(aut12)
assert aut12e.num_states() == 11
assert aut12e.num_states() == 9
aut12f = spot.partial_degeneralize(aut12)
assert aut12f.equivalent_to(aut12)
......
......@@ -92,4 +92,4 @@ State: 2
""")
b = spot.remove_fin(a)
size = (b.num_states(), b.num_edges())
assert size == (5, 15);
assert size == (5, 13);
......@@ -200,7 +200,7 @@ State: 13
[0&1] 5
[!0&!1] 10 {0 1 3 5}
[0&!1] 13 {1 3}
--END--"""), [35, 28, 23, 30, 29, 25, 22])
--END--"""), [35, 30, 23, 32, 31, 28, 22])
test(spot.automaton("""
HOA: v1
......@@ -259,7 +259,7 @@ State: 3
[!0&1] 2 {1 4}
[0&1] 3 {0}
--END--
"""), [80, 22, 80, 80, 80, 17, 10])
"""), [80, 47, 104, 104, 102, 29, 6])
test(spot.automaton("""
HOA: v1
......@@ -315,7 +315,7 @@ State: 1
[0&!1] 1 {2 3}
[0&1] 1 {1 2 4}
--END--
"""), [11, 6, 3, 7, 7, 4, 3])
"""), [11, 3, 2, 3, 3, 3, 2])
# Tests both the old and new version of to_parity
......@@ -346,7 +346,7 @@ explicit-labels trans-acc --BODY-- State: 0 [0&1] 2 {4 5} [0&1] 4 {0 4}
p = spot.to_parity_old(a, True)
assert p.num_states() == 22
assert spot.are_equivalent(a, p)
test(a, [8, 7, 8, 8, 6, 7, 6])
test(a, [8, 6, 6, 6, 6, 6, 6])
# Force a few edges to false, to make sure to_parity() is OK with that.
for e in a.out(2):
......@@ -360,7 +360,7 @@ for e in a.out(3):
p = spot.to_parity_old(a, True)
assert p.num_states() == 22