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

alternation: implement remove_alternation() for weak alt automata

This mixes the subset construction (for 1-state rejecting SCCs) and
the breakpoint construction (for larger rejecting SCCs).  The
algorithm should probably be rewritten in a cleaner and more efficient
way, but that should do for a first version.  It should be easy to
extend it to support Büchi acceptance (since the breakpoint
construction works for this) when we need it.

* spot/twaalgos/alternation.hh,
spot/twaalgos/alternation.cc (remove_alternation): New function.
* tests/python/alternation.ipynb: New file.
* tests/Makefile.am, doc/org/tut.org: Add it.
parent 582d455c
......@@ -75,3 +75,4 @@ real notebooks instead.
automata.
- [[https://spot.lrde.epita.fr/ipynb/atva16-fig2a.html][=atva16-fig2a.ipynb=]] first example from our [[https://www.lrde.epita.fr/~adl/dl/adl/duret.16.atva2.pdf][ATVA'16 tool paper]].
- [[https://spot.lrde.epita.fr/ipynb/atva16-fig2b.html][=atva16-fig2b.ipynb=]] second example from our [[https://www.lrde.epita.fr/~adl/dl/adl/duret.16.atva2.pdf][ATVA'16 tool paper]].
- [[https://spot.lrde.epita.fr/ipynb/alternation.html][=alternation.ipynb=]] examples of alternating automata.
......@@ -17,9 +17,11 @@
// 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 <spot/twaalgos/alternation.hh>
#include <algorithm>
#include <sstream>
#include <spot/twaalgos/alternation.hh>
#include <spot/misc/minato.hh>
#include <spot/twaalgos/strength.hh>
namespace spot
{
......@@ -62,6 +64,8 @@ namespace spot
return res;
}
void outedge_combiner::new_dests(unsigned st, bdd out) const
{
minato_isop isop(out);
......@@ -85,4 +89,394 @@ namespace spot
}
}
namespace
{
class alternation_remover final
{
protected:
const_twa_graph_ptr aut_;
scc_info si_;
enum scc_class : char { accept, reject_1, reject_more };
std::vector<scc_class> class_of_;
bool has_reject_more_ = false;
unsigned reject_1_count_ = 0;
std::set<unsigned> true_states_;
void classify_each_scc()
{
auto& g = aut_->get_graph();
unsigned sc = si_.scc_count();
for (unsigned n = 0; n < sc; ++n)
{
if (si_.is_trivial(n))
continue;
if (si_.states_of(n).size() == 1)
{
if (si_.is_rejecting_scc(n))
{
class_of_[n] = scc_class::reject_1;
++reject_1_count_;
}
else
{
// For size one, scc_info should always be able to
// decide rejecting/accepting.
assert(si_.is_accepting_scc(n));
unsigned s = si_.states_of(n).front();
auto& ss = g.state_storage(s);
if (ss.succ == ss.succ_tail)
{
auto& es = g.edge_storage(ss.succ);
if (es.cond == bddtrue && !aut_->is_univ_dest(es.dst))
true_states_.emplace(s);
}
}
}
else
{
acc_cond::mark_t m;
bool first = true;
for (auto src: si_.states_of(n))
for (auto& t: aut_->out(src))
for (unsigned d: aut_->univ_dests(t.dst))
if (si_.scc_of(d) == n)
{
if (first)
{
first = false;
m = t.acc;
if (!aut_->acc().accepting(m))
{
class_of_[n] = reject_more;
has_reject_more_ = true;
}
// In case of a universal edge we only
// need to check the first destination
// inside the SCC, because the other
// have the same t.acc.
break;
}
else if (m != t.acc)
{
throw std::runtime_error
("alternation_removal() only work with weak "
"alternating automata");
}
}
}
}
}
std::vector<int> state_to_var_;
std::map<int, unsigned> var_to_state_;
std::vector<int> scc_to_var_;
std::map<int, acc_cond::mark_t> var_to_mark_;
bdd all_vars_;
bdd all_marks_;
bdd all_states_;
void allocate_state_vars()
{
auto d = aut_->get_dict();
// We need one BDD variable per possible output state. If
// that state is in a reject_more SCC we actually use two
// variables for the breakpoint.
unsigned ns = aut_->num_states();
state_to_var_.reserve(ns);
bdd all_states = bddtrue;
for (unsigned s = 0; s < ns; ++s)
{
if (!si_.reachable_state(s))
{
state_to_var_.push_back(0);
continue;
}
scc_class c = class_of_[si_.scc_of(s)];
bool r = c == scc_class::reject_more;
int v = d->register_anonymous_variables(1 + r, this);
state_to_var_.push_back(v);
var_to_state_[v] = s;
all_states &= bdd_ithvar(v);
if (r)
{
var_to_state_[v + 1] = ~s;
all_states &= bdd_ithvar(v + 1);
}
}
// We also use one BDD variable per reject_1 SCC. Each of
// these variables will represent a bit in mark_t. We reserve
// the first bit for the break_point construction if we have
// some reject_more SCC.
unsigned nc = si_.scc_count();
scc_to_var_.reserve(nc);
unsigned mark_pos = has_reject_more_;
bdd all_marks = bddtrue;
for (unsigned s = 0; s < nc; ++s)
{
scc_class c = class_of_[s];
if (c == scc_class::reject_1)
{
int v = d->register_anonymous_variables(1, this);
scc_to_var_.emplace_back(v);
var_to_mark_.emplace(v, acc_cond::mark_t({mark_pos++}));
bdd bv = bdd_ithvar(v);
all_marks &= bv;
}
else
{
scc_to_var_.emplace_back(0);
}
}
all_marks_ = all_marks;
all_states_ = all_states;
all_vars_ = all_states & all_marks;
}
std::map<unsigned, bdd> state_as_bdd_cache_;
bdd state_as_bdd(unsigned s)
{
auto p = state_as_bdd_cache_.emplace(s, bddfalse);
if (!p.second)
return p.first->second;
bool marked = (int)s < 0;
if (marked)
s = ~s;
unsigned scc_s = si_.scc_of(s);
bdd res = bddfalse;
for (auto& e: aut_->out(s))
{
bdd dest = bddtrue;
for (unsigned d: aut_->univ_dests(e.dst))
{
unsigned scc_d = si_.scc_of(d);
scc_class c = class_of_[scc_d];
bool mark =
marked && (scc_s == scc_d) && (c == scc_class::reject_more);
dest &= bdd_ithvar(state_to_var_[d] + mark);
if (c == scc_class::reject_1 && scc_s == scc_d)
dest &= bdd_ithvar(scc_to_var_[scc_s]);
}
res |= e.cond & dest;
}
p.first->second = res;
return res;
}
acc_cond::mark_t bdd_to_state(bdd b, std::vector<unsigned>& s)
{
acc_cond::mark_t m = 0U;
while (b != bddtrue)
{
assert(bdd_low(b) == bddfalse);
int v = bdd_var(b);
auto it = var_to_state_.find(v);
if (it != var_to_state_.end())
{
s.push_back(it->second);
}
else
{
auto it2 = var_to_mark_.find(v);
assert(it2 != var_to_mark_.end());
m |= it2->second;
}
b = bdd_high(b);
}
return m;
}
void simplify_state_set(std::vector<unsigned>& ss)
{
auto to_remove = true_states_;
for (unsigned i: ss)
if ((int)i < 0)
to_remove.emplace(~i);
auto i =
std::remove_if(ss.begin(), ss.end(),
[&] (unsigned s) {
return to_remove.find(s) != to_remove.end();
});
ss.erase(i, ss.end());
std::sort(ss.begin(), ss.end());
}
bool has_mark(const std::vector<unsigned>& ss)
{
for (unsigned i: ss)
if ((int)i < 0)
return true;
return false;
}
void set_mark(std::vector<unsigned>& ss)
{
for (unsigned& s: ss)
if (class_of_[si_.scc_of(s)] == scc_class::reject_more)
s = ~s;
}
public:
alternation_remover(const const_twa_graph_ptr& aut)
: aut_(aut), si_(aut), class_of_(si_.scc_count(), scc_class::accept)
{
}
~alternation_remover()
{
aut_->get_dict()->unregister_all_my_variables(this);
}
twa_graph_ptr run(bool named_states)
{
// First, we classify each SCC into three possible classes:
//
// 1) trivial or accepting
// 2) rejecting of size 1
// 3) rejecting of size >1
classify_each_scc();
// Rejecting SCCs of size 1 can be handled using genralized
// Büchi acceptance, using one set per SCC, as in Gastin &
// Oddoux CAV'01. See also Boker & et al. ICALP'10. Larger
// rejecting SCCs require a more expensive procedure known as
// the break point construction. See Miyano & Hayashi (TCS
// 1984). We are currently combining the two constructions.
auto res = make_twa_graph(aut_->get_dict());
res->copy_ap_of(aut_);
// We preserve deterministic-like properties, and
// stutter-invariance.
res->prop_copy(aut_, {false, false, true, true});
res->set_generalized_buchi(has_reject_more_ + reject_1_count_);
// We for easier computation of outgoing sets, we will
// represent states using BDD variables.
allocate_state_vars();
// Conversion between state-sets and states.
typedef std::vector<unsigned> state_set;
std::vector<state_set> s_to_ss;
std::map<state_set, unsigned> ss_to_s;
std::stack<unsigned> todo;
std::vector<std::string>* state_name = nullptr;
if (named_states)
{
state_name = new std::vector<std::string>();
res->set_named_prop("state-names", state_name);
}
auto new_state = [&](state_set& ss, bool& need_mark)
{
simplify_state_set(ss);
if (has_reject_more_)
{
need_mark = has_mark(ss);
if (!need_mark)
set_mark(ss);
}
auto p = ss_to_s.emplace(ss, 0);
if (!p.second)
return p.first->second;
unsigned s = res->new_state();
assert(s == s_to_ss.size());
p.first->second = s;
s_to_ss.emplace_back(ss);
todo.emplace(s);
if (named_states)
{
std::ostringstream os;
bool notfirst = false;
for (unsigned s: ss)
{
if (notfirst)
os << ',';
else
notfirst = true;
if ((int)s < 0)
{
os << '~';
s = ~s;
}
os << s;
}
if (!notfirst)
os << "{}";
state_name->emplace_back(os.str());
}
return s;
};
const auto& i = aut_->univ_dests(aut_->get_init_state_number());
state_set is(i.begin(), i.end());
bool has_mark = false;
res->set_init_state(new_state(is, has_mark));
acc_cond::mark_t all_marks = res->acc().all_sets();
state_set v;
while (!todo.empty())
{
unsigned s = todo.top();
todo.pop();
bdd bs = bddtrue;
for (unsigned se: s_to_ss[s])
bs &= state_as_bdd(se);
bdd ap = bdd_exist(bdd_support(bs), all_vars_);
bdd all_letters = bdd_exist(bs, all_vars_);
// First loop over all possible valuations atomic properties.
while (all_letters != bddfalse)
{
bdd oneletter = bdd_satoneset(all_letters, ap, bddtrue);
all_letters -= oneletter;
minato_isop isop(bs & oneletter);
bdd cube;
while ((cube = isop.next()) != bddfalse)
{
bdd cond = bdd_exist(cube, all_vars_);
bdd dest = bdd_existcomp(cube, all_vars_);
v.clear();
acc_cond::mark_t m = bdd_to_state(dest, v);
unsigned d = new_state(v, has_mark);
if (has_mark)
m.set(0);
res->new_edge(s, d, cond, all_marks - m);
}
}
}
res->merge_edges();
return res;
}
};
}
twa_graph_ptr remove_alternation(const const_twa_graph_ptr& aut,
bool named_states)
{
if (!aut->is_alternating())
// Nothing to do, why was this function called at all?
return std::const_pointer_cast<twa_graph>(aut);
alternation_remover ar(aut);
return ar.run(named_states);
}
}
......@@ -90,7 +90,14 @@ namespace spot
}
/// @}
/// \brief Remove universal edges from an automaton.
///
/// This procedure is restricted to weak alternating automata as
/// input, and produces TGBAs as output. (Generalized Büchi
/// acceptance is only used in presence of size-1 rejecting-SCCs.)
///
/// \param named_states name each state for easier debugging
SPOT_API
twa_graph_ptr remove_alternation(const const_twa_graph_ptr& aut,
bool named_states = false);
}
......@@ -302,6 +302,7 @@ if USE_PYTHON
TESTS_ipython = \
python/acc_cond.ipynb \
python/accparse.ipynb \
python/alternation.ipynb \
python/atva16-fig2a.ipynb \
python/atva16-fig2b.ipynb \
python/automata-io.ipynb \
......
{
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.5.2+"
},
"name": ""
},
"nbformat": 3,
"nbformat_minor": 0,
"worksheets": [
{
"cells": [
{
"cell_type": "code",
"collapsed": true,
"input": [
"import spot\n",
"spot.setup(show_default='.ba')"
],
"language": "python",
"metadata": {},
"outputs": [],
"prompt_number": 1
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"These test cases for the `remove_alternation()` algorithm."
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"aut = spot.automaton('''\n",
"HOA: v1\n",
"tool: \"ltl3ba\" \"1.1.3\"\n",
"name: \"VWAA for FGa && GFb\"\n",
"States: 6\n",
"Start: 0\n",
"acc-name: co-Buchi\n",
"Acceptance: 1 Fin(0)\n",
"AP: 2 \"a\" \"b\"\n",
"properties: trans-labels explicit-labels state-acc univ-branch very-weak\n",
"--BODY--\n",
"State: 0 \"(FG(a) && GF(b))\"\n",
" [t] 3&1\n",
"State: 1 \"GF(b)\"\n",
" [(1)] 1\n",
" [(!1)] 2&1\n",
"State: 2 \"F(b)\" {0}\n",
" [(1)] 5\n",
" [(!1)] 2\n",
"State: 3 \"FG(a)\" {0}\n",
" [(0)] 4\n",
" [t] 3\n",
"State: 4 \"G(a)\"\n",
" [(0)] 4\n",
"State: 5 \"t\"\n",
" [t] 5\n",
"--END--\n",
"'''); aut.show('.1ab')"
],
"language": "python",
"metadata": {},
"outputs": [
{
"metadata": {},
"output_type": "pyout",
"prompt_number": 2,
"svg": [
"<svg height=\"213pt\" viewBox=\"0.00 0.00 475.48 212.74\" width=\"475pt\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">\n",
"<g class=\"graph\" id=\"graph0\" transform=\"scale(1 1) rotate(0) translate(4 208.74)\">\n",
"<title>G</title>\n",
"<polygon fill=\"white\" points=\"-4,4 -4,-208.74 471.48,-208.74 471.48,4 -4,4\" stroke=\"none\"/>\n",
"<text font-family=\"Lato\" font-size=\"14.00\" text-anchor=\"start\" x=\"211.24\" y=\"-190.54\">Fin(</text>\n",
"<text fill=\"#5da5da\" font-family=\"Lato\" font-size=\"14.00\" text-anchor=\"start\" x=\"236.24\" y=\"-190.54\">\u24ff</text>\n",
"<text font-family=\"Lato\" font-size=\"14.00\" text-anchor=\"start\" x=\"252.24\" y=\"-190.54\">)</text>\n",
"<!-- I -->\n",
"<!-- 0 -->\n",
"<g class=\"node\" id=\"node2\"><title>0</title>\n",
"<ellipse cx=\"56\" cy=\"-81.8701\" fill=\"#ffffaa\" rx=\"18\" ry=\"18\" stroke=\"black\"/>\n",
"<text font-family=\"Lato\" font-size=\"14.00\" text-anchor=\"middle\" x=\"56\" y=\"-78.1701\">0</text>\n",
"</g>\n",
"<!-- I&#45;&gt;0 -->\n",
"<g class=\"edge\" id=\"edge1\"><title>I-&gt;0</title>\n",
"<path d=\"M1.15491,-81.8701C2.79388,-81.8701 17.1543,-81.8701 30.6317,-81.8701\" fill=\"none\" stroke=\"black\"/>\n",
"<polygon fill=\"black\" points=\"37.9419,-81.8701 30.9419,-85.0202 34.4419,-81.8701 30.9419,-81.8702 30.9419,-81.8702 30.9419,-81.8702 34.4419,-81.8701 30.9418,-78.7202 37.9419,-81.8701 37.9419,-81.8701\" stroke=\"black\"/>\n",
"</g>\n",
"<!-- &#45;1 -->\n",
"<g class=\"node\" id=\"node3\"><title>-1</title>\n",
"<polygon fill=\"#ffffaa\" points=\"120,-82.3701 119,-82.3701 119,-81.3701 120,-81.3701 120,-82.3701\" stroke=\"none\"/>\n",
"</g>\n",
"<!-- 0&#45;&gt;&#45;1 -->\n",
"<g class=\"edge\" id=\"edge2\"><title>0-&gt;-1</title>\n",
"<path d=\"M74.2705,-81.8701C92.0425,-81.8701 117.359,-81.8701 118.924,-81.8701\" fill=\"none\" stroke=\"black\"/>\n",
"<text font-family=\"Lato\" font-size=\"14.00\" text-anchor=\"start\" x=\"92\" y=\"-85.6701\">1</text>\n",
"</g>\n",
"<!-- 3 -->\n",
"<g class=\"node\" id=\"node4\"><title>3</title>\n",
"<ellipse cx=\"183.87\" cy=\"-121.87\" fill=\"#ffffaa\" rx=\"26.7407\" ry=\"26.7407\" stroke=\"black\"/>\n",
"<text font-family=\"Lato\" font-size=\"14.00\" text-anchor=\"start\" x=\"179.37\" y=\"-125.67\">3</text>\n",
"<text fill=\"#5da5da\" font-family=\"Lato\" font-size=\"14.00\" text-anchor=\"start\" x=\"175.87\" y=\"-110.67\">\u24ff</text>\n",
"</g>\n",
"<!-- &#45;1&#45;&gt;3 -->\n",
"<g class=\"edge\" id=\"edge3\"><title>-1-&gt;3</title>\n",
"<path d=\"M120.046,-81.899C121.052,-82.5396 138.562,-93.6801 154.84,-104.037\" fill=\"none\" stroke=\"black\"/>\n",
"<polygon fill=\"black\" points=\"160.749,-107.796 153.152,-106.696 157.796,-105.917 154.843,-104.038 154.843,-104.038 154.843,-104.038 157.796,-105.917 156.534,-101.38 160.749,-107.796 160.749,-107.796\" stroke=\"black\"/>\n",
"</g>\n",
"<!-- 1 -->\n",
"<g class=\"node\" id=\"node5\"><title>1</title>\n",
"<ellipse cx=\"183.87\" cy=\"-25.8701\" fill=\"#ffffaa\" rx=\"18\" ry=\"18\" stroke=\"black\"/>\n",
"<text font-family=\"Lato\" font-size=\"14.00\" text-anchor=\"start\" x=\"179.37\" y=\"-22.1701\">1</text>\n",
"</g>\n",
"<!-- &#45;1&#45;&gt;1 -->\n",
"<g class=\"edge\" id=\"edge4\"><title>-1-&gt;1</title>\n",
"<path d=\"M120.046,-81.8295C121.259,-80.7489 146.435,-58.3241 164.542,-42.1952\" fill=\"none\" stroke=\"black\"/>\n",
"<polygon fill=\"black\" points=\"169.901,-37.4224 166.769,-44.4305 167.287,-39.7503 164.673,-42.0783 164.673,-42.0783 164.673,-42.0783 167.287,-39.7503 162.578,-39.7261 169.901,-37.4224 169.901,-37.4224\" stroke=\"black\"/>\n",
"</g>\n",
"<!-- 3&#45;&gt;3 -->\n",
"<g class=\"edge\" id=\"edge12\"><title>3-&gt;3</title>\n",
"<path d=\"M175.67,-147.691C175.207,-158.048 177.94,-166.74 183.87,-166.74 188.317,-166.74 190.967,-161.851 191.818,-155.037\" fill=\"none\" stroke=\"black\"/>\n",
"<polygon fill=\"black\" points=\"192.07,-147.691 194.978,-154.795 191.95,-151.189 191.83,-154.687 191.83,-154.687 191.83,-154.687 191.95,-151.189 188.682,-154.579 192.07,-147.691 192.07,-147.691\" stroke=\"black\"/>\n",
"<text font-family=\"Lato\" font-size=\"14.00\" text-anchor=\"start\" x=\"179.37\" y=\"-170.54\">1</text>\n",
"</g>\n",
"<!-- 4 -->\n",
"<g class=\"node\" id=\"node9\"><title>4</title>\n",
"<ellipse cx=\"277.74\" cy=\"-121.87\" fill=\"#ffffaa\" rx=\"18\" ry=\"18\" stroke=\"black\"/>\n",
"<text font-family=\"Lato\" font-size=\"14.00\" text-anchor=\"middle\" x=\"277.74\" y=\"-118.17\">4</text>\n",
"</g>\n",
"<!-- 3&#45;&gt;4 -->\n",
"<g class=\"edge\" id=\"edge11\"><title>3-&gt;4</title>\n",
"<path d=\"M210.951,-121.87C223.988,-121.87 239.668,-121.87 252.449,-121.87\" fill=\"none\" stroke=\"black\"/>\n",
"<polygon fill=\"black\" points=\"259.598,-121.87 252.598,-125.02 256.098,-121.87 252.598,-121.87 252.598,-121.87 252.598,-121.87 256.098,-121.87 252.598,-118.72 259.598,-121.87 259.598,-121.87\" stroke=\"black\"/>\n",
"<text font-family=\"Lato\" font-size=\"14.00\" text-anchor=\"start\" x=\"231.74\" y=\"-125.67\">a</text>\n",
"</g>\n",
"<!-- 1&#45;&gt;1 -->\n",
"<g class=\"edge\" id=\"edge5\"><title>1-&gt;1</title>\n",
"<path d=\"M176.202,-42.1603C174.353,-52.2592 176.909,-61.8701 183.87,-61.8701 189.091,-61.8701 191.834,-56.464 192.099,-49.5004\" fill=\"none\" stroke=\"black\"/>\n",
"<polygon fill=\"black\" points=\"191.538,-42.1603 195.212,-48.9 191.805,-45.6502 192.071,-49.14 192.071,-49.14 192.071,-49.14 191.805,-45.6502 188.93,-49.38 191.538,-42.1603 191.538,-42.1603\" stroke=\"black\"/>\n",
"<text font-family=\"Lato\" font-size=\"14.00\" text-anchor=\"start\" x=\"179.37\" y=\"-65.6701\">b</text>\n",
"</g>\n",
"<!-- &#45;4 -->\n",
"<g class=\"node\" id=\"node6\"><title>-4</title>\n",
"<polygon fill=\"#ffffaa\" points=\"278.24,-27.3701 277.24,-27.3701 277.24,-26.3701 278.24,-26.3701 278.24,-27.3701\" stroke=\"none\"/>\n",
"</g>\n",
"<!-- 1&#45;&gt;&#45;4 -->\n",
"<g class=\"edge\" id=\"edge6\"><title>1-&gt;-4</title>\n",
"<path d=\"M202.15,-26.0582C227.931,-26.3388 273.762,-26.8376 276.602,-26.8686\" fill=\"none\" stroke=\"black\"/>\n",
"<text font-family=\"Lato\" font-size=\"14.00\" text-anchor=\"start\" x=\"228.74\" y=\"-30.6701\">!b</text>\n",
"</g>\n",
"<!-- &#45;4&#45;&gt;1 -->\n",
"<g class=\"edge\" id=\"edge8\"><title>-4-&gt;1</title>\n",
"<path d=\"M276.684,-26.8633C275.531,-26.7259 256.851,-24.5165 241.74,-23.8701 231.018,-23.4114 219.155,-23.7021 209.05,-24.1888\" fill=\"none\" stroke=\"black\"/>\n",
"<polygon fill=\"black\" points=\"202.044,-24.57 208.863,-21.0443 205.539,-24.3798 209.034,-24.1896 209.034,-24.1896 209.034,-24.1896 205.539,-24.3798 209.205,-27.335 202.044,-24.57 202.044,-24.57\" stroke=\"black\"/>\n",