scc_info: make it possible to ignore or cut edges
* spot/twaalgos/sccinfo.hh, spot/twaalgos/sccinfo.cc: Take a filter function as optional argument. * tests/core/sccif.cc, tests/core/sccif.test: New files. * tests/Makefile.am, tests/core/.gitignore: Adjust. * NEWS: Mention the new feature.
This commit is contained in:
parent
4da6a5cde1
commit
425620150a
7 changed files with 287 additions and 6 deletions
3
NEWS
3
NEWS
|
|
@ -76,6 +76,9 @@ New in spot 2.3.4.dev (not yet released)
|
||||||
- spot::scc_info has two new methods to easily iterate over the
|
- spot::scc_info has two new methods to easily iterate over the
|
||||||
edges of an SCC: edges_of() and inner_edges_of().
|
edges of an SCC: edges_of() and inner_edges_of().
|
||||||
|
|
||||||
|
- spot::scc_info can now be passed a filter function to ignore
|
||||||
|
or cut some edges.
|
||||||
|
|
||||||
Python:
|
Python:
|
||||||
|
|
||||||
- The 'spot.gen' package exports the functions from libspotgen.
|
- The 'spot.gen' package exports the functions from libspotgen.
|
||||||
|
|
|
||||||
|
|
@ -47,8 +47,12 @@ namespace spot
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
scc_info::scc_info(const_twa_graph_ptr aut)
|
scc_info::scc_info(const_twa_graph_ptr aut,
|
||||||
: aut_(aut)
|
unsigned initial_state,
|
||||||
|
edge_filter filter,
|
||||||
|
void* filter_data)
|
||||||
|
: aut_(aut), initial_state_(initial_state),
|
||||||
|
filter_(filter), filter_data_(filter_data)
|
||||||
{
|
{
|
||||||
unsigned n = aut->num_states();
|
unsigned n = aut->num_states();
|
||||||
sccof_.resize(n, -1U);
|
sccof_.resize(n, -1U);
|
||||||
|
|
@ -74,10 +78,28 @@ namespace spot
|
||||||
std::stack<stack_item> todo_;
|
std::stack<stack_item> todo_;
|
||||||
auto& gr = aut->get_graph();
|
auto& gr = aut->get_graph();
|
||||||
|
|
||||||
|
|
||||||
|
std::deque<unsigned> init_states;
|
||||||
|
std::vector<bool> init_seen(n, false);
|
||||||
|
auto push_init = [&](unsigned s)
|
||||||
|
{
|
||||||
|
if (h_[s] != 0 || init_seen[s])
|
||||||
|
return;
|
||||||
|
init_seen[s] = true;
|
||||||
|
init_states.push_back(s);
|
||||||
|
};
|
||||||
|
|
||||||
// Setup depth-first search from the initial state. But we may
|
// Setup depth-first search from the initial state. But we may
|
||||||
// have a conjunction of initial state in alternating automata.
|
// have a conjunction of initial state in alternating automata.
|
||||||
for (unsigned init: aut->univ_dests(aut->get_init_state_number()))
|
if (initial_state_ == -1U)
|
||||||
|
initial_state_ = aut->get_init_state_number();
|
||||||
|
for (unsigned init: aut->univ_dests(initial_state_))
|
||||||
|
push_init(init);
|
||||||
|
|
||||||
|
while (!init_states.empty())
|
||||||
{
|
{
|
||||||
|
unsigned init = init_states.front();
|
||||||
|
init_states.pop_front();
|
||||||
int spi = h_[init];
|
int spi = h_[init];
|
||||||
if (spi > 0)
|
if (spi > 0)
|
||||||
continue;
|
continue;
|
||||||
|
|
@ -133,6 +155,17 @@ namespace spot
|
||||||
for (auto& t: aut->out(s))
|
for (auto& t: aut->out(s))
|
||||||
for (unsigned d: aut->univ_dests(t))
|
for (unsigned d: aut->univ_dests(t))
|
||||||
{
|
{
|
||||||
|
// If edges are cut, we are not able to
|
||||||
|
// maintain proper successor information.
|
||||||
|
if (filter_)
|
||||||
|
switch (filter_(t, d, filter_data_))
|
||||||
|
{
|
||||||
|
case edge_filter_choice::keep:
|
||||||
|
break;
|
||||||
|
case edge_filter_choice::ignore:
|
||||||
|
case edge_filter_choice::cut:
|
||||||
|
continue;
|
||||||
|
}
|
||||||
unsigned n = sccof_[d];
|
unsigned n = sccof_[d];
|
||||||
assert(n != -1U);
|
assert(n != -1U);
|
||||||
if (n == num)
|
if (n == num)
|
||||||
|
|
@ -179,6 +212,7 @@ namespace spot
|
||||||
// We have a successor to look at.
|
// We have a successor to look at.
|
||||||
// Fetch the values we are interested in...
|
// Fetch the values we are interested in...
|
||||||
auto& e = gr.edge_storage(tr_succ);
|
auto& e = gr.edge_storage(tr_succ);
|
||||||
|
|
||||||
unsigned dest = e.dst;
|
unsigned dest = e.dst;
|
||||||
if ((int) dest < 0)
|
if ((int) dest < 0)
|
||||||
{
|
{
|
||||||
|
|
@ -203,6 +237,19 @@ namespace spot
|
||||||
todo_.top().out_edge = e.next_succ;
|
todo_.top().out_edge = e.next_succ;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Do we really want to look at this
|
||||||
|
if (filter_)
|
||||||
|
switch (filter_(e, dest, filter_data_))
|
||||||
|
{
|
||||||
|
case edge_filter_choice::keep:
|
||||||
|
break;
|
||||||
|
case edge_filter_choice::ignore:
|
||||||
|
continue;
|
||||||
|
case edge_filter_choice::cut:
|
||||||
|
push_init(e.dst);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
auto acc = e.acc;
|
auto acc = e.acc;
|
||||||
|
|
||||||
// Are we going to a new state?
|
// Are we going to a new state?
|
||||||
|
|
|
||||||
|
|
@ -277,12 +277,45 @@ namespace spot
|
||||||
// support that yet.
|
// support that yet.
|
||||||
typedef scc_info_node scc_node;
|
typedef scc_info_node scc_node;
|
||||||
typedef scc_info_node::scc_succs scc_succs;
|
typedef scc_info_node::scc_succs scc_succs;
|
||||||
|
/// @{
|
||||||
|
/// \brief An edge_filter may be called on each edge to decide what
|
||||||
|
/// to do with it.
|
||||||
|
///
|
||||||
|
/// The edge filter is called with an edge and a destination. (In
|
||||||
|
/// existential automata the destination is already given by the
|
||||||
|
/// edge, but in alternating automata, one edge may have several
|
||||||
|
/// destinations, and in this case the filter will be called for
|
||||||
|
/// each destination.) The filter should return a value from
|
||||||
|
/// edge_filter_choice.
|
||||||
|
///
|
||||||
|
/// \c keep means to use the edge normally, as if no filter had
|
||||||
|
/// been given. \c ignore means to pretend the edge does not
|
||||||
|
/// exist (if the destination is only reachable through this edge,
|
||||||
|
/// it will not be visited). \c cut also ignores the edge, but
|
||||||
|
/// it remembers to visit the destination state (as if it were an
|
||||||
|
/// initial state) in case it is not reachable otherwise.
|
||||||
|
///
|
||||||
|
/// Note that successors between SCCs can only be maintained for
|
||||||
|
/// edges that are kept. If some edges are ignored or cut, the
|
||||||
|
/// SCC graph that you can explore with scc_info::initial() and
|
||||||
|
/// scc_info::succ() will be restricted to the portion reachable
|
||||||
|
/// with "keep" edges. Additionally SCCs might be created when
|
||||||
|
/// edges are cut, but those will not be reachable from
|
||||||
|
/// scc_info::initial()..
|
||||||
|
enum class edge_filter_choice { keep, ignore, cut };
|
||||||
|
typedef edge_filter_choice
|
||||||
|
(*edge_filter)(const twa_graph::edge_storage_t& e, unsigned dst,
|
||||||
|
void* filter_data);
|
||||||
|
/// @}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|
||||||
std::vector<unsigned> sccof_;
|
std::vector<unsigned> sccof_;
|
||||||
std::vector<scc_node> node_;
|
std::vector<scc_node> node_;
|
||||||
const_twa_graph_ptr aut_;
|
const_twa_graph_ptr aut_;
|
||||||
|
unsigned initial_state_;
|
||||||
|
edge_filter filter_;
|
||||||
|
void* filter_data_;
|
||||||
|
|
||||||
// Update the useful_ bits. Called automatically.
|
// Update the useful_ bits. Called automatically.
|
||||||
void determine_usefulness();
|
void determine_usefulness();
|
||||||
|
|
@ -293,7 +326,13 @@ namespace spot
|
||||||
}
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
scc_info(const_twa_graph_ptr aut);
|
|
||||||
|
// Use ~0U instead of -1U to work around a bug in Swig.
|
||||||
|
// See https://github.com/swig/swig/issues/993
|
||||||
|
scc_info(const_twa_graph_ptr aut,
|
||||||
|
unsigned initial_state = ~0U,
|
||||||
|
edge_filter filter = nullptr,
|
||||||
|
void* filter_data = nullptr);
|
||||||
|
|
||||||
const_twa_graph_ptr get_aut() const
|
const_twa_graph_ptr get_aut() const
|
||||||
{
|
{
|
||||||
|
|
@ -389,8 +428,8 @@ namespace spot
|
||||||
/// \brief Get number of the SCC containing the initial state.
|
/// \brief Get number of the SCC containing the initial state.
|
||||||
unsigned initial() const
|
unsigned initial() const
|
||||||
{
|
{
|
||||||
SPOT_ASSERT(scc_count() - 1 == scc_of(aut_->get_init_state_number()));
|
SPOT_ASSERT(filter_ || scc_count() - 1 == scc_of(initial_state_));
|
||||||
return scc_count() - 1;
|
return scc_of(initial_state_);
|
||||||
}
|
}
|
||||||
|
|
||||||
const scc_succs& succ(unsigned scc) const
|
const scc_succs& succ(unsigned scc) const
|
||||||
|
|
|
||||||
|
|
@ -88,6 +88,7 @@ check_PROGRAMS = \
|
||||||
core/reduceu \
|
core/reduceu \
|
||||||
core/reductaustr \
|
core/reductaustr \
|
||||||
core/safra \
|
core/safra \
|
||||||
|
core/sccif \
|
||||||
core/syntimpl \
|
core/syntimpl \
|
||||||
core/taatgba \
|
core/taatgba \
|
||||||
core/trival \
|
core/trival \
|
||||||
|
|
@ -134,6 +135,7 @@ core_reduceu_CPPFLAGS = $(AM_CPPFLAGS) -DREDUC -DEVENT_UNIV
|
||||||
core_reductaustr_SOURCES = core/equalsf.cc
|
core_reductaustr_SOURCES = core/equalsf.cc
|
||||||
core_reductaustr_CPPFLAGS = $(AM_CPPFLAGS) -DREDUC_TAUSTR
|
core_reductaustr_CPPFLAGS = $(AM_CPPFLAGS) -DREDUC_TAUSTR
|
||||||
core_safra_SOURCES = core/safra.cc
|
core_safra_SOURCES = core/safra.cc
|
||||||
|
core_sccif_SOURCES = core/sccif.cc
|
||||||
core_syntimpl_SOURCES = core/syntimpl.cc
|
core_syntimpl_SOURCES = core/syntimpl.cc
|
||||||
core_tostring_SOURCES = core/tostring.cc
|
core_tostring_SOURCES = core/tostring.cc
|
||||||
core_trival_SOURCES = core/trival.cc
|
core_trival_SOURCES = core/trival.cc
|
||||||
|
|
@ -245,6 +247,7 @@ TESTS_twa = \
|
||||||
core/lbttparse.test \
|
core/lbttparse.test \
|
||||||
core/scc.test \
|
core/scc.test \
|
||||||
core/sccdot.test \
|
core/sccdot.test \
|
||||||
|
core/sccif.test \
|
||||||
core/sccsimpl.test \
|
core/sccsimpl.test \
|
||||||
core/sepsets.test \
|
core/sepsets.test \
|
||||||
core/split.test \
|
core/split.test \
|
||||||
|
|
|
||||||
1
tests/core/.gitignore
vendored
1
tests/core/.gitignore
vendored
|
|
@ -58,6 +58,7 @@ reductau
|
||||||
reductaustr
|
reductaustr
|
||||||
reduccmp
|
reduccmp
|
||||||
reductgba
|
reductgba
|
||||||
|
sccif
|
||||||
stdout
|
stdout
|
||||||
spotlbtt
|
spotlbtt
|
||||||
syntimpl
|
syntimpl
|
||||||
|
|
|
||||||
101
tests/core/sccif.cc
Normal file
101
tests/core/sccif.cc
Normal file
|
|
@ -0,0 +1,101 @@
|
||||||
|
// -*- coding: utf-8 -*-
|
||||||
|
// Copyright (C) 2017 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 <iostream>
|
||||||
|
#include <spot/twa/twagraph.hh>
|
||||||
|
#include <spot/twaalgos/sccinfo.hh>
|
||||||
|
#include <spot/twaalgos/hoa.hh>
|
||||||
|
|
||||||
|
static void display(spot::scc_info& si)
|
||||||
|
{
|
||||||
|
unsigned ns = si.scc_count();
|
||||||
|
for (unsigned n = 0; n < ns; ++n)
|
||||||
|
{
|
||||||
|
std::cout << "SCC#" << n << "\n states:";
|
||||||
|
for (unsigned s: si.states_of(n))
|
||||||
|
std::cout << ' ' << s;
|
||||||
|
std::cout << '\n';
|
||||||
|
std::cout << " edges:";
|
||||||
|
for (auto& e: si.inner_edges_of(n))
|
||||||
|
std::cout << ' ' << e.src << "->" << e.dst;
|
||||||
|
std::cout << '\n';
|
||||||
|
std::cout << " succs:";
|
||||||
|
for (unsigned s: si.succ(n))
|
||||||
|
std::cout << ' ' << s;
|
||||||
|
std::cout << '\n';
|
||||||
|
}
|
||||||
|
std::cout << '\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
auto d = spot::make_bdd_dict();
|
||||||
|
auto tg = make_twa_graph(d);
|
||||||
|
bdd p1 = bdd_ithvar(tg->register_ap("p1"));
|
||||||
|
bdd p2 = bdd_ithvar(tg->register_ap("p2"));
|
||||||
|
tg->set_generalized_buchi(2);
|
||||||
|
|
||||||
|
auto s1 = tg->new_state();
|
||||||
|
auto s2 = tg->new_state();
|
||||||
|
auto s3 = tg->new_state();
|
||||||
|
tg->set_init_state(s1);
|
||||||
|
tg->new_edge(s1, s1, bddfalse);
|
||||||
|
tg->new_edge(s1, s2, p1);
|
||||||
|
tg->new_edge(s1, s3, p2, {1});
|
||||||
|
tg->new_edge(s2, s3, p1 & p2, {0});
|
||||||
|
tg->new_edge(s3, s1, p1 | p2, {0, 1});
|
||||||
|
tg->new_edge(s3, s2, p1 >> p2);
|
||||||
|
tg->new_edge(s3, s3, bddtrue, {0, 1});
|
||||||
|
|
||||||
|
spot::print_hoa(std::cout, tg) << '\n';
|
||||||
|
|
||||||
|
{
|
||||||
|
std::cout << "** default\n";
|
||||||
|
spot::scc_info si(tg);
|
||||||
|
display(si);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
std::cout << "** ignore edges to 2\n";
|
||||||
|
auto filter = [](const spot::twa_graph::edge_storage_t&,
|
||||||
|
unsigned dst, void*)
|
||||||
|
{
|
||||||
|
if (dst == 2)
|
||||||
|
return spot::scc_info::edge_filter_choice::ignore;
|
||||||
|
else
|
||||||
|
return spot::scc_info::edge_filter_choice::keep;
|
||||||
|
};
|
||||||
|
spot::scc_info si(tg, s1, filter, nullptr);
|
||||||
|
display(si);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
std::cout << "** cut edges to 2\n";
|
||||||
|
auto filter = [](const spot::twa_graph::edge_storage_t&,
|
||||||
|
unsigned dst, void* val)
|
||||||
|
{
|
||||||
|
if (dst == *static_cast<unsigned*>(val))
|
||||||
|
return spot::scc_info::edge_filter_choice::cut;
|
||||||
|
else
|
||||||
|
return spot::scc_info::edge_filter_choice::keep;
|
||||||
|
};
|
||||||
|
unsigned ignore = 2;
|
||||||
|
spot::scc_info si(tg, s1, filter, &ignore);
|
||||||
|
display(si);
|
||||||
|
}
|
||||||
|
}
|
||||||
87
tests/core/sccif.test
Executable file
87
tests/core/sccif.test
Executable file
|
|
@ -0,0 +1,87 @@
|
||||||
|
#!/bin/sh
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright (C) 2017 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/>.
|
||||||
|
|
||||||
|
# While running some benchmark, Tomáš Babiak found that Spot took too
|
||||||
|
# much time (i.e. >1h) to translate those six formulae. It turns out
|
||||||
|
# that the WDBA minimization was performed after the degeneralization
|
||||||
|
# algorithm, while this is not necessary (WDBA will produce a BA, so
|
||||||
|
# we may as well skip degeneralization). Translating these formulae
|
||||||
|
# in the test-suite ensure that they don't take too much time (the
|
||||||
|
# buildfarm will timeout if it does).
|
||||||
|
|
||||||
|
. ./defs
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
run 0 ../sccif > stdout
|
||||||
|
|
||||||
|
cat >expected <<EOF
|
||||||
|
HOA: v1
|
||||||
|
States: 3
|
||||||
|
Start: 0
|
||||||
|
AP: 2 "p1" "p2"
|
||||||
|
acc-name: generalized-Buchi 2
|
||||||
|
Acceptance: 2 Inf(0)&Inf(1)
|
||||||
|
properties: trans-labels explicit-labels trans-acc
|
||||||
|
--BODY--
|
||||||
|
State: 0
|
||||||
|
[f] 0
|
||||||
|
[0] 1
|
||||||
|
[1] 2 {1}
|
||||||
|
State: 1
|
||||||
|
[0&1] 2 {0}
|
||||||
|
State: 2
|
||||||
|
[0 | 1] 0 {0 1}
|
||||||
|
[!0 | 1] 1
|
||||||
|
[t] 2 {0 1}
|
||||||
|
--END--
|
||||||
|
** default
|
||||||
|
SCC#0
|
||||||
|
states: 0 1 2
|
||||||
|
edges: 0->0 0->1 0->2 1->2 2->0 2->1 2->2
|
||||||
|
succs:
|
||||||
|
|
||||||
|
** ignore edges to 2
|
||||||
|
SCC#0
|
||||||
|
states: 1
|
||||||
|
edges:
|
||||||
|
succs:
|
||||||
|
SCC#1
|
||||||
|
states: 0
|
||||||
|
edges: 0->0
|
||||||
|
succs: 0
|
||||||
|
|
||||||
|
** cut edges to 2
|
||||||
|
SCC#0
|
||||||
|
states: 1
|
||||||
|
edges:
|
||||||
|
succs:
|
||||||
|
SCC#1
|
||||||
|
states: 0
|
||||||
|
edges: 0->0
|
||||||
|
succs: 0
|
||||||
|
SCC#2
|
||||||
|
states: 2
|
||||||
|
edges: 2->2
|
||||||
|
succs: 0 1
|
||||||
|
|
||||||
|
EOF
|
||||||
|
|
||||||
|
diff expected stdout
|
||||||
Loading…
Add table
Add a link
Reference in a new issue