introduce delay_branching_here

This is motivated by an example sent by Edmond Irani Liu,
that will be tested in next patch.

* spot/twaalgos/dbranch.cc, spot/twaalgos/dbranch.hh: New files.
* python/spot/impl.i, spot/twaalgos/Makefile.am: Add them.
* spot/twaalgos/translate.cc: Call delay_branching_here
unconditionally.
* spot/twa/twagraph.cc (defrag_states): Do not assume
that games are alternating.
* tests/core/genltl.test: Adjust expected numbers.
* tests/python/dbranch.py: New file.
* tests/Makefile.am: Add it.
This commit is contained in:
Alexandre Duret-Lutz 2022-09-21 15:40:12 +02:00
parent aa7992c65f
commit 3efab05cf2
10 changed files with 378 additions and 24 deletions

9
NEWS
View file

@ -142,6 +142,15 @@ New in spot 2.10.6.dev (not yet released)
succesors, should be called before running simulation-based succesors, should be called before running simulation-based
reductions. reductions.
- A new function delay_branching_here(aut) can be used to simplify
some non-deterministic branching. If two transitions (q₁,,M,q₂)
and (q₁,,M,q₃) differ only by their destination state, and are
the only incoming transitions of their destination states, then q₂
and q₃ can be merged (taking the union of their outgoing
transitions). This is cheap function is automatically called by
spot::translate() after translation of a formula to GBA, before
further simplification.
- spot::parallel_policy is an object that can be passed to some - spot::parallel_policy is an object that can be passed to some
algorithm to specify how many threads can be used if Spot has been algorithm to specify how many threads can be used if Spot has been
compiled with --enable-pthread. Currently, only compiled with --enable-pthread. Currently, only

View file

@ -114,13 +114,14 @@
#include <spot/twaalgos/aiger.hh> #include <spot/twaalgos/aiger.hh>
#include <spot/twaalgos/alternation.hh> #include <spot/twaalgos/alternation.hh>
#include <spot/twaalgos/cleanacc.hh> #include <spot/twaalgos/cleanacc.hh>
#include <spot/twaalgos/degen.hh>
#include <spot/twaalgos/dot.hh>
#include <spot/twaalgos/dualize.hh>
#include <spot/twaalgos/cobuchi.hh> #include <spot/twaalgos/cobuchi.hh>
#include <spot/twaalgos/copy.hh> #include <spot/twaalgos/copy.hh>
#include <spot/twaalgos/complete.hh> #include <spot/twaalgos/complete.hh>
#include <spot/twaalgos/complement.hh> #include <spot/twaalgos/complement.hh>
#include <spot/twaalgos/dbranch.hh>
#include <spot/twaalgos/degen.hh>
#include <spot/twaalgos/dot.hh>
#include <spot/twaalgos/dualize.hh>
#include <spot/twaalgos/emptiness.hh> #include <spot/twaalgos/emptiness.hh>
#include <spot/twaalgos/gtec/gtec.hh> #include <spot/twaalgos/gtec/gtec.hh>
#include <spot/twaalgos/genem.hh> #include <spot/twaalgos/genem.hh>
@ -678,11 +679,14 @@ def state_is_accepting(self, src) -> "bool":
%include <spot/twaalgos/aiger.hh> %include <spot/twaalgos/aiger.hh>
%include <spot/twaalgos/alternation.hh> %include <spot/twaalgos/alternation.hh>
%include <spot/twaalgos/cleanacc.hh> %include <spot/twaalgos/cleanacc.hh>
%include <spot/twaalgos/degen.hh>
%include <spot/twaalgos/dot.hh>
%include <spot/twaalgos/cobuchi.hh> %include <spot/twaalgos/cobuchi.hh>
%include <spot/twaalgos/copy.hh> %include <spot/twaalgos/copy.hh>
%include <spot/twaalgos/complete.hh> %include <spot/twaalgos/complete.hh>
%include <spot/twaalgos/dbranch.hh>
%include <spot/twaalgos/degen.hh>
%include <spot/twaalgos/determinize.hh>
%include <spot/twaalgos/dot.hh>
%include <spot/twaalgos/dualize.hh>
%feature("flatnested") spot::twa_run::step; %feature("flatnested") spot::twa_run::step;
%include <spot/twaalgos/emptiness.hh> %include <spot/twaalgos/emptiness.hh>
%template(list_step) std::list<spot::twa_run::step>; %template(list_step) std::list<spot::twa_run::step>;
@ -694,8 +698,6 @@ def state_is_accepting(self, src) -> "bool":
%include <spot/twaalgos/gfguarantee.hh> %include <spot/twaalgos/gfguarantee.hh>
%include <spot/twaalgos/compsusp.hh> %include <spot/twaalgos/compsusp.hh>
%include <spot/twaalgos/contains.hh> %include <spot/twaalgos/contains.hh>
%include <spot/twaalgos/determinize.hh>
%include <spot/twaalgos/dualize.hh>
%include <spot/twaalgos/langmap.hh> %include <spot/twaalgos/langmap.hh>
%include <spot/twaalgos/magic.hh> %include <spot/twaalgos/magic.hh>
%include <spot/twaalgos/minimize.hh> %include <spot/twaalgos/minimize.hh>

View file

@ -1323,18 +1323,6 @@ namespace spot
} }
init_number_ = newst[init_number_]; init_number_ = newst[init_number_];
g_.defrag_states(newst, used_states); g_.defrag_states(newst, used_states);
// Make sure we did not mess up the structure
assert([&]()
{
if (auto sp = get_named_prop<std::vector<bool>>("state-player"))
{
for (const auto& e : edges())
if (sp->at(e.src) == sp->at(e.dst))
return false;
return true;
}
return true;
}() && "Game not alternating!");
} }
void twa_graph::remove_unused_ap() void twa_graph::remove_unused_ap()

View file

@ -1,5 +1,5 @@
## -*- coding: utf-8 -*- ## -*- coding: utf-8 -*-
## Copyright (C) 2008-2018, 2020-2021 Laboratoire de Recherche et ## Copyright (C) 2008-2018, 2020-2022 Laboratoire de Recherche et
## Développement de l'Epita (LRDE). ## Développement de l'Epita (LRDE).
## Copyright (C) 2003-2005 Laboratoire d'Informatique de Paris 6 ## Copyright (C) 2003-2005 Laboratoire d'Informatique de Paris 6
## (LIP6), département Systèmes Répartis Coopératifs (SRC), Université ## (LIP6), département Systèmes Répartis Coopératifs (SRC), Université
@ -42,6 +42,7 @@ twaalgos_HEADERS = \
contains.hh \ contains.hh \
copy.hh \ copy.hh \
cycles.hh \ cycles.hh \
dbranch.hh \
degen.hh \ degen.hh \
determinize.hh \ determinize.hh \
dot.hh \ dot.hh \
@ -115,6 +116,7 @@ libtwaalgos_la_SOURCES = \
compsusp.cc \ compsusp.cc \
contains.cc \ contains.cc \
cycles.cc \ cycles.cc \
dbranch.cc \
degen.cc \ degen.cc \
determinize.cc \ determinize.cc \
dot.cc \ dot.cc \

163
spot/twaalgos/dbranch.cc Normal file
View file

@ -0,0 +1,163 @@
// -*- coding: utf-8 -*-
// Copyright (C) 2022 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/>.
#include "config.h"
#include <spot/twaalgos/dbranch.hh>
#include <spot/misc/bddlt.hh>
#include <spot/priv/robin_hood.hh>
#include <unordered_map>
#include <utility>
#include <stack>
namespace spot
{
namespace
{
typedef std::pair<bdd, acc_cond::mark_t> bdd_color;
struct bdd_color_hash
{
size_t
operator()(const bdd_color& bc) const noexcept
{
return bc.first.id() ^ bc.second.hash();
}
};
template<bool is_game>
bool delay_branching_aux(const twa_graph_ptr& aut, std::vector<bool>* owner)
{
unsigned ns = aut->num_states();
// number of predecessors of each state
std::vector<unsigned> pred_count(ns, 0);
unsigned init = aut->get_init_state_number();
pred_count[init] = 2; // pretend the initial state has too many
// predecessors, so it does not get fused.
// for each state, number of successors that have a single predecessors
std::vector<unsigned> succ_cand(ns, 0);
for (auto& e: aut->edges())
for (unsigned d: aut->univ_dests(e))
{
// Note that e.dst might be a destination group in
// alternating automata.
unsigned pc = ++pred_count[d];
succ_cand[e.src] += (pc == 1) - (pc == 2);
}
bool changed = false;
typedef robin_hood::unordered_map<bdd_color, unsigned,
bdd_color_hash> hashmap_t;
hashmap_t first_dest[1 + is_game];
auto& g = aut->get_graph();
// setup a DFS
std::vector<bool> seen(ns);
std::stack<unsigned> todo;
auto push_state = [&](unsigned state)
{
todo.push(state);
seen[state] = true;
};
push_state(init);
while (!todo.empty())
{
unsigned src = todo.top();
todo.pop();
if (succ_cand[src] < 2) // nothing to merge
{
for (auto& e: aut->out(src))
for (unsigned d: aut->univ_dests(e))
if (!seen[d])
push_state(d);
continue;
}
first_dest[0].clear();
if constexpr (is_game)
first_dest[1].clear();
auto it = g.out_iteraser(src);
while (it)
{
unsigned canddst = it->dst;
for (unsigned d: aut->univ_dests(canddst))
if (!seen[d])
push_state(d);
if (aut->is_univ_dest(canddst) || pred_count[canddst] != 1)
{
++it;
continue;
}
if (it->cond == bddfalse)
{
it.erase();
continue;
}
unsigned mapidx = is_game ? (*owner)[canddst] : 0;
auto [it2, inserted] =
first_dest[mapidx].emplace(bdd_color{it->cond, it->acc},
canddst);
if (inserted)
{
++it;
continue;
}
unsigned mergedst = it2->second;
// we have to merge canddst into mergedst. This is as
// simple as:
// 1) connecting their list of transitions
unsigned& mergedfirst = g.state_storage(mergedst).succ;
unsigned& mergedlast = g.state_storage(mergedst).succ_tail;
unsigned& candfirst = g.state_storage(canddst).succ;
unsigned& candlast = g.state_storage(canddst).succ_tail;
if (mergedlast)
aut->edge_storage(mergedlast).next_succ = candfirst;
else // mergedst had now successor
mergedfirst = candfirst;
mergedlast = candlast;
// 2) updating the source of the merged transitions
for (unsigned e2 = candfirst; e2 != 0;)
{
auto& edge = aut->edge_storage(e2);
edge.src = mergedst;
e2 = edge.next_succ;
}
// 3) deleting the edge to canddst.
candfirst = candlast = 0;
it.erase();
// 4) updating succ_cand
succ_cand[mergedst] += succ_cand[canddst];
succ_cand[canddst] = 0;
changed = true;
}
}
return changed;
}
}
bool delay_branching_here(const twa_graph_ptr& aut)
{
if (aut->prop_universal())
return false;
auto owner = aut->get_named_prop<std::vector<bool>>("state-player");
if (SPOT_UNLIKELY(owner))
return delay_branching_aux<true>(aut, owner);
else
return delay_branching_aux<false>(aut, nullptr);
}
}

36
spot/twaalgos/dbranch.hh Normal file
View file

@ -0,0 +1,36 @@
// -*- coding: utf-8 -*-
// Copyright (C) 2022 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/>.
#pragma once
#include <spot/twa/twagraph.hh>
namespace spot
{
/// \ingroup twa_algorithms
/// \brief Merge states to delay
///
/// If a state (x) has two outgoing transitions (x,l,m,y) and
/// (x,l,m,z) going to states (x) and (y) that have no other
/// incoming edges, then (y) and (z) can be merged (keeping the
/// union of their outgoing destinations).
///
/// \return true iff the automaton was modified.
SPOT_API bool delay_branching_here(const twa_graph_ptr& aut);
}

View file

@ -1,5 +1,5 @@
// -*- coding: utf-8 -*- // -*- coding: utf-8 -*-
// Copyright (C) 2013-2018, 2020-2021 Laboratoire de Recherche et // Copyright (C) 2013-2018, 2020-2022 Laboratoire de Recherche et
// Développement de l'Epita (LRDE). // Développement de l'Epita (LRDE).
// //
// This file is part of Spot, a model checking library. // This file is part of Spot, a model checking library.
@ -29,6 +29,7 @@
#include <spot/twaalgos/product.hh> #include <spot/twaalgos/product.hh>
#include <spot/twaalgos/sccinfo.hh> #include <spot/twaalgos/sccinfo.hh>
#include <spot/twaalgos/hoa.hh> #include <spot/twaalgos/hoa.hh>
#include <spot/twaalgos/dbranch.hh>
namespace spot namespace spot
{ {
@ -401,6 +402,11 @@ namespace spot
aut = ltl_to_tgba_fm(r, simpl_->get_dict(), exprop, aut = ltl_to_tgba_fm(r, simpl_->get_dict(), exprop,
true, false, false, nullptr, nullptr, true, false, false, nullptr, nullptr,
unambiguous); unambiguous);
if (delay_branching_here(aut))
{
aut->purge_unreachable_states();
aut->merge_edges();
}
} }
aut = this->postprocessor::run(aut, r); aut = this->postprocessor::run(aut, r);

View file

@ -405,6 +405,7 @@ TESTS_python = \
python/bddnqueen.py \ python/bddnqueen.py \
python/bugdet.py \ python/bugdet.py \
python/complement_semidet.py \ python/complement_semidet.py \
python/dbranch.py \
python/declenv.py \ python/declenv.py \
python/decompose_scc.py \ python/decompose_scc.py \
python/det.py \ python/det.py \

View file

@ -1,6 +1,6 @@
#!/bin/sh #!/bin/sh
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright (C) 2016-2021 Laboratoire de Recherche et Développement # Copyright (C) 2016-2022 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.
@ -134,8 +134,8 @@ genltl --kr-n2=1..2 --kr-nlogn=1..2 --kr-n=1..2 --gxf-and=0..3 --fxg-or=0..3 \
--pps-arbiter-standard=2..3 --pps-arbiter-strict=2..3 --format=%F=%L,%f | --pps-arbiter-standard=2..3 --pps-arbiter-strict=2..3 --format=%F=%L,%f |
ltl2tgba --low --det -F-/2 --stats='%<,%s' > out ltl2tgba --low --det -F-/2 --stats='%<,%s' > out
cat >exp<<EOF cat >exp<<EOF
kv-psi=1,10 kv-psi=1,9
kv-psi=2,24 kv-psi=2,19
kr-nlogn=1,16 kr-nlogn=1,16
kr-nlogn=2,44 kr-nlogn=2,44
kr-n=1,10 kr-n=1,10

147
tests/python/dbranch.py Normal file
View file

@ -0,0 +1,147 @@
# -*- mode: python; coding: utf-8 -*-
# Copyright (C) 2022 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/>.
# Test that the spot.gen package works, in particular, we want
# to make sure that the objects created from spot.gen methods
# are usable with methods from the spot package.
import spot
from unittest import TestCase
tc = TestCase()
aut5 = spot.automaton("""HOA: v1 States: 28 Start: 0 AP: 4 "alive" "b"
"a" "c" acc-name: Buchi Acceptance: 1 Inf(0) properties: trans-labels
explicit-labels state-acc very-weak --BODY-- State: 0 [0] 1 [0] 2 [0]
3 [0] 4 [0] 5 [0&!1] 6 [0] 7 State: 1 [0] 8 State: 2 [!0] 9 [0] 10
State: 3 [!0] 9 [0] 11 State: 4 [!0] 9 [0] 12 State: 5 [!0] 9 [0] 13
State: 6 [!0] 9 [0&!1] 14 State: 7 [!0] 9 [0&!1&!2] 14 State: 8 [0] 15
State: 9 {0} [!0] 9 State: 10 [!0] 9 [0] 16 State: 11 [!0] 9 [0] 17
State: 12 [!0] 9 [0] 18 State: 13 [!0] 9 [0&!1&!2] 19 State: 14 [!0] 9
[0&!1] 19 State: 15 [0] 20 State: 16 [!0] 9 [0] 21 State: 17 [!0] 9
[0] 22 State: 18 [!0] 9 [0&!1&!2] 23 State: 19 [!0] 9 [0&!1] 23 State:
20 [0] 24 State: 21 [!0] 9 [0] 25 State: 22 [!0] 9 [0&!1&!2] 26 State:
23 [!0] 9 [0&!1] 26 State: 24 [0&3] 27 State: 25 [!0] 9 [0&!1&!2] 27
State: 26 [!0] 9 [0&!1] 27 State: 27 [!0] 9 [0] 27 --END--""")
copy = spot.make_twa_graph(aut5, spot.twa_prop_set.all())
tc.assertFalse(spot.is_deterministic(aut5))
if spot.delay_branching_here(aut5):
aut5.purge_unreachable_states()
aut5.merge_edges()
tc.assertEqual(aut5.num_states(), 13)
tc.assertEqual(aut5.num_edges(), 29)
tc.assertTrue(spot.are_equivalent(copy, aut5))
a = spot.automaton("""HOA: v1 States: 8 Start: 0 AP: 3 "a" "b" "c"
Acceptance: 0 t --BODY-- State: 0 [0] 1 [0] 2 [0] 3 State: 1 [!1] 4&5
[1] 5&6 State: 2 [0] 4&6 State: 3 [0] 3&6 State: 4 [!0] 7 State: 5
[!0] 7 State: 6 [!0] 6 State: 7 [0] 7 --END--""")
copy = spot.make_twa_graph(a, spot.twa_prop_set.all())
if spot.delay_branching_here(a):
a.purge_unreachable_states()
a.merge_edges()
tc.assertEqual(a.to_str(), """HOA: v1
States: 7
Start: 0
AP: 3 "b" "a" "c"
acc-name: all
Acceptance: 0 t
properties: trans-labels explicit-labels state-acc univ-branch
--BODY--
State: 0
[1] 1
[1] 2
State: 1
[1] 3&5
[0] 4&5
[!0] 3&4
State: 2
[1] 2&5
State: 3
[!1] 6
State: 4
[!1] 6
State: 5
[!1] 5
State: 6
[1] 6
--END--""")
a = spot.automaton("""HOA: v1
States: 9
Start: 0 AP: 2 "a" "b"
spot.state-player: 0 1 1 0 0 0 0 1 1
Acceptance: 0 t
--BODY--
State: 0
[0] 1
[0] 2
[0] 3
[0] 4
State: 1
[1] 5
State: 2
[!1] 6
State: 3
[1] 7
State: 4
[!1] 8
State: 5
[t] 5
State: 6
[t] 6
State: 7
[t] 7
State: 8
[t] 8
--END--""")
copy = spot.make_twa_graph(a, spot.twa_prop_set.all())
if spot.delay_branching_here(a):
a.purge_unreachable_states()
tc.assertTrue(spot.are_equivalent(a, copy))
tc.assertEqual(a.to_str(), """HOA: v1
States: 7
Start: 0
AP: 2 "b" "a"
acc-name: all
Acceptance: 0 t
properties: trans-labels explicit-labels state-acc very-weak
spot-state-player: 0 1 0 0 0 1 1
--BODY--
State: 0
[1] 1
[1] 2
State: 1
[0] 3
[!0] 4
State: 2
[0] 5
[!0] 6
State: 3
[t] 3
State: 4
[t] 4
State: 5
[t] 5
State: 6
[t] 6
--END--""")