Renaming and clean up

"Strategy" was used for mealy machines and game strategies a like.
Introduced the notion of mealy machine in three different flavors:
mealy machine: twa_graph with synthesis-outputs
separated mealy machine: mealy machine and all transitions
have conditions of the form (bdd over inputs)&(bdd over outputs)
split mealy machine: mealy machine that alternates between
env and player states. Needs state-players

* bin/ltlsynt.cc: renaming
* python/spot/impl.i: Add vector for const_twa_graph_ptr
* spot/twaalgos/aiger.cc,
spot/twaalgos/aiger.hh: Adapting functions
* spot/twaalgos/mealy_machine.cc,
spot/twaalgos/mealy_machine.hh: Add test functions and
propagate properties correctly. Adjust for names
* spot/twaalgos/synthesis.cc,
spot/twaalgos/synthesis.hh: Removing unnecessary functions
and adapt to new names
* tests/python/aiger.py,
tests/python/_mealy.ipynb,
tests/python/mealy.py,
tests/python/synthesis.ipynb: Adjust
This commit is contained in:
philipp 2021-11-04 00:24:17 +01:00
parent 6ebe3d7447
commit 98ebbea17e
12 changed files with 3376 additions and 3038 deletions

View file

@ -36,6 +36,7 @@
#include <spot/twaalgos/game.hh> #include <spot/twaalgos/game.hh>
#include <spot/twaalgos/hoa.hh> #include <spot/twaalgos/hoa.hh>
#include <spot/twaalgos/minimize.hh> #include <spot/twaalgos/minimize.hh>
#include <spot/twaalgos/mealy_machine.hh>
#include <spot/twaalgos/product.hh> #include <spot/twaalgos/product.hh>
#include <spot/twaalgos/synthesis.hh> #include <spot/twaalgos/synthesis.hh>
#include <spot/twaalgos/translate.hh> #include <spot/twaalgos/translate.hh>
@ -344,7 +345,7 @@ namespace
auto sub_f = sub_form.begin(); auto sub_f = sub_form.begin();
auto sub_o = sub_outs_str.begin(); auto sub_o = sub_outs_str.begin();
std::vector<spot::strategy_like_t> strategies; std::vector<spot::mealy_like> mealy_machines;
auto print_game = want_game ? auto print_game = want_game ?
[](const spot::twa_graph_ptr& game)->void [](const spot::twa_graph_ptr& game)->void
@ -359,27 +360,27 @@ namespace
for (; sub_f != sub_form.end(); ++sub_f, ++sub_o) for (; sub_f != sub_form.end(); ++sub_f, ++sub_o)
{ {
spot::strategy_like_t strat spot::mealy_like m_like
{ {
spot::strategy_like_t::realizability_code::UNKNOWN, spot::mealy_like::realizability_code::UNKNOWN,
nullptr, nullptr,
bddfalse bddfalse
}; };
// If we want to print a game, // If we want to print a game,
// we never use the direct approach // we never use the direct approach
if (!want_game) if (!want_game)
strat = m_like =
spot::try_create_direct_strategy(*sub_f, *sub_o, *gi); spot::try_create_direct_strategy(*sub_f, *sub_o, *gi);
switch (strat.success) switch (m_like.success)
{ {
case spot::strategy_like_t::realizability_code::UNREALIZABLE: case spot::mealy_like::realizability_code::UNREALIZABLE:
{ {
std::cout << "UNREALIZABLE" << std::endl; std::cout << "UNREALIZABLE" << std::endl;
safe_tot_time(); safe_tot_time();
return 1; return 1;
} }
case spot::strategy_like_t::realizability_code::UNKNOWN: case spot::mealy_like::realizability_code::UNKNOWN:
{ {
auto arena = spot::ltl_to_game(*sub_f, *sub_o, *gi); auto arena = spot::ltl_to_game(*sub_f, *sub_o, *gi);
if (gi->bv) if (gi->bv)
@ -404,55 +405,75 @@ namespace
// only if we need it // only if we need it
if (!opt_real) if (!opt_real)
{ {
spot::strategy_like_t sl; spot::mealy_like ml;
sl.success = ml.success =
spot::strategy_like_t::realizability_code::REALIZABLE_REGULAR; spot::mealy_like::realizability_code::REALIZABLE_REGULAR;
sl.strat_like = spot::create_strategy(arena, *gi); ml.mealy_like =
sl.glob_cond = bddfalse; spot::solved_game_to_separated_mealy(arena, *gi);
strategies.push_back(sl); ml.glob_cond = bddfalse;
mealy_machines.push_back(ml);
} }
break; break;
} }
case spot::strategy_like_t::realizability_code::REALIZABLE_REGULAR: case spot::mealy_like::realizability_code::REALIZABLE_REGULAR:
{ {
// the direct approach yielded a strategy // the direct approach yielded a strategy
// which can now be minimized // which can now be minimized
// We minimize only if we need it // We minimize only if we need it
assert(strat.strat_like && "Expected success but found no strat!"); assert(m_like.mealy_like && "Expected success but found no mealy!");
if (!opt_real) if (!opt_real)
{ {
spot::stopwatch sw_min; spot::stopwatch sw_direct;
sw_min.start(); sw_direct.start();
unsigned simplify = gi->minimize_lvl;
bool do_split = 3 <= simplify; if ((0 < gi->minimize_lvl) && (gi->minimize_lvl < 3))
if (do_split) // Uses reduction or not,
strat.strat_like = // both work with mealy machines (non-separated)
split_2step(strat.strat_like, reduce_mealy_here(m_like.mealy_like, gi->minimize_lvl == 2);
spot::get_synthesis_outputs(strat.strat_like),
false); auto delta = sw_direct.stop();
minimize_strategy_here(strat.strat_like, simplify);
if (do_split) sw_direct.start();
strat.strat_like = spot::unsplit_2step(strat.strat_like); // todo better algo here?
auto delta = sw_min.stop(); m_like.mealy_like =
split_2step(m_like.mealy_like,
spot::get_synthesis_outputs(m_like.mealy_like),
false);
if (gi->bv)
gi->bv->split_time += sw_direct.stop();
sw_direct.start();
if (gi->minimize_lvl >= 3)
{
sw_direct.start();
// actual minimization, works on split mealy
m_like.mealy_like = minimize_mealy(m_like.mealy_like,
gi->minimize_lvl - 4);
delta = sw_direct.stop();
}
// Unsplit to have separated mealy
m_like.mealy_like = unsplit_mealy(m_like.mealy_like);
if (gi->bv) if (gi->bv)
gi->bv->strat2aut_time += delta; gi->bv->strat2aut_time += delta;
if (gi->verbose_stream) if (gi->verbose_stream)
*gi->verbose_stream << "final strategy has " *gi->verbose_stream << "final strategy has "
<< strat.strat_like->num_states() << m_like.mealy_like->num_states()
<< " states and " << " states and "
<< strat.strat_like->num_edges() << m_like.mealy_like->num_edges()
<< " edges\n" << " edges\n"
<< "minimization took " << delta << "minimization took " << delta
<< " seconds\n"; << " seconds\n";
} }
SPOT_FALLTHROUGH; SPOT_FALLTHROUGH;
} }
case spot::strategy_like_t::realizability_code::REALIZABLE_DTGBA: case spot::mealy_like::realizability_code::REALIZABLE_DTGBA:
if (!opt_real) if (!opt_real)
strategies.push_back(strat); mealy_machines.push_back(m_like);
break; break;
default: default:
error(2, 0, "unexpected success code during strategy generation " error(2, 0, "unexpected success code during mealy machine generation "
"(please send bug report)"); "(please send bug report)");
} }
} }
@ -472,8 +493,9 @@ namespace
} }
// If we reach this line // If we reach this line
// a strategy was found for each subformula // a strategy was found for each subformula
assert(strategies.size() == sub_form.size() assert(mealy_machines.size() == sub_form.size()
&& "Strategies are missing"); && "There are subformula for which no mealy like object"
"has been created.");
spot::aig_ptr saig = nullptr; spot::aig_ptr saig = nullptr;
spot::twa_graph_ptr tot_strat = nullptr; spot::twa_graph_ptr tot_strat = nullptr;
@ -485,9 +507,9 @@ namespace
spot::stopwatch sw2; spot::stopwatch sw2;
if (gi->bv) if (gi->bv)
sw2.start(); sw2.start();
saig = spot::strategies_to_aig(strategies, opt_print_aiger, saig = spot::mealy_machines_to_aig(mealy_machines, opt_print_aiger,
input_aps, input_aps,
sub_outs_str); sub_outs_str);
if (gi->bv) if (gi->bv)
{ {
gi->bv->aig_time = sw2.stop(); gi->bv->aig_time = sw2.stop();
@ -507,14 +529,14 @@ namespace
else else
{ {
assert(std::all_of( assert(std::all_of(
strategies.begin(), strategies.end(), mealy_machines.begin(), mealy_machines.end(),
[](const auto& sl) [](const auto& ml)
{return sl.success == {return ml.success ==
spot::strategy_like_t::realizability_code::REALIZABLE_REGULAR; }) spot::mealy_like::realizability_code::REALIZABLE_REGULAR; })
&& "ltlsynt: Can not handle TGBA as strategy."); && "ltlsynt: Cannot handle TGBA as strategy.");
tot_strat = strategies.front().strat_like; tot_strat = mealy_machines.front().mealy_like;
for (size_t i = 1; i < strategies.size(); ++i) for (size_t i = 1; i < mealy_machines.size(); ++i)
tot_strat = spot::product(tot_strat, strategies[i].strat_like); tot_strat = spot::product(tot_strat, mealy_machines[i].mealy_like);
printer.print(tot_strat, timer_printer_dummy); printer.print(tot_strat, timer_printer_dummy);
} }

View file

@ -461,6 +461,8 @@ static void handle_any_exception()
%include <spot/misc/trival.hh> %include <spot/misc/trival.hh>
%implicitconv std::vector<spot::formula>; %implicitconv std::vector<spot::formula>;
%implicitconv std::vector<spot::twa_graph_ptr>;
%implicitconv std::vector<spot::const_twa_graph_ptr>;
%implicitconv spot::formula; %implicitconv spot::formula;
%implicitconv std::vector<bool>; %implicitconv std::vector<bool>;
@ -670,6 +672,7 @@ def state_is_accepting(self, src) -> "bool":
%template(scc_info_scc_edges) spot::internal::scc_edges<spot::digraph<spot::twa_graph_state, spot::twa_graph_edge_data> const, spot::internal::keep_all>; %template(scc_info_scc_edges) spot::internal::scc_edges<spot::digraph<spot::twa_graph_state, spot::twa_graph_edge_data> const, spot::internal::keep_all>;
%template(scc_info_inner_scc_edges) spot::internal::scc_edges<spot::digraph<spot::twa_graph_state, spot::twa_graph_edge_data> const, spot::internal::keep_inner_scc>; %template(scc_info_inner_scc_edges) spot::internal::scc_edges<spot::digraph<spot::twa_graph_state, spot::twa_graph_edge_data> const, spot::internal::keep_inner_scc>;
%template(vector_twa_graph) std::vector<spot::twa_graph_ptr>; %template(vector_twa_graph) std::vector<spot::twa_graph_ptr>;
%template(vector_const_twa_graph) std::vector<spot::const_twa_graph_ptr>;
%include <spot/twaalgos/strength.hh> %include <spot/twaalgos/strength.hh>
%include <spot/twaalgos/sccfilter.hh> %include <spot/twaalgos/sccfilter.hh>
%include <spot/twaalgos/stats.hh> %include <spot/twaalgos/stats.hh>

View file

@ -34,6 +34,7 @@
#include <spot/twa/twagraph.hh> #include <spot/twa/twagraph.hh>
#include <spot/misc/bddlt.hh> #include <spot/misc/bddlt.hh>
#include <spot/misc/minato.hh> #include <spot/misc/minato.hh>
#include <spot/twaalgos/mealy_machine.hh>
#include <spot/twaalgos/synthesis.hh> #include <spot/twaalgos/synthesis.hh>
#define STR(x) #x #define STR(x) #x
@ -1201,12 +1202,20 @@ namespace spot
}; };
std::vector<bdd> outbddvec; std::vector<bdd> outbddvec;
outbddvec.reserve(output_names_.size());
for (auto& ao : output_names_) for (auto& ao : output_names_)
outbddvec.push_back(bdd_ithvar(aut->register_ap(ao))); outbddvec.push_back(bdd_ithvar(aut->register_ap(ao)));
// Also register the ins // Also register the ins
for (auto& ai : input_names_) for (auto& ai : input_names_)
aut->register_ap(ai); aut->register_ap(ai);
// Set the named prop
set_synthesis_outputs(aut,
std::accumulate(outbddvec.begin(), outbddvec.end(),
(bdd) bddtrue,
[](const bdd& b1, const bdd& b2) -> bdd
{return b1 & b2; }));
std::vector<bdd> outcondbddvec; std::vector<bdd> outcondbddvec;
for (auto ov : outputs_) for (auto ov : outputs_)
outcondbddvec.push_back(aigvar2bdd(ov)); outcondbddvec.push_back(aigvar2bdd(ov));
@ -1387,8 +1396,6 @@ namespace
{ {
assert(e.cond != bddfalse); assert(e.cond != bddfalse);
bdd bout = bdd_exist(e.cond, all_inputs); bdd bout = bdd_exist(e.cond, all_inputs);
assert(((bout & bdd_existcomp(e.cond, all_inputs)) == e.cond) &&
"Precondition (in) & (out) == cond violated");
// low is good, dc is ok, high is bad // low is good, dc is ok, high is bad
this_outc[astrat.first->edge_number(e)] = this_outc[astrat.first->edge_number(e)] =
bdd_satoneshortest(bout, 0, 1, 5); bdd_satoneshortest(bout, 0, 1, 5);
@ -1447,9 +1454,9 @@ namespace
const std::vector<std::string>& unused_ins = {}, const std::vector<std::string>& unused_ins = {},
const std::vector<std::string>& unused_outs = {}) const std::vector<std::string>& unused_outs = {})
{ {
// The aiger circuit cannot encode the acceptance condition // The aiger circuit can currently noly encode separated mealy machines
// Test that the acceptance condition is true
for (auto&& astrat : strat_vec) for (const auto& astrat : strat_vec)
if (!astrat.first->acc().is_t()) if (!astrat.first->acc().is_t())
{ {
std::cerr << "Acc cond must be true not " << astrat.first->acc() std::cerr << "Acc cond must be true not " << astrat.first->acc()
@ -1457,6 +1464,10 @@ namespace
throw std::runtime_error("Cannot turn automaton into " throw std::runtime_error("Cannot turn automaton into "
"aiger circuit"); "aiger circuit");
} }
// This checks the acc again, but does more and is to
// costly for non-debug mode
assert(std::all_of(strat_vec.begin(), strat_vec.end(),
[](const auto& p){ return is_separated_mealy(p.first); }));
// get the propositions // get the propositions
std::vector<std::string> input_names; std::vector<std::string> input_names;
@ -1840,56 +1851,40 @@ namespace spot
{ {
aig_ptr aig_ptr
strategy_to_aig(const const_twa_graph_ptr& aut, const char* mode) mealy_machine_to_aig(const const_twa_graph_ptr& m, const char* mode)
{ {
if (!aut) if (!m)
throw std::runtime_error("aut cannot be null"); throw std::runtime_error("mealy_machine_to_aig(): "
"m cannot be null");
return auts_to_aiger({{aut, get_synthesis_outputs(aut)}}, mode); return auts_to_aiger({{m, get_synthesis_outputs(m)}}, mode);
} }
aig_ptr aig_ptr
strategies_to_aig(const std::vector<const_twa_graph_ptr>& strat_vec, mealy_machine_to_aig(const mealy_like& m, const char* mode)
const char *mode)
{ {
std::for_each(strat_vec.begin()+1, strat_vec.end(), if (m.success != mealy_like::realizability_code::REALIZABLE_REGULAR)
[usedbdd = strat_vec.at(0)->get_dict()](const auto& s) throw std::runtime_error("mealy_machine_to_aig(): "
{ "Can only handle regular mealy machine, TBD");
if (usedbdd != s->get_dict())
throw std::runtime_error("All strategies have to "
"share a bdd_dict!\n");
});
std::vector<std::pair<const_twa_graph_ptr, bdd>> new_vec; return mealy_machine_to_aig(m.mealy_like, mode);
new_vec.reserve(strat_vec.size());
bdd all_outputs = bddtrue;
for (auto& astrat : strat_vec)
{
bdd this_outputs = get_synthesis_outputs(astrat);
// Check if outs do not overlap
if (bdd_and(bdd_not(this_outputs), all_outputs) == bddfalse)
throw std::runtime_error("\"outs\" of strategies are not "
"distinct!.\n");
all_outputs &= this_outputs;
new_vec.emplace_back(astrat, this_outputs);
}
return auts_to_aiger(new_vec, mode);
} }
aig_ptr aig_ptr
strategy_to_aig(const twa_graph_ptr &aut, const char *mode, mealy_machine_to_aig(const twa_graph_ptr &m, const char *mode,
const std::vector<std::string>& ins, const std::vector<std::string>& ins,
const std::vector<std::string>& outs) const std::vector<std::string>& outs)
{ {
if (!aut) if (!m)
throw std::runtime_error("aut cannot be null"); throw std::runtime_error("mealy_machine_to_aig(): "
"m cannot be null");
// Make sure ins and outs are disjoint // Make sure ins and outs are disjoint
{ {
std::vector<std::string> all_ap = ins; std::vector<std::string> all_ap = ins;
all_ap.insert(all_ap.end(), outs.begin(), outs.end()); all_ap.insert(all_ap.end(), outs.begin(), outs.end());
check_double_names(all_ap, "Atomic propositions appears in input " check_double_names(all_ap, "mealy_machine_to_aig(): "
"Atomic propositions appears in input "
"and output propositions; "); "and output propositions; ");
} }
// Check if there exist outs or ins that are not used by the // Check if there exist outs or ins that are not used by the
@ -1900,7 +1895,7 @@ namespace spot
std::vector<std::string> unused_ins; std::vector<std::string> unused_ins;
{ {
std::set<std::string> used_aps; std::set<std::string> used_aps;
for (const auto& f : aut->ap()) for (const auto& f : m->ap())
used_aps.insert(f.ap_name()); used_aps.insert(f.ap_name());
for (const auto& ao : outs) for (const auto& ao : outs)
if (!used_aps.count(ao)) if (!used_aps.count(ao))
@ -1910,25 +1905,90 @@ namespace spot
unused_ins.push_back(ai); unused_ins.push_back(ai);
} }
// todo Some additional checks? // todo Some additional checks?
return auts_to_aiger({{aut, get_synthesis_outputs(aut)}}, mode, return auts_to_aiger({{m, get_synthesis_outputs(m)}}, mode,
unused_ins, unused_outs); unused_ins, unused_outs);
} }
aig_ptr
mealy_machine_to_aig(mealy_like& m, const char *mode,
const std::vector<std::string>& ins,
const std::vector<std::string>& outs)
{
if (m.success != mealy_like::realizability_code::REALIZABLE_REGULAR)
throw std::runtime_error("mealy_machine_to_aig(): "
"Can only handle regular mealy machine, TBD");
return mealy_machine_to_aig(m.mealy_like, mode, ins, outs);
}
aig_ptr
mealy_machines_to_aig(const std::vector<const_twa_graph_ptr>& m_vec,
const char *mode)
{
std::for_each(m_vec.begin()+1, m_vec.end(),
[usedbdd = m_vec.at(0)->get_dict()](const auto& s)
{
if (usedbdd != s->get_dict())
throw std::runtime_error("mealy_machines_to_aig(): "
"All machines have to "
"share a bdd_dict!\n");
});
std::vector<std::pair<const_twa_graph_ptr, bdd>> new_vec;
new_vec.reserve(m_vec.size());
bdd all_outputs = bddtrue;
for (auto& am : m_vec)
{
bdd this_outputs = get_synthesis_outputs(am);
// Check if outs do not overlap
if (bdd_and(bdd_not(this_outputs), all_outputs) == bddfalse)
throw std::runtime_error("mealy_machines_to_aig(): "
"\"outs\" of the machines are not "
"distinct!.\n");
all_outputs &= this_outputs;
new_vec.emplace_back(am, this_outputs);
}
return auts_to_aiger(new_vec, mode);
}
aig_ptr
mealy_machines_to_aig(const std::vector<mealy_like>& m_vec,
const char *mode)
{
if (std::any_of(m_vec.cbegin(), m_vec.cend(),
[](const auto& m)
{return m.success !=
mealy_like::realizability_code::REALIZABLE_REGULAR; }))
throw std::runtime_error("mealy_machines_to_aig(): "
"Can only handle regular mealy machine for "
"the moment, TBD");
auto new_vec = std::vector<const_twa_graph_ptr>();
new_vec.reserve(m_vec.size());
std::transform(m_vec.cbegin(), m_vec.cend(),
std::back_inserter(new_vec),
[](const auto& m){return m.mealy_like; });
return mealy_machines_to_aig(new_vec, mode);
}
// Note: This ignores the named property // Note: This ignores the named property
aig_ptr aig_ptr
strategies_to_aig(const std::vector<twa_graph_ptr>& strat_vec, mealy_machines_to_aig(const std::vector<twa_graph_ptr>& m_vec,
const char *mode, const char *mode,
const std::vector<std::string>& ins, const std::vector<std::string>& ins,
const std::vector<std::vector<std::string>>& outs) const std::vector<std::vector<std::string>>& outs)
{ {
if (strat_vec.size() != outs.size()) if (m_vec.size() != outs.size())
throw std::runtime_error("Expected as many outs as strategies!\n"); throw std::runtime_error("mealy_machines_to_aig(): "
"Expected as many outs as strategies!\n");
std::for_each(strat_vec.begin()+1, strat_vec.end(), std::for_each(m_vec.begin()+1, m_vec.end(),
[usedbdd = strat_vec.at(0)->get_dict()](auto&& it) [usedbdd = m_vec.at(0)->get_dict()](auto&& it)
{ {
if (usedbdd != it->get_dict()) if (usedbdd != it->get_dict())
throw std::runtime_error("All strategies have to " throw std::runtime_error("mealy_machines_to_aig(): "
"All strategies have to "
"share a bdd_dict!\n"); "share a bdd_dict!\n");
}); });
@ -1944,17 +2004,17 @@ namespace spot
} }
std::vector<std::pair<const_twa_graph_ptr, bdd>> new_vec; std::vector<std::pair<const_twa_graph_ptr, bdd>> new_vec;
new_vec.reserve(strat_vec.size()); new_vec.reserve(m_vec.size());
std::set<std::string> used_aps; std::set<std::string> used_aps;
for (size_t i = 0; i < strat_vec.size(); ++i) for (size_t i = 0; i < m_vec.size(); ++i)
{ {
for (const auto& f : strat_vec[i]->ap()) for (const auto& f : m_vec[i]->ap())
used_aps.insert(f.ap_name()); used_aps.insert(f.ap_name());
// todo Some additional checks? // todo Some additional checks?
new_vec.emplace_back(strat_vec[i], new_vec.emplace_back(m_vec[i],
get_synthesis_outputs(strat_vec[i])); get_synthesis_outputs(m_vec[i]));
} }
// Check if there exist outs or ins that are not used by the // Check if there exist outs or ins that are not used by the
@ -1975,15 +2035,15 @@ namespace spot
} }
aig_ptr aig_ptr
strategies_to_aig(const std::vector<strategy_like_t>& strat_vec, mealy_machines_to_aig(const std::vector<mealy_like>& strat_vec,
const char *mode, const char* mode,
const std::vector<std::string>& ins, const std::vector<std::string>& ins,
const std::vector<std::vector<std::string>>& outs) const std::vector<std::vector<std::string>>& outs)
{ {
// todo extend to TGBA and possibly others // todo extend to TGBA and possibly others
const unsigned ns = strat_vec.size(); const unsigned ns = strat_vec.size();
std::vector<twa_graph_ptr> strategies; std::vector<twa_graph_ptr> m_machines;
strategies.reserve(ns); m_machines.reserve(ns);
std::vector<std::vector<std::string>> outs_used; std::vector<std::vector<std::string>> outs_used;
outs_used.reserve(ns); outs_used.reserve(ns);
@ -1991,27 +2051,28 @@ namespace spot
{ {
switch (strat_vec[i].success) switch (strat_vec[i].success)
{ {
case strategy_like_t::realizability_code::UNREALIZABLE: case mealy_like::realizability_code::UNREALIZABLE:
throw std::runtime_error("strategies_to_aig(): Partial strat is " throw std::runtime_error("mealy_machines_to_aig(): One of the "
"not feasible!"); "mealy like machines is not realizable.");
case strategy_like_t::realizability_code::UNKNOWN: case mealy_like::realizability_code::UNKNOWN:
throw std::runtime_error("strategies_to_aig(): Partial strat has " throw std::runtime_error("mealy_machines_to_aig(): One of the"
"unknown status!"); "mealy like objects has "
case strategy_like_t::realizability_code::REALIZABLE_REGULAR: "status \"unkwnown\"");
case mealy_like::realizability_code::REALIZABLE_REGULAR:
{ {
strategies.push_back(strat_vec[i].strat_like); m_machines.push_back(strat_vec[i].mealy_like);
outs_used.push_back(outs[i]); outs_used.push_back(outs[i]);
break; break;
} }
case strategy_like_t::realizability_code::REALIZABLE_DTGBA: case mealy_like::realizability_code::REALIZABLE_DTGBA:
throw std::runtime_error("strategies_to_aig(): TGBA not " throw std::runtime_error("mealy_machines_to_aig(): TGBA not "
"yet supported."); "yet supported - TBD");
default: default:
throw std::runtime_error("strategies_to_aig(): Unknown " throw std::runtime_error("mealy_machines_to_aig(): Unknown "
"success identifier."); "success identifier.");
} }
} }
return strategies_to_aig(strategies, mode, ins, outs_used); return mealy_machines_to_aig(m_machines, mode, ins, outs_used);
} }
std::ostream & std::ostream &
@ -2073,7 +2134,7 @@ namespace spot
print_aiger(std::ostream& os, const const_twa_graph_ptr& aut, print_aiger(std::ostream& os, const const_twa_graph_ptr& aut,
const char* mode) const char* mode)
{ {
print_aiger(os, strategy_to_aig(aut, mode)); print_aiger(os, mealy_machine_to_aig(aut, mode));
return os; return os;
} }
} }

View file

@ -36,7 +36,7 @@
namespace spot namespace spot
{ {
// Forward for synthesis // Forward for synthesis
struct strategy_like_t; struct mealy_like;
class aig; class aig;
@ -415,8 +415,8 @@ namespace spot
}; };
/// \ingroup synthesis /// \ingroup synthesis
/// \brief Convert a strategy into an aig relying on the transformation /// \brief Convert a mealy (like) machine into an aig relying on
/// described by \a mode. /// the transformation described by \a mode.
/// \param mode This param has to be of the form /// \param mode This param has to be of the form
/// `ite|isop|both [+dc][+ud][+sub0|+sub1|+sub2]` /// `ite|isop|both [+dc][+ud][+sub0|+sub1|+sub2]`
/// Where `ite` means encoded via if-then-else normal form, /// Where `ite` means encoded via if-then-else normal form,
@ -437,11 +437,18 @@ namespace spot
/// outs are guaranteed to appear in the aiger circuit. /// outs are guaranteed to appear in the aiger circuit.
///@{ ///@{
SPOT_API aig_ptr SPOT_API aig_ptr
strategy_to_aig(const const_twa_graph_ptr& aut, const char* mode); mealy_machine_to_aig(const const_twa_graph_ptr& m, const char* mode);
SPOT_API aig_ptr SPOT_API aig_ptr
strategy_to_aig(const twa_graph_ptr& aut, const char *mode, mealy_machine_to_aig(const twa_graph_ptr& m, const char *mode,
const std::vector<std::string>& ins, const std::vector<std::string>& ins,
const std::vector<std::string>& outs); const std::vector<std::string>& outs);
SPOT_API aig_ptr
mealy_machine_to_aig(const mealy_like& m, const char* mode);
SPOT_API aig_ptr
mealy_machine_to_aig(mealy_like& m, const char *mode,
const std::vector<std::string>& ins,
const std::vector<std::string>& outs);
///@} ///@}
/// \ingroup synthesis /// \ingroup synthesis
@ -459,18 +466,21 @@ namespace spot
/// guaranteed to appear in the aiger circuit. /// guaranteed to appear in the aiger circuit.
/// @{ /// @{
SPOT_API aig_ptr SPOT_API aig_ptr
strategies_to_aig(const std::vector<const_twa_graph_ptr>& strat_vec, mealy_machines_to_aig(const std::vector<const_twa_graph_ptr>& m_vec,
const char* mode); const char* mode);
SPOT_API aig_ptr SPOT_API aig_ptr
strategies_to_aig(const std::vector<twa_graph_ptr>& strat_vec, mealy_machines_to_aig(const std::vector<mealy_like>& m_vec,
const char* mode, const char* mode);
const std::vector<std::string>& ins,
const std::vector<std::vector<std::string>>& outs);
SPOT_API aig_ptr SPOT_API aig_ptr
strategies_to_aig(const std::vector<strategy_like_t>& strat_vec, mealy_machines_to_aig(const std::vector<twa_graph_ptr>& m_vec,
const char* mode, const char* mode,
const std::vector<std::string>& ins, const std::vector<std::string>& ins,
const std::vector<std::vector<std::string>>& outs); const std::vector<std::vector<std::string>>& outs);
SPOT_API aig_ptr
mealy_machines_to_aig(const std::vector<mealy_like>& m_vec,
const char* mode,
const std::vector<std::string>& ins,
const std::vector<std::vector<std::string>>& outs);
/// @} /// @}

View file

@ -33,6 +33,7 @@
#include <spot/misc/timer.hh> #include <spot/misc/timer.hh>
#include <spot/misc/minato.hh> #include <spot/misc/minato.hh>
#include <spot/twaalgos/game.hh> #include <spot/twaalgos/game.hh>
#include <spot/twaalgos/hoa.hh>
#include <spot/twaalgos/synthesis.hh> #include <spot/twaalgos/synthesis.hh>
#include <picosat/picosat.h> #include <picosat/picosat.h>
@ -52,11 +53,245 @@
# define dotimeprint while (0) std::cerr # define dotimeprint while (0) std::cerr
#endif #endif
namespace
{
bool is_deterministic_(const std::vector<bdd>& ins)
{
const unsigned n_ins = ins.size();
for (unsigned idx1 = 0; idx1 < n_ins - 1; ++idx1)
for (unsigned idx2 = idx1 + 1; idx2 < n_ins; ++idx2)
if (bdd_have_common_assignment(ins[idx1], ins[idx2]))
return false;
return true;
}
}
namespace spot
{
bool
is_mealy(const const_twa_graph_ptr& m)
{
if (!m->acc().is_t())
{
trace << "is_mealy(): Mealy machines must have "
"true acceptance condition.\n";
return false;
}
if (!m->get_named_prop<bdd>("synthesis-outputs"))
{
trace << "is_mealy(): \"synthesis-outputs\" not found!\n";
return false;
}
return true;
}
bool
is_separated_mealy(const const_twa_graph_ptr& m)
{
if (!is_mealy(m))
return false;
auto outs = get_synthesis_outputs(m);
for (const auto& e : m->edges())
if ((bdd_exist(e.cond, outs) & bdd_existcomp(e.cond, outs)) != e.cond)
{
trace << "is_separated_mealy_machine(): Edge number "
<< m->edge_number(e) << " from " << e.src << " to " << e.dst
<< " with " << e.cond << " is not separated!\n";
return false;
}
return true;
}
bool
is_split_mealy(const const_twa_graph_ptr& m)
{
if (!is_mealy(m))
return false;
if (m->get_named_prop<region_t>("state-player") == nullptr)
{
trace << "is_split_mealy(): Split mealy machine must define the named "
"property \"state-player\"!\n";
}
auto sp = get_state_players(m);
if (sp.size() != m->num_states())
{
trace << "\"state-player\" has not the same size as the "
"automaton!\n";
return false;
}
if (sp[m->get_init_state_number()])
{
trace << "is_split_mealy(): Initial state must be owned by env!\n";
return false;
}
auto outs = get_synthesis_outputs(m);
for (const auto& e : m->edges())
{
if (sp[e.src] == sp[e.dst])
{
trace << "is_split_mealy(): Edge number "
<< m->edge_number(e) << " from " << e.src << " to " << e.dst
<< " is not alternating!\n";
return false;
}
if (sp[e.src] && (bdd_exist(e.cond, outs) != bddtrue))
{
trace << "is_split_mealy(): Edge number "
<< m->edge_number(e) << " from " << e.src << " to " << e.dst
<< " depends on inputs, but should be labeled by outputs!\n";
return false;
}
if (!sp[e.src] && (bdd_existcomp(e.cond, outs) != bddtrue))
{
trace << "is_split_mealy(): Edge number "
<< m->edge_number(e) << " from " << e.src << " to " << e.dst
<< " depends on outputs, but should be labeled by inputs!\n";
return false;
}
}
return true;
}
bool
is_input_deterministic_mealy(const const_twa_graph_ptr& m)
{
if (!is_mealy(m))
return false;
const unsigned N = m->num_states();
auto sp_ptr = m->get_named_prop<region_t>("state-player");
// Unsplit mealy -> sp_ptr == nullptr
if (sp_ptr && !is_split_mealy(m))
return false;
auto outs = get_synthesis_outputs(m);
std::vector<bdd> ins;
for (unsigned s = 0; s < N; ++s)
{
if (sp_ptr && (*sp_ptr)[s])
continue;
ins.clear();
for (const auto& e : m->out(s))
ins.push_back(sp_ptr ? e.cond
: bdd_exist(e.cond, outs));
if (!is_deterministic_(ins))
{
trace << "is_input_deterministic_mealy(): State number "
<< s << " is not input determinisc!\n";
return false;
}
}
return true;
}
void
split_separated_mealy_here(const twa_graph_ptr& m)
{
assert(is_mealy(m));
auto output_bdd = get_synthesis_outputs(m);
struct dst_cond_color_t
{
std::pair<unsigned, int> dst_cond;
acc_cond::mark_t color;
};
auto hasher = [](const dst_cond_color_t& dcc) noexcept
{
return dcc.color.hash() ^ pair_hash()(dcc.dst_cond);
};
auto equal = [](const dst_cond_color_t& dcc1,
const dst_cond_color_t& dcc2) noexcept
{
return (dcc1.dst_cond == dcc2.dst_cond)
&& (dcc1.color == dcc2.color);
};
std::unordered_map<dst_cond_color_t, unsigned,
decltype(hasher),
decltype(equal)> player_map(m->num_edges(),
hasher, equal);
auto get_ps = [&](unsigned dst, const bdd& ocond,
acc_cond::mark_t color)
{
dst_cond_color_t key{std::make_pair(dst, ocond.id()),
color};
auto [it, inserted] =
player_map.try_emplace(key, m->num_states());
if (!inserted)
return it->second;
unsigned ns = m->new_state();
assert(ns == it->second);
m->new_edge(ns, dst, ocond, color);
return ns;
};
unsigned ne = m->edge_vector().size();
for (unsigned eidx = 1; eidx < ne; ++eidx)
{
const auto& e = m->edge_storage(eidx);
if (e.next_succ == eidx) // dead edge
continue;
bdd incond = bdd_exist(e.cond, output_bdd);
bdd outcond = bdd_existcomp(e.cond, output_bdd);
assert(((incond&outcond) == e.cond) && "Precondition violated");
// Create new state and trans
// this may invalidate "e".
unsigned new_dst = get_ps(e.dst, outcond, e.acc);
// redirect
auto& e2 = m->edge_storage(eidx);
e2.dst = new_dst;
e2.cond = incond;
}
auto* sp_ptr = m->get_or_set_named_prop<region_t>("state-player");
sp_ptr->resize(m->num_states());
std::fill(sp_ptr->begin(), sp_ptr->end(), false);
for (const auto& eit : player_map)
(*sp_ptr)[eit.second] = true;
//Done
}
twa_graph_ptr
split_separated_mealy(const const_twa_graph_ptr& m)
{
assert(is_mealy((m)));
auto m2 = make_twa_graph(m, twa::prop_set::all());
m2->copy_acceptance_of(m);
set_synthesis_outputs(m2, get_synthesis_outputs(m));
split_separated_mealy_here(m2);
return m2;
}
twa_graph_ptr
unsplit_mealy(const const_twa_graph_ptr& m)
{
assert(is_split_mealy(m));
return unsplit_2step(m);
}
}
namespace namespace
{ {
// Anonymous for minimize_mealy_fast // Anonymous for reduce_mealy
using namespace spot; using namespace spot;
// Used to get the signature of the state. // Used to get the signature of the state.
@ -478,84 +713,33 @@ namespace
namespace spot namespace spot
{ {
twa_graph_ptr minimize_mealy_fast(const const_twa_graph_ptr& mm, twa_graph_ptr reduce_mealy(const const_twa_graph_ptr& mm,
bool use_inclusion) bool output_assignment)
{ {
bool orig_is_split = true; assert(is_mealy(mm));
bdd* outsptr = nullptr; if (mm->get_named_prop<std::vector<bool>>("state-player"))
bdd outs = bddfalse; throw std::runtime_error("reduce_mealy(): "
"Only works on unsplit machines.\n");
twa_graph_ptr mmc; auto mmc = make_twa_graph(mm, twa::prop_set::all());
if (mm->get_named_prop<std::vector<bool>>("state-player") != nullptr) mmc->copy_ap_of(mm);
{ mmc->copy_acceptance_of(mm);
outsptr = mm->get_named_prop<bdd>("synthesis-outputs"); set_synthesis_outputs(mmc, get_synthesis_outputs(mm));
assert(outsptr && "If the original strat is split, "
"we need \"synthesis-outputs\".");
outs = *outsptr;
mmc = unsplit_2step(mm);
}
else
{
mmc = make_twa_graph(mm, twa::prop_set::all());
mmc->copy_ap_of(mm);
mmc->copy_acceptance_of(mm);
orig_is_split = false;
}
minimize_mealy_fast_here(mmc, use_inclusion); reduce_mealy_here(mmc, output_assignment);
// Split if the initial aut was split
if (orig_is_split)
{
mmc = split_2step(mmc, outs, false);
bool orig_init_player =
mm->get_named_prop<std::vector<bool>>("state-player")
->at(mm->get_init_state_number());
alternate_players(mmc, orig_init_player, false);
}
// Try to set outputs
if (outsptr)
mmc->set_named_prop("synthesis-outputs", new bdd(outs));
assert(is_mealy(mmc));
return mmc; return mmc;
} }
void minimize_mealy_fast_here(twa_graph_ptr& mm, bool use_inclusion) void reduce_mealy_here(twa_graph_ptr& mm, bool output_assignment)
{ {
assert(is_mealy(mm));
bool orig_is_split = false;
bdd* outsptr = nullptr;
bdd outs = bddfalse;
auto sp_ptr = mm->get_named_prop<std::vector<bool>>("state-player");
bool init_player = sp_ptr ? sp_ptr->at(mm->get_init_state_number())
: false;
// Only consider infinite runs // Only consider infinite runs
mm->purge_dead_states(); mm->purge_dead_states();
if (mm->get_named_prop<std::vector<bool>>("state-player") != nullptr) auto repr = get_repres(mm, output_assignment);
{
// Check if done
if (std::count(sp_ptr->begin(), sp_ptr->end(), false) == 1)
return;
outsptr = mm->get_named_prop<bdd>("synthesis-outputs");
assert(outsptr && "If the original strat is split, "
"we need \"synthesis-outputs\".");
outs = *outsptr;
init_player =
mm->get_named_prop<std::vector<bool>>("state-player")->at(
mm->get_init_state_number());
mm = unsplit_2step(mm);
orig_is_split = true;
}
else
// Check if done
if (mm->num_states() == 1)
return;
auto repr = get_repres(mm, use_inclusion);
if (repr[0] == -1U) if (repr[0] == -1U)
return; return;
@ -567,25 +751,20 @@ namespace spot
std::vector<bool> done(mm->num_states(), false); std::vector<bool> done(mm->num_states(), false);
todo.emplace(init); todo.emplace(init);
while (!todo.empty()) while (!todo.empty())
{
auto current = todo.top();
todo.pop();
done[current] = true;
for (auto& e : mm->out(current))
{ {
auto repr_dst = repr[e.dst]; auto current = todo.top();
e.dst = repr_dst; todo.pop();
if (!done[repr_dst]) done[current] = true;
todo.emplace(repr_dst); for (auto& e : mm->out(current))
{
auto repr_dst = repr[e.dst];
e.dst = repr_dst;
if (!done[repr_dst])
todo.emplace(repr_dst);
}
} }
}
mm->purge_unreachable_states(); mm->purge_unreachable_states();
if (orig_is_split) assert(is_mealy(mm));
{
mm = split_2step(mm, outs, false);
alternate_players(mm, init_player, false);
mm->set_named_prop("synthesis-outputs", new bdd(outs));
}
} }
} }
@ -639,7 +818,7 @@ namespace
template <class CONT, class PRED> template <class CONT, class PRED>
size_t find_first_index_of(const CONT& c, PRED pred) size_t find_first_index_of(const CONT& c, PRED pred)
{ {
size_t s = c.size(); const size_t s = c.size();
for (unsigned i = 0; i < s; ++i) for (unsigned i = 0; i < s; ++i)
if (pred(c[i])) if (pred(c[i]))
return i; return i;
@ -874,8 +1053,8 @@ namespace
std::vector<bool> spnew(n_new, true); std::vector<bool> spnew(n_new, true);
std::fill(spnew.begin(), spnew.begin()+n_env, false); std::fill(spnew.begin(), spnew.begin()+n_env, false);
omm->set_named_prop("state-player", set_state_players(omm, std::move(spnew));
new std::vector<bool>(std::move(spnew))); set_synthesis_outputs(omm, get_synthesis_outputs(mm));
// Make sure we have a proper strategy, // Make sure we have a proper strategy,
// that is each player state has only one successor // that is each player state has only one successor
@ -1217,7 +1396,7 @@ namespace
#ifdef TRACE #ifdef TRACE
trace << "We found " << n_group << " groups.\n"; trace << "We found " << n_group << " groups.\n";
for (unsigned s = 0; s < n_env; ++s) for (unsigned s = 0; s < n_env; ++s)
trace << s << " : " << which_group[s] << '\n'; trace << s << " : " << which_group.at(s) << '\n';
#endif #endif
return std::make_pair(n_group, which_group); return std::make_pair(n_group, which_group);
} }
@ -1640,7 +1819,7 @@ namespace
split_mmw->get_graph().chain_edges_(); split_mmw->get_graph().chain_edges_();
#ifdef TRACE #ifdef TRACE
trace << "Orig split aut\n"; trace << "Orig split aut\n";
print_hoa(std::cerr, split_mmw) << '\n'; print_hoa(std::cerr, mmw) << '\n';
{ {
auto ss = make_twa_graph(split_mmw, twa::prop_set::all()); auto ss = make_twa_graph(split_mmw, twa::prop_set::all());
for (unsigned group = 0; group < red.n_groups; ++group) for (unsigned group = 0; group < red.n_groups; ++group)
@ -1652,10 +1831,9 @@ namespace
bdd_ithvar(ss->register_ap("g"+std::to_string(group) bdd_ithvar(ss->register_ap("g"+std::to_string(group)
+"e"+std::to_string(i)))); +"e"+std::to_string(i))));
} }
const unsigned ns = ss->num_states(); for (unsigned s = 0; s < n_env; ++s)
for (unsigned s = 0; s < ns; ++s)
{ {
if (red.which_group[s] != group) if (red.which_group.at(s) != group)
continue; continue;
for (auto& e : ss->out(s)) for (auto& e : ss->out(s))
e.cond = e.cond =
@ -1676,8 +1854,8 @@ namespace
for (const auto& idx_vec : red.bisim_letters[i]) for (const auto& idx_vec : red.bisim_letters[i])
trace << red.minimal_letters_vec[i][idx_vec.front()].id() << '\n'; trace << red.minimal_letters_vec[i][idx_vec.front()].id() << '\n';
trace << "ids in the edge of the group\n"; trace << "ids in the edge of the group\n";
for (unsigned s = 0; s < split_mmw->num_states(); ++s) for (unsigned s = 0; s < n_env; ++s)
if (red.which_group[s] == i) if (red.which_group.at(s) == i)
for (const auto& e : split_mmw->out(s)) for (const auto& e : split_mmw->out(s))
trace << e.src << "->" << e.dst << ':' << e.cond.id() << '\n'; trace << e.src << "->" << e.dst << ':' << e.cond.id() << '\n';
} }
@ -2763,64 +2941,14 @@ namespace
return gmm2cond; return gmm2cond;
} }
void cstr_unsplit(twa_graph_ptr& minmach, void cstr_split_mealy(twa_graph_ptr& minmach,
const reduced_alphabet_t& red, const reduced_alphabet_t& red,
const std::vector<std::pair<unsigned, const std::vector<std::pair<unsigned,
std::vector<bool>>>& std::vector<bool>>>&
x_in_class, x_in_class,
std::unordered_map<iaj_t, bdd, std::unordered_map<iaj_t, bdd,
decltype(iaj_hash), decltype(iaj_hash),
decltype(iaj_eq)>& used_ziaj_map) decltype(iaj_eq)>& used_ziaj_map)
{
const unsigned n_groups = red.n_groups;
// For each minimal letter, create the (input) condition that it represents
// This has to be created for each minimal letters
// and might be shared between alphabets
std::vector<std::vector<bdd>> gmm2cond
= comp_represented_cond(red);
// Use a new map to reduce the number of edges created
// iaj.i: src_class or 0
// iaj.a: id of out condition
// iaj.j: dst_class
std::unordered_map<iaj_t, bdd,
decltype(iaj_hash), decltype(iaj_eq)>
edge_map(2*used_ziaj_map.size(), iaj_hash, iaj_eq);
for (unsigned group = 0; group < n_groups; ++group)
{
edge_map.clear();
// Attention the a in used_ziaj corresponds to the index
for (const auto& [iaj, outcond] : used_ziaj_map)
{
if (x_in_class[iaj.i].first != group)
continue;
const bdd& repr_cond = gmm2cond[group].at(iaj.a);
// Check if there already exists a suitable edge
auto [itedge, inserted] =
edge_map.try_emplace({iaj.i, (unsigned) outcond.id(), iaj.j},
repr_cond);
if (!inserted)
itedge->second |= repr_cond;
}
// Create the actual edges
for (const auto& ep : edge_map)
minmach->new_edge(ep.first.i, ep.first.j,
ep.second & bdd_from_int((int) ep.first.a));
}
}
void cstr_keep_split(twa_graph_ptr& minmach,
const reduced_alphabet_t& red,
const std::vector<std::pair<unsigned,
std::vector<bool>>>&
x_in_class,
std::unordered_map<iaj_t, bdd,
decltype(iaj_hash),
decltype(iaj_eq)>& used_ziaj_map)
{ {
const unsigned n_env_states = minmach->num_states(); const unsigned n_env_states = minmach->num_states();
// For each minimal letter, create the (input) condition that it represents // For each minimal letter, create the (input) condition that it represents
@ -2895,11 +3023,10 @@ namespace
} }
// Set the state-player // Set the state-player
std::vector<bool> sp(minmach->num_states(), true); auto* sp = new std::vector<bool>(minmach->num_states(), true);
for (unsigned i = 0; i < n_env_states; ++i) for (unsigned i = 0; i < n_env_states; ++i)
sp[i] = false; (*sp)[i] = false;
minmach->set_named_prop("state-player", minmach->set_named_prop("state-player", sp);
new std::vector<bool>(std::move(sp)));
} }
// This function refines the sat constraints in case the // This function refines the sat constraints in case the
@ -3185,7 +3312,6 @@ namespace
const reduced_alphabet_t& red, const reduced_alphabet_t& red,
const part_sol_t& psol, const part_sol_t& psol,
const unsigned n_env, const unsigned n_env,
bool keep_split,
stopwatch& sw) stopwatch& sw)
{ {
const auto& psolv = psol.psol; const auto& psolv = psol.psol;
@ -3432,10 +3558,7 @@ namespace
continue; //retry continue; //retry
} }
if (keep_split) cstr_split_mealy(minmach, red, x_in_class, used_ziaj_map);
cstr_keep_split(minmach, red, x_in_class, used_ziaj_map);
else
cstr_unsplit(minmach, red, x_in_class, used_ziaj_map);
// todo: What is the impact of chosing one of the possibilities // todo: What is the impact of chosing one of the possibilities
minmach->set_init_state(init_class_v.front()); minmach->set_init_state(init_class_v.front());
@ -3447,26 +3570,17 @@ namespace
namespace spot namespace spot
{ {
twa_graph_ptr minimize_mealy(const const_twa_graph_ptr& mm, twa_graph_ptr minimize_mealy(const const_twa_graph_ptr& mm,
int premin, bool keep_split) int premin)
{ {
assert(is_split_mealy(mm));
stopwatch sw; stopwatch sw;
sw.start(); sw.start();
if ((premin < -1) || (premin > 1)) if ((premin < -1) || (premin > 1))
throw std::runtime_error("premin has to be -1, 0 or 1"); throw std::runtime_error("premin has to be -1, 0 or 1");
if (!mm->acc().is_t())
throw std::runtime_error("Mealy machine needs true acceptance!\n");
auto orig_spref = get_state_players(mm); auto orig_spref = get_state_players(mm);
if (orig_spref.size() != mm->num_states())
throw std::runtime_error("Inconsistent \"state-player\"");
if (std::any_of(mm->edges().begin(), mm->edges().end(),
[&](const auto& e){return orig_spref[e.src]
== orig_spref[e.dst]; }))
throw std::runtime_error("Arena is not alternating!");
// Check if finite traces exist // Check if finite traces exist
// If so, deactivate fast minimization // If so, deactivate fast minimization
@ -3491,10 +3605,18 @@ namespace spot
if (premin == -1) if (premin == -1)
return mm; return mm;
else else
return minimize_mealy_fast(mm, premin == 1); {
// We have a split machine -> unsplit then resplit,
// as reduce mealy works on separated
auto mms = unsplit_mealy(mm);
reduce_mealy_here(mms, premin == 1);
split_separated_mealy_here(mms);
return mms;
}
}; };
const_twa_graph_ptr mmw = do_premin(); const_twa_graph_ptr mmw = do_premin();
assert(is_split_mealy(mmw));
dotimeprint << "Done premin " << sw.stop() << '\n'; dotimeprint << "Done premin " << sw.stop() << '\n';
// 0 -> "Env" next is input props // 0 -> "Env" next is input props
@ -3508,6 +3630,7 @@ namespace spot
// second black : player-states // second black : player-states
unsigned n_env = -1u; unsigned n_env = -1u;
std::tie(mmw, n_env) = reorganize_mm(mmw, spref); std::tie(mmw, n_env) = reorganize_mm(mmw, spref);
assert(is_split_mealy(mmw));
#ifdef TRACE #ifdef TRACE
print_hoa(std::cerr, mmw); print_hoa(std::cerr, mmw);
#endif #endif
@ -3530,15 +3653,9 @@ namespace spot
auto early_exit = [&]() auto early_exit = [&]()
{ {
if (keep_split) // Always keep machines split
return std::const_pointer_cast<twa_graph>(mmw); assert(is_split_mealy_specialization(mm, mmw));
else return std::const_pointer_cast<twa_graph>(mmw);
{
auto mmw_u = unsplit_2step(mmw);
// todo correct unsplit to avoid this
mmw_u->purge_unreachable_states();
return mmw_u;
}
}; };
// If the partial solution has the same number of // If the partial solution has the same number of
@ -3576,7 +3693,6 @@ namespace spot
minmachine = try_build_min_machine(mm_pb, mmw, minmachine = try_build_min_machine(mm_pb, mmw,
reduced_alphabet, reduced_alphabet,
partsol, n_env, partsol, n_env,
keep_split,
sw); sw);
dotimeprint << "Done try_build " << n_classes dotimeprint << "Done try_build " << n_classes
<< " - " << sw.stop() << '\n'; << " - " << sw.stop() << '\n';
@ -3591,35 +3707,32 @@ namespace spot
// Set state players! // Set state players!
if (!minmachine) if (!minmachine)
return early_exit(); return early_exit();
// Try to set outputs set_synthesis_outputs(minmachine, get_synthesis_outputs(mm));
if (bdd* outptr = mm->get_named_prop<bdd>("synthesis-outputs"))
minmachine->set_named_prop("synthesis-outputs", new bdd(*outptr));
dotimeprint << "Done minimizing - " << minmachine->num_states() dotimeprint << "Done minimizing - " << minmachine->num_states()
<< " - " << sw.stop() << '\n'; << " - " << sw.stop() << '\n';
assert(is_split_mealy_specialization(mm, minmachine));
return minmachine; return minmachine;
} }
} }
namespace spot namespace spot
{ {
bool is_mealy_specialization(const_twa_graph_ptr left, bool is_split_mealy_specialization(const_twa_graph_ptr left,
const_twa_graph_ptr right, const_twa_graph_ptr right,
bool verbose) bool verbose)
{ {
assert(is_split_mealy(left));
assert(is_split_mealy(right));
auto& spl = get_state_players(left); auto& spl = get_state_players(left);
auto& spr = get_state_players(right); auto& spr = get_state_players(right);
const unsigned initl = left->get_init_state_number(); const unsigned initl = left->get_init_state_number();
const unsigned initr = right->get_init_state_number(); const unsigned initr = right->get_init_state_number();
if (spl[initl])
throw std::runtime_error("left: Mealy machine must have an "
"environment controlled initial state.");
if (spr[initr])
throw std::runtime_error("right: Mealy machine must have an "
"environment controlled initial state.");
#ifndef NDEBUG #ifndef NDEBUG
// todo
auto check_out = [](const const_twa_graph_ptr& aut, auto check_out = [](const const_twa_graph_ptr& aut,
const auto& sp) const auto& sp)
{ {

View file

@ -23,48 +23,109 @@
namespace spot namespace spot
{ {
/// \brief Minimizes an (in)completely specified mealy machine /// todo
/// Comment je faire au mieux pour expliquer mealy dans les doc
/// \brief Checks whether or not the automaton is a mealy machine
///
/// \param m The automaton to be verified
/// \note A mealy machine must have the named property \"synthesis-outputs\"
/// and have a \"true\" as acceptance condition
SPOT_API bool
is_mealy(const const_twa_graph_ptr& m);
/// \brief Checks whether or not the automaton is a separated mealy machine
///
/// \param m The automaton to be verified
/// \note A separated mealy machine is a mealy machine machine with all
/// transitions having the form (in)&(out) where in[out] is a bdd over
/// input[output] propositions of m
SPOT_API bool
is_separated_mealy(const const_twa_graph_ptr& m);
/// \brief Checks whether or not the automaton is a split mealy machine
///
/// \param m The automaton to be verified
/// \note A split mealy machine is a mealy machine machine with the named
/// property \"state-player\". Moreover the underlying automaton
/// must be alternating between the player and the env. Transitions
/// leaving env[player] states can only be labeled by
/// input[output] propositions.
SPOT_API bool
is_split_mealy(const const_twa_graph_ptr& m);
/// \brief Checks whether or not a mealy machine is input deterministic
///
/// \param m The automaton to be verified
/// \note works all mealy machines, no matter whether they are split
/// or separated or neither of neither of them.
/// \note A machine is input deterministic if none of the states
/// has two outgoing transitions that can agree on a assignement
/// of the input propositions.
SPOT_API bool
is_input_deterministic_mealy(const const_twa_graph_ptr& m);
/// \brief make each transition in a separated mealy machine a
/// 2-step transition.
///
/// \param m separated mealy machine to be split
/// \return returns the equivalent split mealy machine if not done inplace
/// @{
SPOT_API twa_graph_ptr
split_separated_mealy(const const_twa_graph_ptr& m);
SPOT_API void
split_separated_mealy_here(const twa_graph_ptr& m);
/// @}
/// \brief the inverse of split_separated_mealy
SPOT_API twa_graph_ptr
unsplit_mealy(const const_twa_graph_ptr& m);
/// \brief reduce an (in)completely specified mealy machine
/// Based on signature inclusion or equality. This is not guaranteed /// Based on signature inclusion or equality. This is not guaranteed
/// to find the minimal number of states but is usually faster. /// to find the minimal number of states but is usually faster.
/// This also comes at another drawback: /// This also comes at another drawback:
/// All applicable sequences have to be infinite. Finite /// All applicable sequences have to be infinite. Finite
/// traces are disregarded /// traces are disregarded
/// \Note The graph does not have to be split into env and player /// \param mm The mealy machine to be minimized, has to be unsplit
/// states as for minimize_mealy /// \param output_assignment Whether or not to use output assignment
SPOT_API /// \return A specialization of \c mm. Note that if mm is separated,
void minimize_mealy_fast_here(twa_graph_ptr& mm, /// the returned machine is separated as well.
bool use_inclusion = false); /// \note See todo TACAS22 Effective reductions of mealy machines
/// @{
SPOT_API twa_graph_ptr
reduce_mealy(const const_twa_graph_ptr& mm,
bool output_assignment = false);
/// \brief Like minimize_mealy_fast_here SPOT_API void
SPOT_API reduce_mealy_here(twa_graph_ptr& mm,
twa_graph_ptr minimize_mealy_fast(const const_twa_graph_ptr& mm, bool output_assignment = false);
bool use_inclusion = false); /// @}
/// \brief Minimizes an (in)completely specified mealy machine /// \brief Minimizes a split (in)completely specified mealy machine
/// The approach is basically described in \cite abel2015memin /// The approach is described in \todo TACAS
/// \param premin Use minimize_mealy_fast before applying the /// \param premin Use reduce_mealy before applying the
/// main algorithm if demanded AND /// main algorithm if demanded AND
/// the original machine has no finite trace. /// the original machine has no finite trace.
/// -1 : Do not use; /// -1 : Do not use;
/// 0 : Use without inclusion; /// 0 : Use without output assignment;
/// 1 : Use with inclusion /// 1 : Use with output assignment
/// \param keep_split Whether or not to keep the automaton states /// \return Returns a split mealy machines which is a minimal
/// partitioned into player and env states /// speciliazation of the original machine
/// \pre Graph must be split into env states and player states, SPOT_API twa_graph_ptr
/// such that they alternate. Edges leaving env states must be labeled minimize_mealy(const const_twa_graph_ptr& mm, int premin = -1);
/// with input proposition and edges leaving player states must be
/// labeled with output propositions.
SPOT_API
twa_graph_ptr minimize_mealy(const const_twa_graph_ptr& mm,
int premin = -1,
bool keep_split = true);
/// \brief Test if the mealy machine \a right is a specialization of /// \brief Test if the split mealy machine \a right is a specialization of
/// the mealy machine \a left. That is all input sequences valid for left /// the split mealy machine \a left.
///
/// That is all input sequences valid for left
/// must be applicable for right and the induced sequence of output signals /// must be applicable for right and the induced sequence of output signals
/// of right must imply the ones of left /// of right must imply the ones of left
SPOT_API bool is_mealy_specialization(const_twa_graph_ptr left, SPOT_API bool
const_twa_graph_ptr right, is_split_mealy_specialization(const_twa_graph_ptr left,
bool verbose = false); const_twa_graph_ptr right,
bool verbose = false);
} }

View file

@ -39,6 +39,7 @@
// Helper function/structures for split_2step // Helper function/structures for split_2step
namespace{ namespace{
using namespace spot;
// Computes and stores the restriction // Computes and stores the restriction
// of each cond to the input domain and the support // of each cond to the input domain and the support
// This is useful as it avoids recomputation // This is useful as it avoids recomputation
@ -47,9 +48,9 @@ namespace{
struct small_cacher_t struct small_cacher_t
{ {
//e to e_in and support //e to e_in and support
std::unordered_map<bdd, std::pair<bdd, bdd>, spot::bdd_hash> cond_hash_; std::unordered_map<bdd, std::pair<bdd, bdd>, bdd_hash> cond_hash_;
void fill(const spot::const_twa_graph_ptr& aut, bdd output_bdd) void fill(const const_twa_graph_ptr& aut, bdd output_bdd)
{ {
cond_hash_.reserve(aut->num_edges()/5+1); cond_hash_.reserve(aut->num_edges()/5+1);
// 20% is about lowest number of different edge conditions // 20% is about lowest number of different edge conditions
@ -79,28 +80,28 @@ namespace{
// of the state. // of the state.
struct e_info_t struct e_info_t
{ {
e_info_t(const spot::twa_graph::edge_storage_t& e, e_info_t(const twa_graph::edge_storage_t& e,
const small_cacher_t& sm) const small_cacher_t& sm)
: dst(e.dst), : dst(e.dst),
econd(e.cond), econd(e.cond),
einsup(sm[e.cond]), einsup(sm[e.cond]),
acc(e.acc) acc(e.acc)
{ {
pre_hash = (spot::wang32_hash(dst) pre_hash = (wang32_hash(dst)
^ std::hash<spot::acc_cond::mark_t>()(acc)) ^ std::hash<acc_cond::mark_t>()(acc))
* spot::fnv<size_t>::prime; * fnv<size_t>::prime;
} }
inline size_t hash() const inline size_t hash() const
{ {
return spot::wang32_hash(spot::bdd_hash()(econdout)) ^ pre_hash; return wang32_hash(bdd_hash()(econdout)) ^ pre_hash;
} }
unsigned dst; unsigned dst;
bdd econd; bdd econd;
mutable bdd econdout; mutable bdd econdout;
std::pair<bdd, bdd> einsup; std::pair<bdd, bdd> einsup;
spot::acc_cond::mark_t acc; acc_cond::mark_t acc;
size_t pre_hash; size_t pre_hash;
}; };
// We define an order between the edges to avoid creating multiple // We define an order between the edges to avoid creating multiple
@ -129,6 +130,313 @@ namespace{
< std::tie(rhs->dst, rhs->acc, r_id); < std::tie(rhs->dst, rhs->acc, r_id);
} }
}less_info_ptr; }less_info_ptr;
// Improved apply strat, that reduces the number of edges/states
// while keeping the needed edge-properties
// Note, this only deals with deterministic strategies
// Note, assumes that env starts playing
twa_graph_ptr
apply_strategy(const twa_graph_ptr& arena,
bool unsplit, bool keep_acc)
{
const auto& win = get_state_winners(arena);
const auto& strat = get_strategy(arena);
const auto& sp = get_state_players(arena);
auto outs = get_synthesis_outputs(arena);
if (!win[arena->get_init_state_number()])
throw std::runtime_error("Player does not win initial state, strategy "
"is not applicable");
assert((sp[arena->get_init_state_number()] == false)
&& "Env needs to have first turn!");
assert(std::none_of(arena->edges().begin(), arena->edges().end(),
[&sp](const auto& e)
{ bool same_player = sp.at(e.src) == sp.at(e.dst);
if (same_player)
std::cerr << e.src << " and " << e.dst << " belong to same "
<< "player, arena not alternating!\n";
return same_player; }));
auto strat_split = make_twa_graph(arena->get_dict());
strat_split->copy_ap_of(arena);
if (keep_acc)
strat_split->copy_acceptance_of(arena);
else
strat_split->acc().set_acceptance(acc_cond::acc_code::t());
std::stack<unsigned> todo;
todo.push(arena->get_init_state_number());
struct dca{
unsigned dst;
unsigned condvar;
acc_cond::mark_t acc;
};
struct dca_hash
{
size_t operator()(const dca& d) const noexcept
{
return pair_hash()(std::make_pair(d.dst, d.condvar))
^ wang32_hash(d.acc.hash());
}
};
struct dca_equal
{
bool operator()(const dca& d1, const dca& d2) const noexcept
{
return std::tie(d1.dst, d1.condvar, d1.acc)
== std::tie(d2.dst, d2.condvar, d2.acc);
}
};
//env dst + player cond + acc -> p stat
std::unordered_map<dca,
unsigned,
dca_hash,
dca_equal> p_map;
constexpr unsigned unseen_mark = std::numeric_limits<unsigned>::max();
std::vector<unsigned> env_map(arena->num_states(), unseen_mark);
strat_split->set_init_state(strat_split->new_state());
env_map[arena->get_init_state_number()] =
strat_split->get_init_state_number();
// The states in the new graph are qualified local
// Get a local environment state
auto get_sel = [&](unsigned s)
{
if (SPOT_UNLIKELY(env_map[s] == unseen_mark))
env_map[s] = strat_split->new_state();
return env_map[s];
};
// local dst
// Check if there exists already a local player state
// that has the same dst, cond and (if keep_acc is true) acc
auto get_spl = [&](unsigned dst, const bdd& cond, acc_cond::mark_t acc)
{
dca d{dst, (unsigned) cond.id(), acc};
auto it = p_map.find(d);
if (it != p_map.end())
return it->second;
unsigned ns = strat_split->new_state();
p_map[d] = ns;
strat_split->new_edge(ns, dst, cond, acc);
return ns;
};
while (!todo.empty())
{
unsigned src_env = todo.top();
unsigned src_envl = get_sel(src_env);
todo.pop();
// All env edges
for (const auto& e_env : arena->out(src_env))
{
// Get the corresponding strat
const auto& e_strat = arena->edge_storage(strat[e_env.dst]);
// Check if already explored
if (env_map[e_strat.dst] == unseen_mark)
todo.push(e_strat.dst);
unsigned dst_envl = get_sel(e_strat.dst);
// The new env edge, player is constructed automatically
auto used_acc = keep_acc ? e_strat.acc : acc_cond::mark_t({});
strat_split->new_edge(src_envl,
get_spl(dst_envl, e_strat.cond, used_acc),
e_env.cond, used_acc);
}
}
// All states exists, we can try to further merge them
// Specialized merge
std::vector<bool> spnew(strat_split->num_states(), false);
for (const auto& p : p_map)
spnew[p.second] = true;
// Sorting edges in place
auto comp_edge = [](const auto& e1, const auto& e2)
{
return std::tuple(e1.dst, e1.acc, e1.cond.id())
< std::tuple(e2.dst, e2.acc, e2.cond.id());
};
// todo, replace with sort_edges_of_ in graph
auto sort_edges_of =
[&, &split_graph = strat_split->get_graph()](unsigned s)
{
static std::vector<unsigned> edge_nums;
edge_nums.clear();
auto eit = strat_split->out(s);
const auto eit_e = eit.end();
// 0 Check if it is already sorted
if (std::is_sorted(eit.begin(), eit_e, comp_edge))
return false; // No change
// 1 Get all edges numbers and sort them
std::transform(eit.begin(), eit_e, std::back_inserter(edge_nums),
[&](const auto& e)
{ return strat_split->edge_number(e); });
std::sort(edge_nums.begin(), edge_nums.end(),
[&](unsigned ne1, unsigned ne2)
{ return comp_edge(strat_split->edge_storage(ne1),
strat_split->edge_storage(ne2)); });
// 2 Correct the order
auto& sd = split_graph.state_storage(s);
sd.succ = edge_nums.front();
sd.succ_tail = edge_nums.back();
const unsigned n_edges_p = edge_nums.size()-1;
for (unsigned i = 0; i < n_edges_p; ++i)
split_graph.edge_storage(edge_nums[i]).next_succ = edge_nums[i+1];
split_graph.edge_storage(edge_nums.back()).next_succ = 0; //terminal
// All nicely chained
return true;
};
auto merge_edges_of = [&](unsigned s)
{
// Call this after sort edges of
// Mergeable edges are nicely chained
bool changed = false;
auto eit = strat_split->out_iteraser(s);
unsigned idx_last = strat_split->edge_number(*eit);
++eit;
while (eit)
{
auto& e_last = strat_split->edge_storage(idx_last);
if (std::tie(e_last.dst, e_last.acc)
== std::tie(eit->dst, eit->acc))
{
//Same dest and acc -> or condition
e_last.cond |= eit->cond;
eit.erase();
changed = true;
}
else
{
idx_last = strat_split->edge_number(*eit);
++eit;
}
}
return changed;
};
auto merge_able = [&](unsigned s1, unsigned s2)
{
auto eit1 = strat_split->out(s1);
auto eit2 = strat_split->out(s2);
// Note: No self-loops here
return std::equal(eit1.begin(), eit1.end(),
eit2.begin(), eit2.end(),
[](const auto& e1, const auto& e2)
{
return std::tuple(e1.dst, e1.acc, e1.cond.id())
== std::tuple(e2.dst, e2.acc, e2.cond.id());
});
};
const unsigned n_sstrat = strat_split->num_states();
std::vector<unsigned> remap(n_sstrat, -1u);
bool changed_any;
std::vector<unsigned> to_check;
to_check.reserve(n_sstrat);
// First iteration -> all env states are candidates
for (unsigned i = 0; i < n_sstrat; ++i)
if (!spnew[i])
to_check.push_back(i);
while (true)
{
changed_any = false;
std::for_each(to_check.begin(), to_check.end(),
[&](unsigned s){ sort_edges_of(s); merge_edges_of(s); });
// Check if we can merge env states
for (unsigned s1 : to_check)
for (unsigned s2 = 0; s2 < n_sstrat; ++s2)
{
if (spnew[s2] || (s1 == s2))
continue; // Player state or s1 == s2
if ((remap[s2] < s2))
continue; //s2 is already remapped
if (merge_able(s1, s2))
{
changed_any = true;
if (s1 < s2)
remap[s2] = s1;
else
remap[s1] = s2;
break;
}
}
if (!changed_any)
break;
// Redirect changed targets and set possibly mergeable states
to_check.clear();
for (auto& e : strat_split->edges())
if (remap[e.dst] != -1u)
{
e.dst = remap[e.dst];
to_check.push_back(e.src);
assert(spnew[e.src]);
}
// Now check the player states
// We can skip sorting -> only one edge
// todo change when multistrat
changed_any = false;
for (unsigned s1 : to_check)
for (unsigned s2 = 0; s2 < n_sstrat; ++s2)
{
if (!spnew[s2] || (s1 == s2))
continue; // Env state or s1 == s2
if ((remap[s2] < s2))
continue; //s2 is already remapped
if (merge_able(s1, s2))
{
changed_any = true;
if (s1 < s2)
remap[s2] = s1;
else
remap[s1] = s2;
break;
}
}
if (!changed_any)
break;
// Redirect changed targets and set possibly mergeable states
to_check.clear();
for (auto& e : strat_split->edges())
if (remap[e.dst] != -1u)
{
e.dst = remap[e.dst];
to_check.push_back(e.src);
assert(!spnew[e.src]);
}
}
// Defrag and alternate
if (remap[strat_split->get_init_state_number()] != -1u)
strat_split->set_init_state(remap[strat_split->get_init_state_number()]);
unsigned st = 0;
for (auto& s: remap)
if (s == -1U)
s = st++;
else
s = -1U;
strat_split->defrag_states(remap, st);
alternate_players(strat_split, false, false);
// What we do now depends on whether we unsplit or not
if (unsplit)
{
auto final = unsplit_2step(strat_split);
set_synthesis_outputs(final, outs);
return final;
}
set_synthesis_outputs(strat_split, outs);
return strat_split;
}
} }
namespace spot namespace spot
@ -142,7 +450,7 @@ namespace spot
split->copy_acceptance_of(aut); split->copy_acceptance_of(aut);
split->new_states(aut->num_states()); split->new_states(aut->num_states());
split->set_init_state(aut->get_init_state_number()); split->set_init_state(aut->get_init_state_number());
split->set_named_prop<bdd>("synthesis-output", new bdd(output_bdd)); split->set_named_prop<bdd>("synthesis-outputs", new bdd(output_bdd));
auto [is_unsat, unsat_mark] = aut->acc().unsat_mark(); auto [is_unsat, unsat_mark] = aut->acc().unsat_mark();
if (!is_unsat && complete_env) if (!is_unsat && complete_env)
@ -318,89 +626,13 @@ namespace spot
if (sink_env != -1u) if (sink_env != -1u)
owner->at(sink_env) = false; owner->at(sink_env) = false;
split->set_named_prop("state-player", owner); split->set_named_prop("state-player", owner);
split->set_named_prop("synthesis-outputs",
new bdd(output_bdd));
// Done // Done
return split; return split;
} }
void
split_2step_fast_here(const twa_graph_ptr& aut, const bdd& output_bdd)
{
struct dst_cond_color_t
{
std::pair<unsigned, int> dst_cond;
acc_cond::mark_t color;
};
auto hasher = [](const dst_cond_color_t& dcc) noexcept
{
return dcc.color.hash() ^ pair_hash()(dcc.dst_cond);
};
auto equal = [](const dst_cond_color_t& dcc1,
const dst_cond_color_t& dcc2) noexcept
{
return (dcc1.dst_cond == dcc2.dst_cond)
&& (dcc1.color == dcc2.color);
};
std::unordered_map<dst_cond_color_t, unsigned,
decltype(hasher),
decltype(equal)> player_map(aut->num_edges(),
hasher, equal);
auto get_ps = [&](unsigned dst, const bdd& ocond,
acc_cond::mark_t color)
{
dst_cond_color_t key{std::make_pair(dst, ocond.id()),
color};
auto [it, inserted] =
player_map.try_emplace(key, aut->num_states());
if (!inserted)
return it->second;
unsigned ns = aut->new_state();
assert(ns == it->second);
aut->new_edge(ns, dst, ocond, color);
return ns;
};
unsigned ne = aut->edge_vector().size();
for (unsigned eidx = 1; eidx < ne; ++eidx)
{
const auto& e = aut->edge_storage(eidx);
if (e.next_succ == eidx) // dead edge
continue;
bdd incond = bdd_exist(e.cond, output_bdd);
bdd outcond = bdd_existcomp(e.cond, output_bdd);
assert(((incond&outcond) == e.cond) && "Precondition violated");
// Create new state and trans
// this may invalidate "e".
unsigned new_dst = get_ps(e.dst, outcond, e.acc);
// redirect
auto& e2 = aut->edge_storage(eidx);
e2.dst = new_dst;
e2.cond = incond;
}
auto* sp_ptr =
aut->get_or_set_named_prop<std::vector<bool>>("state-player");
sp_ptr->resize(aut->num_states());
std::fill(sp_ptr->begin(), sp_ptr->end(), false);
for (auto& eit : player_map)
(*sp_ptr)[eit.second] = true;
//Done
}
twa_graph_ptr
split_2step_fast(const const_twa_graph_ptr& aut, const bdd& output_bdd)
{
auto aut2 = make_twa_graph(aut, twa::prop_set::all());
aut2->copy_acceptance_of(aut);
aut2->set_named_prop<bdd>("synthesis-output", new bdd(output_bdd));
split_2step_fast_here(aut2, output_bdd);
return aut2;
}
twa_graph_ptr twa_graph_ptr
unsplit_2step(const const_twa_graph_ptr& aut) unsplit_2step(const const_twa_graph_ptr& aut)
{ {
@ -453,321 +685,6 @@ namespace spot
return out; return out;
} }
// Improved apply strat, that reduces the number of edges/states
// while keeping the needed edge-properties
// Note, this only deals with deterministic strategies
// Note, assumes that env starts playing
spot::twa_graph_ptr
apply_strategy(const spot::twa_graph_ptr& arena,
bool unsplit, bool keep_acc)
{
const auto& win = get_state_winners(arena);
const auto& strat = get_strategy(arena);
const auto& sp = get_state_players(arena);
auto outs = get_synthesis_outputs(arena);
if (!win[arena->get_init_state_number()])
throw std::runtime_error("Player does not win initial state, strategy "
"is not applicable");
assert((sp[arena->get_init_state_number()] == false)
&& "Env needs to have first turn!");
assert(std::none_of(arena->edges().begin(), arena->edges().end(),
[&sp](const auto& e)
{ bool same_player = sp.at(e.src) == sp.at(e.dst);
if (same_player)
std::cerr << e.src << " and " << e.dst << " belong to same "
<< "player, arena not alternating!\n";
return same_player; }));
auto strat_split = spot::make_twa_graph(arena->get_dict());
strat_split->copy_ap_of(arena);
if (keep_acc)
strat_split->copy_acceptance_of(arena);
std::stack<unsigned> todo;
todo.push(arena->get_init_state_number());
struct dca{
unsigned dst;
unsigned condvar;
acc_cond::mark_t acc;
};
struct dca_hash
{
size_t operator()(const dca& d) const noexcept
{
return pair_hash()(std::make_pair(d.dst, d.condvar))
^ wang32_hash(d.acc.hash());
}
};
struct dca_equal
{
bool operator()(const dca& d1, const dca& d2) const noexcept
{
return std::tie(d1.dst, d1.condvar, d1.acc)
== std::tie(d2.dst, d2.condvar, d2.acc);
}
};
//env dst + player cond + acc -> p stat
std::unordered_map<dca,
unsigned,
dca_hash,
dca_equal> p_map;
constexpr unsigned unseen_mark = std::numeric_limits<unsigned>::max();
std::vector<unsigned> env_map(arena->num_states(), unseen_mark);
strat_split->set_init_state(strat_split->new_state());
env_map[arena->get_init_state_number()] =
strat_split->get_init_state_number();
// The states in the new graph are qualified local
// Get a local environment state
auto get_sel = [&](unsigned s)
{
if (SPOT_UNLIKELY(env_map[s] == unseen_mark))
env_map[s] = strat_split->new_state();
return env_map[s];
};
// local dst
// Check if there exists already a local player state
// that has the same dst, cond and (if keep_acc is true) acc
auto get_spl = [&](unsigned dst, const bdd& cond, acc_cond::mark_t acc)
{
dca d{dst, (unsigned) cond.id(), acc};
auto it = p_map.find(d);
if (it != p_map.end())
return it->second;
unsigned ns = strat_split->new_state();
p_map[d] = ns;
strat_split->new_edge(ns, dst, cond, acc);
return ns;
};
while (!todo.empty())
{
unsigned src_env = todo.top();
unsigned src_envl = get_sel(src_env);
todo.pop();
// All env edges
for (const auto& e_env : arena->out(src_env))
{
// Get the corresponding strat
const auto& e_strat = arena->edge_storage(strat[e_env.dst]);
// Check if already explored
if (env_map[e_strat.dst] == unseen_mark)
todo.push(e_strat.dst);
unsigned dst_envl = get_sel(e_strat.dst);
// The new env edge, player is constructed automatically
auto used_acc = keep_acc ? e_strat.acc : acc_cond::mark_t({});
strat_split->new_edge(src_envl,
get_spl(dst_envl, e_strat.cond, used_acc),
e_env.cond, used_acc);
}
}
// All states exists, we can try to further merge them
// Specialized merge
std::vector<bool> spnew(strat_split->num_states(), false);
for (const auto& p : p_map)
spnew[p.second] = true;
// Sorting edges in place
auto comp_edge = [](const auto& e1, const auto& e2)
{
return std::tuple(e1.dst, e1.acc, e1.cond.id())
< std::tuple(e2.dst, e2.acc, e2.cond.id());
};
auto sort_edges_of =
[&, &split_graph = strat_split->get_graph()](unsigned s)
{
static std::vector<unsigned> edge_nums;
edge_nums.clear();
auto eit = strat_split->out(s);
const auto eit_e = eit.end();
// 0 Check if it is already sorted
if (std::is_sorted(eit.begin(), eit_e, comp_edge))
return false; // No change
// 1 Get all edges numbers and sort them
std::transform(eit.begin(), eit_e, std::back_inserter(edge_nums),
[&](const auto& e)
{ return strat_split->edge_number(e); });
std::sort(edge_nums.begin(), edge_nums.end(),
[&](unsigned ne1, unsigned ne2)
{ return comp_edge(strat_split->edge_storage(ne1),
strat_split->edge_storage(ne2)); });
// 2 Correct the order
auto& sd = split_graph.state_storage(s);
sd.succ = edge_nums.front();
sd.succ_tail = edge_nums.back();
const unsigned n_edges_p = edge_nums.size()-1;
for (unsigned i = 0; i < n_edges_p; ++i)
split_graph.edge_storage(edge_nums[i]).next_succ = edge_nums[i+1];
split_graph.edge_storage(edge_nums.back()).next_succ = 0; //terminal
// All nicely chained
return true;
};
auto merge_edges_of = [&](unsigned s)
{
// Call this after sort edges of
// Mergeable edges are nicely chained
bool changed = false;
auto eit = strat_split->out_iteraser(s);
unsigned idx_last = strat_split->edge_number(*eit);
++eit;
while (eit)
{
auto& e_last = strat_split->edge_storage(idx_last);
if (std::tie(e_last.dst, e_last.acc)
== std::tie(eit->dst, eit->acc))
{
//Same dest and acc -> or condition
e_last.cond |= eit->cond;
eit.erase();
changed = true;
}
else
{
idx_last = strat_split->edge_number(*eit);
++eit;
}
}
return changed;
};
auto merge_able = [&](unsigned s1, unsigned s2)
{
auto eit1 = strat_split->out(s1);
auto eit2 = strat_split->out(s2);
// Note: No self-loops here
return std::equal(eit1.begin(), eit1.end(),
eit2.begin(), eit2.end(),
[](const auto& e1, const auto& e2)
{
return std::tuple(e1.dst, e1.acc, e1.cond.id())
== std::tuple(e2.dst, e2.acc, e2.cond.id());
});
};
const unsigned n_sstrat = strat_split->num_states();
std::vector<unsigned> remap(n_sstrat, -1u);
bool changed_any;
std::vector<unsigned> to_check;
to_check.reserve(n_sstrat);
// First iteration -> all env states are candidates
for (unsigned i = 0; i < n_sstrat; ++i)
if (!spnew[i])
to_check.push_back(i);
while (true)
{
changed_any = false;
std::for_each(to_check.begin(), to_check.end(),
[&](unsigned s){ sort_edges_of(s); merge_edges_of(s); });
// Check if we can merge env states
for (unsigned s1 : to_check)
for (unsigned s2 = 0; s2 < n_sstrat; ++s2)
{
if (spnew[s2] || (s1 == s2))
continue; // Player state or s1 == s2
if ((remap[s2] < s2))
continue; //s2 is already remapped
if (merge_able(s1, s2))
{
changed_any = true;
if (s1 < s2)
remap[s2] = s1;
else
remap[s1] = s2;
break;
}
}
if (!changed_any)
break;
// Redirect changed targets and set possibly mergeable states
to_check.clear();
for (auto& e : strat_split->edges())
if (remap[e.dst] != -1u)
{
e.dst = remap[e.dst];
to_check.push_back(e.src);
assert(spnew[e.src]);
}
// Now check the player states
// We can skip sorting -> only one edge
// todo change when multistrat
changed_any = false;
for (unsigned s1 : to_check)
for (unsigned s2 = 0; s2 < n_sstrat; ++s2)
{
if (!spnew[s2] || (s1 == s2))
continue; // Env state or s1 == s2
if ((remap[s2] < s2))
continue; //s2 is already remapped
if (merge_able(s1, s2))
{
changed_any = true;
if (s1 < s2)
remap[s2] = s1;
else
remap[s1] = s2;
break;
}
}
if (!changed_any)
break;
// Redirect changed targets and set possibly mergeable states
to_check.clear();
for (auto& e : strat_split->edges())
if (remap[e.dst] != -1u)
{
e.dst = remap[e.dst];
to_check.push_back(e.src);
assert(!spnew[e.src]);
}
}
// Defrag and alternate
if (remap[strat_split->get_init_state_number()] != -1u)
strat_split->set_init_state(remap[strat_split->get_init_state_number()]);
unsigned st = 0;
for (auto& s: remap)
if (s == -1U)
s = st++;
else
s = -1U;
strat_split->defrag_states(remap, st);
alternate_players(strat_split, false, false);
// What we do now depends on whether we unsplit or not
if (unsplit)
{
auto final = unsplit_2step(strat_split);
set_synthesis_outputs(final, outs);
return final;
}
// Keep the splitted version
set_state_winners(strat_split, region_t(strat_split->num_states(), true));
std::vector<unsigned> new_strat(strat_split->num_states(), -1u);
for (unsigned i = 0; i < strat_split->num_states(); ++i)
{
if (!sp[i])
continue;
new_strat[i] = strat_split->edge_number(*strat_split->out(i).begin());
}
set_strategy(strat_split, std::move(new_strat));
set_synthesis_outputs(strat_split, outs);
return strat_split;
}
std::ostream& operator<<(std::ostream& os, synthesis_info::algo s) std::ostream& operator<<(std::ostream& os, synthesis_info::algo s)
{ {
using algo = synthesis_info::algo; using algo = synthesis_info::algo;
@ -1059,7 +976,7 @@ namespace spot
} //end switch solver } //end switch solver
// Set the named properties // Set the named properties
assert(dpa->get_named_prop<std::vector<bool>>("state-player") assert(dpa->get_named_prop<std::vector<bool>>("state-player")
&& "DPA has no state-players"); && "DPA has no \"state-player\"");
set_synthesis_outputs(dpa, outs); set_synthesis_outputs(dpa, outs);
return dpa; return dpa;
} }
@ -1087,78 +1004,15 @@ namespace spot
return ltl_to_game(parse_formula(f), all_outs, gi); return ltl_to_game(parse_formula(f), all_outs, gi);
} }
void
minimize_strategy_here(twa_graph_ptr& strat, int min_lvl)
{
if (!strat->acc().is_t())
throw std::runtime_error("minimize_strategy_here(): strategy is expected "
"to accept all runs! Otherwise it does not "
"correspond to a mealy machine and can not be "
"optimized this way.");
// 0 -> No minimization
// 1 -> Regular monitor/DFA minimization
// 2 -> Mealy minimization based on language inclusion
// with output assignement
// 3 -> Exact Mealy minimization using SAT
// 4 -> Monitor min then exact minimization
// 5 -> Mealy minimization based on language inclusion then exact
// minimization
bdd outs = get_synthesis_outputs(strat);
switch (min_lvl)
{
case 0:
return;
case 1:
{
minimize_mealy_fast_here(strat, false);
break;
}
case 2:
{
minimize_mealy_fast_here(strat, true);
break;
}
case 3:
{
strat = minimize_mealy(strat, -1);
break;
}
case 4:
{
strat = minimize_mealy(strat, 0);
break;
}
case 5:
{
strat = minimize_mealy(strat, 1);
break;
}
default:
throw std::runtime_error("Unknown minimization option");
}
set_synthesis_outputs(strat, outs);
}
twa_graph_ptr twa_graph_ptr
minimize_strategy(const_twa_graph_ptr& strat, int min_lvl) solved_game_to_separated_mealy(twa_graph_ptr arena, synthesis_info& gi)
{
auto strat_dup = spot::make_twa_graph(strat, spot::twa::prop_set::all());
set_synthesis_outputs(strat_dup,
get_synthesis_outputs(strat));
if (auto sp_ptr = strat->get_named_prop<region_t>("state-player"))
set_state_players(strat_dup, *sp_ptr);
minimize_strategy_here(strat_dup, min_lvl);
return strat_dup;
}
twa_graph_ptr
create_strategy(twa_graph_ptr arena, synthesis_info& gi)
{ {
assert(arena->get_named_prop<region_t>("state-player")
&& "Need prop \"state-player\"");
if (!arena) if (!arena)
throw std::runtime_error("Arena can not be null"); throw std::runtime_error("Arena can not be null");
spot::stopwatch sw; stopwatch sw;
if (gi.bv) if (gi.bv)
sw.start(); sw.start();
@ -1168,32 +1022,34 @@ namespace spot
// If we use minimizations 0,1 or 2 -> unsplit // If we use minimizations 0,1 or 2 -> unsplit
const bool do_unsplit = gi.minimize_lvl < 3; const bool do_unsplit = gi.minimize_lvl < 3;
twa_graph_ptr strat_aut = apply_strategy(arena, do_unsplit, false); auto m = apply_strategy(arena, do_unsplit, false);
strat_aut->prop_universal(true); m->prop_universal(true);
minimize_strategy_here(strat_aut, gi.minimize_lvl); if ((0 < gi.minimize_lvl) && (gi.minimize_lvl < 3))
reduce_mealy_here(m, gi.minimize_lvl == 2);
else if (gi.minimize_lvl >= 3)
m = minimize_mealy(m, gi.minimize_lvl - 4);
assert(do_unsplit
|| strat_aut->get_named_prop<std::vector<bool>>("state-player"));
if (!do_unsplit) if (!do_unsplit)
strat_aut = unsplit_2step(strat_aut); m = unsplit_mealy(m);
if (gi.bv) if (gi.bv)
{ {
gi.bv->strat2aut_time += sw.stop(); gi.bv->strat2aut_time += sw.stop();
gi.bv->nb_strat_states += strat_aut->num_states(); gi.bv->nb_strat_states += m->num_states();
gi.bv->nb_strat_edges += strat_aut->num_edges(); gi.bv->nb_strat_edges += m->num_edges();
} }
return strat_aut; assert(is_separated_mealy(m));
return m;
} }
twa_graph_ptr twa_graph_ptr
create_strategy(twa_graph_ptr arena) solved_game_to_separated_mealy(twa_graph_ptr arena)
{ {
synthesis_info dummy; synthesis_info dummy;
return create_strategy(arena, dummy); return solved_game_to_separated_mealy(arena, dummy);
} }
} }
@ -1241,7 +1097,7 @@ namespace spot
}; };
} }
strategy_like_t mealy_like
try_create_direct_strategy(formula f, try_create_direct_strategy(formula f,
const std::vector<std::string>& output_aps, const std::vector<std::string>& output_aps,
synthesis_info &gi) synthesis_info &gi)
@ -1253,41 +1109,41 @@ namespace spot
*vs << "trying to create strategy directly for " << f << '\n'; *vs << "trying to create strategy directly for " << f << '\n';
auto ret_sol_maybe = [&vs]() auto ret_sol_maybe = [&vs]()
{ {
if (vs) if (vs)
*vs << "direct strategy might exist but was not found.\n"; *vs << "direct strategy might exist but was not found.\n";
return strategy_like_t{ return mealy_like{
strategy_like_t::realizability_code::UNKNOWN, mealy_like::realizability_code::UNKNOWN,
nullptr, nullptr,
bddfalse}; bddfalse};
}; };
auto ret_sol_none = [&vs]() auto ret_sol_none = [&vs]()
{ {
if (vs) if (vs)
*vs << "no strategy exists.\n"; *vs << "no strategy exists.\n";
return strategy_like_t{ return mealy_like{
strategy_like_t::realizability_code::UNREALIZABLE, mealy_like::realizability_code::UNREALIZABLE,
nullptr, nullptr,
bddfalse}; bddfalse};
}; };
auto ret_sol_exists = [&vs](auto strat) auto ret_sol_exists = [&vs](auto strat)
{
if (vs)
{ {
*vs << "direct strategy was found.\n" if (vs)
<< "direct strat has " << strat->num_states() {
<< " states and " << strat->num_sets() << " colors\n"; *vs << "direct strategy was found.\n"
} << "direct strat has " << strat->num_states()
return strategy_like_t{ << " states and " << strat->num_sets() << " colors\n";
strategy_like_t::realizability_code::REALIZABLE_REGULAR, }
strat, return mealy_like{
bddfalse}; mealy_like::realizability_code::REALIZABLE_REGULAR,
}; strat,
bddfalse};
};
formula_2_inout_props form2props(output_aps); formula_2_inout_props form2props(output_aps);
auto output_aps_set = std::set<std::string>(output_aps.begin(), auto output_aps_set = std::set<std::string>(output_aps.begin(),
output_aps.end()); output_aps.end());
formula f_g = formula::tt(), f_left, f_right; formula f_g = formula::tt(), f_left, f_right;

View file

@ -58,29 +58,13 @@ namespace spot
split_2step(const const_twa_graph_ptr& aut, split_2step(const const_twa_graph_ptr& aut,
const bdd& output_bdd, bool complete_env); const bdd& output_bdd, bool complete_env);
/// \ingroup synthesis /// \ingroup synthesis
/// \brief make each transition a 2-step transition. /// \brief the inverse of split_2step
///
/// This algorithm is only applicable if all transitions of the
/// graph have the form p -- ins & outs --> q.
/// That is they are a conjunction of a condition over the input
/// propositions ins and a condition over the output propositions outs
///
/// \param aut automaton to be transformed
/// \param output_bdd conjunction of all output AP, all APs not present
/// are treated as inputs
/// @{
SPOT_API void
split_2step_fast_here(const twa_graph_ptr& aut, const bdd& output_bdd);
SPOT_API twa_graph_ptr
split_2step_fast(const const_twa_graph_ptr& aut, const bdd& output_bdd);
/// @}
/// \ingroup synthesis
/// \brief the reverse of split_2step
/// ///
/// \pre aut is an alternating arena
/// \post All edge conditions in the returned automaton are of the form
/// ins&outs, with ins/outs being a condition over the input/output
/// propositions
/// \note This function relies on the named property "state-player" /// \note This function relies on the named property "state-player"
SPOT_API twa_graph_ptr SPOT_API twa_graph_ptr
unsplit_2step(const const_twa_graph_ptr& aut); unsplit_2step(const const_twa_graph_ptr& aut);
@ -174,57 +158,23 @@ namespace spot
/// @} /// @}
/// \ingroup synthesis /// \ingroup synthesis
/// \brief Takes a solved game and restricts the automaton to the /// \brief creates a separated mealy machine from a solved game
/// winning strategy of the player /// taking into account the options given in \a gi.
/// /// This concerns in particular whether or not the machine is to be reduced
/// \param arena twa_graph with named properties "state-player", /// and how.
/// "strategy" and "state-winner"
/// \param unsplit Whether or not to unsplit the automaton
/// \param keep_acc Whether or not keep the acceptance condition
/// \return the resulting twa_graph
SPOT_API spot::twa_graph_ptr
apply_strategy(const spot::twa_graph_ptr& arena,
bool unsplit, bool keep_acc);
/// \ingroup synthesis
/// \brief Minimizes a strategy. Strategies are infact
/// Mealy machines. So we can use techniques designed for them
///
/// \param strat The machine to minimize
/// \param min_lvl How to minimize the machine:
/// 0: No minimization, 1: Minimizing it like an DFA,
/// means the language is preserved, 2: Inclusion with
/// output assignement, 3: Exact minimization using
/// SAT solving, 4: SAT solving with DFA minimization as
/// preprocessing, 5: SAT solving using inclusion based
/// minimization with output assignment as preprocessing
/// \note min_lvl 1 and 2 work more efficiently on UNSPLIT strategies,
/// whereas min_lvl 3, 4 and 5 mandate a SPLIT strategy
/// @{ /// @{
SPOT_API void
minimize_strategy_here(twa_graph_ptr& strat, int min_lvl);
SPOT_API twa_graph_ptr SPOT_API twa_graph_ptr
minimize_strategy(const_twa_graph_ptr& strat, int min_lvl); solved_game_to_separated_mealy(twa_graph_ptr arena, synthesis_info& gi);
SPOT_API twa_graph_ptr
solved_game_to_separated_mealy(twa_graph_ptr arena);
/// @} /// @}
/// \ingroup synthesis /// \ingroup synthesis
/// \brief creates a strategy from a solved game taking into account the /// \brief A struct that represents different types of mealy like
/// options given in \a gi
/// @{
SPOT_API twa_graph_ptr
create_strategy(twa_graph_ptr arena, synthesis_info& gi);
SPOT_API twa_graph_ptr
create_strategy(twa_graph_ptr arena);
/// @}
/// \ingroup synthesis
/// \brief A struct that represents different types of strategy like
/// objects /// objects
struct SPOT_API struct SPOT_API
strategy_like_t mealy_like
{ {
enum class realizability_code enum class realizability_code
{ {
UNREALIZABLE, UNREALIZABLE,
@ -235,7 +185,7 @@ namespace spot
}; };
realizability_code success; realizability_code success;
twa_graph_ptr strat_like; twa_graph_ptr mealy_like;
bdd glob_cond; bdd glob_cond;
}; };
@ -272,7 +222,7 @@ namespace spot
/// \param f The formula to synthesize a strategy for /// \param f The formula to synthesize a strategy for
/// \param output_aps A vector with the name of all output properties. /// \param output_aps A vector with the name of all output properties.
/// All APs not named in this vector are treated as inputs /// All APs not named in this vector are treated as inputs
SPOT_API strategy_like_t SPOT_API mealy_like
try_create_direct_strategy(formula f, try_create_direct_strategy(formula f,
const std::vector<std::string>& output_aps, const std::vector<std::string>& output_aps,
synthesis_info& gi); synthesis_info& gi);

View file

@ -3,7 +3,6 @@
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 1, "execution_count": 1,
"id": "99baf49c",
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [],
"source": [ "source": [
@ -13,7 +12,6 @@
}, },
{ {
"cell_type": "markdown", "cell_type": "markdown",
"id": "066489fd",
"metadata": {}, "metadata": {},
"source": [ "source": [
"Test the Mealy printer." "Test the Mealy printer."
@ -22,7 +20,6 @@
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 2, "execution_count": 2,
"id": "08d7d512",
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [],
"source": [ "source": [
@ -32,7 +29,6 @@
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 3, "execution_count": 3,
"id": "4e5b66f4",
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [
{ {
@ -53,7 +49,6 @@
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 4, "execution_count": 4,
"id": "ce2ff962",
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [
{ {
@ -140,7 +135,7 @@
"</svg>\n" "</svg>\n"
], ],
"text/plain": [ "text/plain": [
"<spot.twa_graph; proxy of <Swig Object of type 'std::shared_ptr< spot::twa_graph > *' at 0x7f3d8c04f720> >" "<spot.twa_graph; proxy of <Swig Object of type 'std::shared_ptr< spot::twa_graph > *' at 0x7f300caabba0> >"
] ]
}, },
"execution_count": 4, "execution_count": 4,
@ -155,17 +150,15 @@
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 5, "execution_count": 5,
"id": "3bcbeef2",
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [],
"source": [ "source": [
"x = spot.apply_strategy(g, True, False)" "x = spot.solved_game_to_separated_mealy(g)"
] ]
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 6, "execution_count": 6,
"id": "47d298f0",
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [
{ {
@ -220,7 +213,7 @@
"</svg>\n" "</svg>\n"
], ],
"text/plain": [ "text/plain": [
"<spot.twa_graph; proxy of <Swig Object of type 'std::shared_ptr< spot::twa_graph > *' at 0x7f3d8c04f630> >" "<spot.twa_graph; proxy of <Swig Object of type 'std::shared_ptr< spot::twa_graph > *' at 0x7f300c179300> >"
] ]
}, },
"execution_count": 6, "execution_count": 6,
@ -235,7 +228,6 @@
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 7, "execution_count": 7,
"id": "f0d098ac",
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [],
"source": [ "source": [
@ -245,7 +237,6 @@
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 8, "execution_count": 8,
"id": "adf92ac7",
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [
{ {
@ -294,7 +285,7 @@
"</svg>\n" "</svg>\n"
], ],
"text/plain": [ "text/plain": [
"<spot.twa_graph; proxy of <Swig Object of type 'std::shared_ptr< spot::twa_graph > *' at 0x7f3d8c04f630> >" "<spot.twa_graph; proxy of <Swig Object of type 'std::shared_ptr< spot::twa_graph > *' at 0x7f300c179300> >"
] ]
}, },
"execution_count": 8, "execution_count": 8,
@ -305,96 +296,6 @@
"source": [ "source": [
"x" "x"
] ]
},
{
"cell_type": "code",
"execution_count": 9,
"id": "6e8cd892",
"metadata": {},
"outputs": [
{
"data": {
"image/svg+xml": [
"<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n",
"<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\"\n",
" \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n",
"<!-- Generated by graphviz version 2.43.0 (0)\n",
" -->\n",
"<!-- Pages: 1 -->\n",
"<svg width=\"247pt\" height=\"204pt\"\n",
" viewBox=\"0.00 0.00 247.00 204.00\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">\n",
"<g id=\"graph0\" class=\"graph\" transform=\"scale(1.0 1.0) rotate(0) translate(4 200)\">\n",
"<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-200 243,-200 243,4 -4,4\"/>\n",
"<text text-anchor=\"start\" x=\"8\" y=\"-181.8\" font-family=\"Lato\" font-size=\"14.00\">Inf(</text>\n",
"<text text-anchor=\"start\" x=\"29\" y=\"-181.8\" font-family=\"Lato\" font-size=\"14.00\" fill=\"#6a3d9a\">❸</text>\n",
"<text text-anchor=\"start\" x=\"45\" y=\"-181.8\" font-family=\"Lato\" font-size=\"14.00\">) | (Fin(</text>\n",
"<text text-anchor=\"start\" x=\"87\" y=\"-181.8\" font-family=\"Lato\" font-size=\"14.00\" fill=\"#ff7f00\">❷</text>\n",
"<text text-anchor=\"start\" x=\"103\" y=\"-181.8\" font-family=\"Lato\" font-size=\"14.00\">) &amp; (Inf(</text>\n",
"<text text-anchor=\"start\" x=\"149\" y=\"-181.8\" font-family=\"Lato\" font-size=\"14.00\" fill=\"#ff4da0\">❶</text>\n",
"<text text-anchor=\"start\" x=\"165\" y=\"-181.8\" font-family=\"Lato\" font-size=\"14.00\">) | Fin(</text>\n",
"<text text-anchor=\"start\" x=\"203\" y=\"-181.8\" font-family=\"Lato\" font-size=\"14.00\" fill=\"#1f78b4\">⓿</text>\n",
"<text text-anchor=\"start\" x=\"219\" y=\"-181.8\" font-family=\"Lato\" font-size=\"14.00\">)))</text>\n",
"<text text-anchor=\"start\" x=\"64\" y=\"-167.8\" font-family=\"Lato\" font-size=\"14.00\">[parity max odd 4]</text>\n",
"<!-- I -->\n",
"<!-- 0 -->\n",
"<g id=\"node2\" class=\"node\">\n",
"<title>0</title>\n",
"<ellipse fill=\"#ffffaa\" stroke=\"black\" cx=\"123\" cy=\"-18\" rx=\"18\" ry=\"18\"/>\n",
"<text text-anchor=\"middle\" x=\"123\" y=\"-14.3\" font-family=\"Lato\" font-size=\"14.00\">0</text>\n",
"</g>\n",
"<!-- I&#45;&gt;0 -->\n",
"<g id=\"edge1\" class=\"edge\">\n",
"<title>I&#45;&gt;0</title>\n",
"<path fill=\"none\" stroke=\"black\" d=\"M64.17,-18C66.01,-18 82.75,-18 97.75,-18\"/>\n",
"<polygon fill=\"black\" stroke=\"black\" points=\"104.9,-18 97.9,-21.15 101.4,-18 97.9,-18 97.9,-18 97.9,-18 101.4,-18 97.9,-14.85 104.9,-18 104.9,-18\"/>\n",
"</g>\n",
"<!-- 0&#45;&gt;0 -->\n",
"<g id=\"edge2\" class=\"edge\">\n",
"<title>0&#45;&gt;0</title>\n",
"<path fill=\"none\" stroke=\"black\" d=\"M119.4,-35.78C118.79,-45.31 119.99,-54 123,-54 125.21,-54 126.44,-49.32 126.7,-43.05\"/>\n",
"<polygon fill=\"black\" stroke=\"black\" points=\"126.6,-35.78 129.85,-42.74 126.65,-39.28 126.7,-42.78 126.7,-42.78 126.7,-42.78 126.65,-39.28 123.55,-42.83 126.6,-35.78 126.6,-35.78\"/>\n",
"<polygon fill=\"#e9f4fb\" stroke=\"transparent\" points=\"72,-77 72,-96 115,-96 115,-77 72,-77\"/>\n",
"<text text-anchor=\"start\" x=\"74\" y=\"-82.8\" font-family=\"Lato\" font-size=\"14.00\">!a &amp; !c</text>\n",
"<text text-anchor=\"start\" x=\"119\" y=\"-82.8\" font-family=\"Lato\" font-size=\"14.00\">/</text>\n",
"<polygon fill=\"#ffe5f1\" stroke=\"transparent\" points=\"130,-77 130,-96 174,-96 174,-77 130,-77\"/>\n",
"<text text-anchor=\"start\" x=\"132\" y=\"-82.8\" font-family=\"Lato\" font-size=\"14.00\">!b &amp; !d</text>\n",
"<text text-anchor=\"start\" x=\"115\" y=\"-61.8\" font-family=\"Lato\" font-size=\"14.00\" fill=\"#6a3d9a\">❸</text>\n",
"</g>\n",
"<!-- 0&#45;&gt;0 -->\n",
"<g id=\"edge3\" class=\"edge\">\n",
"<title>0&#45;&gt;0</title>\n",
"<path fill=\"none\" stroke=\"black\" d=\"M117.81,-35.4C113.5,-60.54 115.23,-98 123,-98 130.08,-98 132.14,-66.97 129.21,-42.44\"/>\n",
"<polygon fill=\"black\" stroke=\"black\" points=\"128.19,-35.4 132.31,-41.88 128.69,-38.86 129.19,-42.33 129.19,-42.33 129.19,-42.33 128.69,-38.86 126.07,-42.78 128.19,-35.4 128.19,-35.4\"/>\n",
"<polygon fill=\"#e9f4fb\" stroke=\"transparent\" points=\"86,-121 86,-140 115,-140 115,-121 86,-121\"/>\n",
"<text text-anchor=\"start\" x=\"88\" y=\"-126.8\" font-family=\"Lato\" font-size=\"14.00\">a | c</text>\n",
"<text text-anchor=\"start\" x=\"119\" y=\"-126.8\" font-family=\"Lato\" font-size=\"14.00\">/</text>\n",
"<polygon fill=\"#ffe5f1\" stroke=\"transparent\" points=\"130,-121 130,-140 161,-140 161,-121 130,-121\"/>\n",
"<text text-anchor=\"start\" x=\"132\" y=\"-126.8\" font-family=\"Lato\" font-size=\"14.00\">b | d</text>\n",
"<text text-anchor=\"start\" x=\"115.5\" y=\"-105.8\" font-family=\"Lato\" font-size=\"14.00\" fill=\"#6a3d9a\">❸</text>\n",
"</g>\n",
"</g>\n",
"</svg>\n"
],
"text/plain": [
"<spot.twa_graph; proxy of <Swig Object of type 'std::shared_ptr< spot::twa_graph > *' at 0x7f3d76e561e0> >"
]
},
"execution_count": 9,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"spot.apply_strategy(g, True, True)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "5948f923",
"metadata": {},
"outputs": [],
"source": []
} }
], ],
"metadata": { "metadata": {
@ -413,7 +314,7 @@
"name": "python", "name": "python",
"nbconvert_exporter": "python", "nbconvert_exporter": "python",
"pygments_lexer": "ipython3", "pygments_lexer": "ipython3",
"version": "3.9.2" "version": "3.8.10"
} }
}, },
"nbformat": 4, "nbformat": 4,

View file

@ -3339,9 +3339,10 @@ for strat_string, (ins_str, outs_str) in strats:
for ss in [""] + [f"+sub{ii}" for ii in range(3)]: for ss in [""] + [f"+sub{ii}" for ii in range(3)]:
for ddx in ["", "+dc"]:# todo prob here for ddx in ["", "+dc"]:# todo prob here
for uud in ["", "+ud"]: for uud in ["", "+ud"]:
aig = spot.strategy_to_aig(strat, m+ss+ddx+uud) aig = spot.mealy_machine_to_aig(strat, m+ss+ddx+uud)
strat2_s = aig.as_automaton(True) strat2_s = aig.as_automaton(True)
if not spot.is_mealy_specialization(strat_s, strat2_s): if not spot.is_split_mealy_specialization(strat_s,
strat2_s):
print(f"Mode is {m+ss+ddx+uud}") print(f"Mode is {m+ss+ddx+uud}")
print(f"""Strat is \n{strat_s.to_str("hoa")}""") print(f"""Strat is \n{strat_s.to_str("hoa")}""")
print(f"""Aig as aut is \n{strat2_s.to_str("hoa")}""") print(f"""Aig as aut is \n{strat2_s.to_str("hoa")}""")
@ -3361,7 +3362,7 @@ for aout in outs_str:
outs &= buddy.bdd_ithvar(strat.register_ap(aout)) outs &= buddy.bdd_ithvar(strat.register_ap(aout))
spot.set_synthesis_outputs(strat, outs) spot.set_synthesis_outputs(strat, outs)
aig = spot.strategy_to_aig(strat, "isop") aig = spot.mealy_machine_to_aig(strat, "isop")
ins = [True, False, False, False, True, True, True, True, True, True] ins = [True, False, False, False, True, True, True, True, True, True]
exp_latches = [(False, False), exp_latches = [(False, False),

View file

@ -43,9 +43,10 @@ spot.set_synthesis_outputs(a, o1&o2)
b = spot.minimize_mealy(a) b = spot.minimize_mealy(a)
assert(list(spot.get_state_players(b)).count(False) == 2) assert(list(spot.get_state_players(b)).count(False) == 2)
assert(spot.is_mealy_specialization(a, b)) assert(spot.is_split_mealy_specialization(a, b))
test_auts = [("""HOA: v1 test_auts = [
("""HOA: v1
States: 22 States: 22
Start: 0 Start: 0
AP: 6 "i0" "i1" "i2" "i3" "o0" "o1" AP: 6 "i0" "i1" "i2" "i3" "o0" "o1"
@ -360,13 +361,6 @@ for (mealy_str, nenv_min) in test_auts:
mealy = spot.automaton(mealy_str) mealy = spot.automaton(mealy_str)
mealy.merge_edges() mealy.merge_edges()
mealy_min_ks = spot.minimize_mealy(mealy, -1, True)
mealy_min_us = spot.minimize_mealy(mealy, -1, False)
n_e = sum([s == 0 for s in spot.get_state_players(mealy_min_ks)])
assert(n_e == nenv_min)
assert(mealy_min_us.num_states() == nenv_min)
assert(spot.is_mealy_specialization(mealy, mealy_min_ks, True))
outs = buddy.bddtrue outs = buddy.bddtrue
ins = buddy.bddtrue ins = buddy.bddtrue
@ -378,8 +372,20 @@ for (mealy_str, nenv_min) in test_auts:
ins = ins & buddy.bdd_ithvar(mealy.register_ap(aap.ap_name())) ins = ins & buddy.bdd_ithvar(mealy.register_ap(aap.ap_name()))
else: else:
assert("""Aps must start with either "i" or "o".""") assert("""Aps must start with either "i" or "o".""")
mealy_min_us_s = spot.split_2step(mealy_min_us, outs, False)
assert(spot.is_mealy_specialization(mealy, mealy_min_us_s, True)) spot.set_synthesis_outputs(mealy, outs)
mealy_min_ks = spot.minimize_mealy(mealy, -1)
n_e = sum([s == 0 for s in spot.get_state_players(mealy_min_ks)])
assert(n_e == nenv_min)
assert(spot.is_split_mealy_specialization(mealy, mealy_min_ks))
# Test un- and resplit
tmp = spot.unsplit_2step(mealy_min_ks)
mealy_min_rs = spot.split_2step(tmp, spot.get_synthesis_outputs(tmp), False)
assert(spot.is_split_mealy_specialization(mealy, mealy_min_rs, True))
assert(spot.are_equivalent(mealy_min_ks, mealy_min_rs))
# Testing bisimulation (with output assignment) # Testing bisimulation (with output assignment)
@ -498,13 +504,23 @@ State: 16
--END--""") --END--""")
# Build an equivalent deterministic monitor # Build an equivalent deterministic monitor
min_equiv = spot.minimize_mealy_fast(aut, False) # ;
# ;
#
spot.set_synthesis_outputs(aut,
buddy.bdd_ithvar(
aut.register_ap("u02alarm29control02alarm29control"))\
& buddy.bdd_ithvar(
aut.register_ap("u02alarm29control0f1d2alarm29turn2on1b"))\
& buddy.bdd_ithvar(
aut.register_ap("u02alarm29control0f1d2alarm29turn2off1b")))
min_equiv = spot.reduce_mealy(aut, False)
assert min_equiv.num_states() == 6 assert min_equiv.num_states() == 6
assert spot.are_equivalent(min_equiv, aut) assert spot.are_equivalent(min_equiv, aut)
# Build an automaton that recognizes a subset of the language of the original # Build an automaton that recognizes a subset of the language of the original
# automaton # automaton
min_sub = spot.minimize_mealy_fast(aut, True) min_sub = spot.reduce_mealy(aut, True)
assert min_sub.num_states() == 5 assert min_sub.num_states() == 5
prod = spot.product(spot.complement(aut), min_sub) prod = spot.product(spot.complement(aut), min_sub)
assert spot.generic_emptiness_check(prod) assert spot.generic_emptiness_check(prod)
@ -531,6 +547,8 @@ State: 3
--END-- --END--
""") """)
spot.set_synthesis_outputs(aut, buddy.bdd_ithvar(aut.register_ap("b")))
exp = """HOA: v1 exp = """HOA: v1
States: 1 States: 1
Start: 0 Start: 0
@ -544,7 +562,7 @@ State: 0
--END--""" --END--"""
# An example that shows that we should not build a tree when we use inclusion. # An example that shows that we should not build a tree when we use inclusion.
res = spot.minimize_mealy_fast(aut, True) res = spot.reduce_mealy(aut, True)
assert res.to_str() == exp assert res.to_str() == exp
aut = spot.automaton(""" aut = spot.automaton("""
@ -569,6 +587,8 @@ State: 3
--END-- --END--
""") """)
spot.set_synthesis_outputs(aut, buddy.bdd_ithvar(aut.register_ap("b")))
exp = """HOA: v1 exp = """HOA: v1
States: 2 States: 2
Start: 0 Start: 0
@ -585,7 +605,7 @@ State: 1
[0&1] 1 [0&1] 1
--END--""" --END--"""
res = spot.minimize_mealy_fast(aut, True) res = spot.reduce_mealy(aut, True)
assert res.to_str() == exp assert res.to_str() == exp

File diff suppressed because it is too large Load diff