// -*- coding: utf-8 -*- // Copyright (C) 2013, 2014, 2015 Laboratoire de Recherche et // Développement de l'Epita. // // This file is part of Spot, a model checking library. // // Spot is free software; you can redistribute it and/or modify it // under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 3 of the License, or // (at your option) any later version. // // Spot is distributed in the hope that it will be useful, but WITHOUT // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY // or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public // License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // If you set the SPOT_TMPKEEP environment variable the temporary // file used to communicate with the sat solver will be left in // the current directory. // // Additionally, if the following DEBUG macro is set to 1, the CNF // file will be output with a comment before each clause, and an // additional output file (dtwa-sat.dbg) will be created with a list // of all positive variables in the result and their meaning. #define DEBUG 0 #if DEBUG #define dout out << "c " #define trace std::cerr #else #define dout while (0) std::cout #define trace dout #endif namespace spot { namespace { static bdd_dict_ptr debug_dict = nullptr; static const acc_cond* debug_ref_acc = nullptr; static const acc_cond* debug_cand_acc = nullptr; struct transition { unsigned src; bdd cond; unsigned dst; transition(int src, bdd cond, int dst) : src(src), cond(cond), dst(dst) { } bool operator<(const transition& other) const { if (this->src < other.src) return true; if (this->src > other.src) return false; if (this->dst < other.dst) return true; if (this->dst > other.dst) return false; return this->cond.id() < other.cond.id(); } bool operator==(const transition& other) const { return (this->src == other.src && this->dst == other.dst && this->cond.id() == other.cond.id()); } }; struct src_cond { unsigned src; bdd cond; src_cond(int src, bdd cond) : src(src), cond(cond) { } bool operator<(const src_cond& other) const { if (this->src < other.src) return true; if (this->src > other.src) return false; return this->cond.id() < other.cond.id(); } bool operator==(const src_cond& other) const { return (this->src == other.src && this->cond.id() == other.cond.id()); } }; struct transition_acc { unsigned src; bdd cond; acc_cond::mark_t acc; unsigned dst; transition_acc(int src, bdd cond, acc_cond::mark_t acc, int dst) : src(src), cond(cond), acc(acc), dst(dst) { } bool operator<(const transition_acc& other) const { if (this->src < other.src) return true; if (this->src > other.src) return false; if (this->dst < other.dst) return true; if (this->dst > other.dst) return false; if (this->cond.id() < other.cond.id()) return true; if (this->cond.id() > other.cond.id()) return false; return this->acc < other.acc; } bool operator==(const transition_acc& other) const { return (this->src == other.src && this->dst == other.dst && this->cond.id() == other.cond.id() && this->acc == other.acc); } }; struct path { unsigned src_cand; unsigned src_ref; unsigned dst_cand; unsigned dst_ref; acc_cond::mark_t acc_cand; acc_cond::mark_t acc_ref; path(unsigned src_cand, unsigned src_ref) : src_cand(src_cand), src_ref(src_ref), dst_cand(src_cand), dst_ref(src_ref), acc_cand(0U), acc_ref(0U) { } path(unsigned src_cand, unsigned src_ref, unsigned dst_cand, unsigned dst_ref, acc_cond::mark_t acc_cand, acc_cond::mark_t acc_ref) : src_cand(src_cand), src_ref(src_ref), dst_cand(dst_cand), dst_ref(dst_ref), acc_cand(acc_cand), acc_ref(acc_ref) { } bool operator<(const path& other) const { if (this->src_cand < other.src_cand) return true; if (this->src_cand > other.src_cand) return false; if (this->src_ref < other.src_ref) return true; if (this->src_ref > other.src_ref) return false; if (this->dst_cand < other.dst_cand) return true; if (this->dst_cand > other.dst_cand) return false; if (this->dst_ref < other.dst_ref) return true; if (this->dst_ref > other.dst_ref) return false; if (this->acc_ref < other.acc_ref) return true; if (this->acc_ref > other.acc_ref) return false; if (this->acc_cand < other.acc_cand) return true; if (this->acc_cand > other.acc_cand) return false; return false; } }; std::ostream& operator<<(std::ostream& os, const transition& t) { os << '<' << t.src << ',' << bdd_format_formula(debug_dict, t.cond) << ',' << t.dst << '>'; return os; } std::ostream& operator<<(std::ostream& os, const transition_acc& t) { os << '<' << t.src << ',' << bdd_format_formula(debug_dict, t.cond) << ',' << debug_cand_acc->format(t.acc) << ',' << t.dst << '>'; return os; } std::ostream& operator<<(std::ostream& os, const path& p) { os << '<' << p.src_cand << ',' << p.src_ref << ',' << p.dst_cand << ',' << p.dst_ref << ", " << debug_cand_acc->format(p.acc_cand) << ", " << debug_ref_acc->format(p.acc_ref) << '>'; return os; } // If the DNF is // Fin(1)&Fin(2)&Inf(3) | Fin(0)&Inf(3) | Fin(4)&Inf(5)&Inf(6) // this returns the following map: // {3} => [{1,2} {0}] // {5} => [{4}] // {6} => [{4}] // We use that do detect (and disallow) what we call "silly histories", // i.e., transitions or histories labeled by sets such as // {3,1,0}, that have no way to be satisfied. So whenever we see // such history in a path, we actually map it to {1,0} instead, // which is enough to remember that this history is not satisfiable. // We also forbid any transition from being labeled by {3,1,0}. typedef std::map> trimming_map; static trimming_map split_dnf_acc_by_inf(const acc_cond::acc_code& input_acc) { trimming_map res; auto acc = input_acc.to_dnf(); auto pos = &acc.back(); if (pos->op == acc_cond::acc_op::Or) --pos; acc_cond::mark_t all_fin = 0U; auto start = &acc.front(); while (pos > start) { if (pos->op == acc_cond::acc_op::Fin) { // We have only a Fin term, without Inf. // There is nothing to do about it. pos -= pos->size + 1; } else { // We have a conjunction of Fin and Inf sets. auto end = pos - pos->size - 1; acc_cond::mark_t fin = 0U; acc_cond::mark_t inf = 0U; while (pos > end) { switch (pos->op) { case acc_cond::acc_op::And: --pos; break; case acc_cond::acc_op::Fin: fin |= pos[-1].mark; assert(pos[-1].mark.count() == 1); pos -= 2; break; case acc_cond::acc_op::Inf: inf |= pos[-1].mark; pos -= 2; break; case acc_cond::acc_op::FinNeg: case acc_cond::acc_op::InfNeg: case acc_cond::acc_op::Or: SPOT_UNREACHABLE(); break; } } assert(pos == end); all_fin |= fin; for (unsigned i: inf.sets()) if (fin) { res[i].emplace_back(fin); } else { // Make sure the empty set is always the first one. res[i].clear(); res[i].emplace_back(fin); } } } // Remove entries that are necessarily false because they // contain an emptyset, or entries that also appear as Fin // somewhere in the acceptance. auto i = res.begin(); while (i != res.end()) { if (all_fin.has(i->first) || !i->second[0]) i = res.erase(i); else ++i; } return res; } struct dict { dict(const const_twa_ptr& a) : aut(a) { } const_twa_ptr aut; typedef std::map trans_map; typedef std::map trans_acc_map; trans_map transid; trans_acc_map transaccid; typedef std::map rev_map; typedef std::map rev_acc_map; rev_map revtransid; rev_acc_map revtransaccid; std::map pathid; int nvars = 0; //typedef std::unordered_map state_map; //typedef std::unordered_map int_map; //state_map state_to_int; // int_map int_to_state; unsigned cand_size; unsigned int cand_nacc; acc_cond::acc_code cand_acc; std::vector all_cand_acc; std::vector all_ref_acc; // Markings that make no sense and that we do not want to see in // the candidate. See comment above split_dnf_acc_by_inf(). std::vector all_silly_cand_acc; std::vector is_weak_scc; std::vector scc_marks; acc_cond cacc; trimming_map ref_inf_trim_map; trimming_map cand_inf_trim_map; ~dict() { aut->get_dict()->unregister_all_my_variables(this); } acc_cond::mark_t inf_trim(acc_cond::mark_t m, trimming_map& tm) { for (auto& s: tm) { unsigned inf = s.first; if (m.has(inf)) { bool remove = true; for (auto fin: s.second) if (!(m & fin)) { remove = false; break; } if (remove) m.clear(inf); } } return m; } acc_cond::mark_t ref_inf_trim(acc_cond::mark_t m) { return inf_trim(m, ref_inf_trim_map); } acc_cond::mark_t cand_inf_trim(acc_cond::mark_t m) { return inf_trim(m, cand_inf_trim_map); } }; unsigned declare_vars(const const_twa_graph_ptr& aut, dict& d, bdd ap, bool state_based, scc_info& sm) { d.is_weak_scc = sm.weak_sccs(); unsigned scccount = sm.scc_count(); { auto tmp = sm.used_acc(); d.scc_marks.reserve(scccount); for (auto& v: tmp) { acc_cond::mark_t m = 0U; for (auto i: v) m |= i; d.scc_marks.emplace_back(m); } } d.cacc.add_sets(d.cand_nacc); d.cacc.set_acceptance(d.cand_acc); // If the acceptance conditions use both Fin and Inf primitives, // we may have some silly history configurations to ignore. if (aut->acc().uses_fin_acceptance()) d.ref_inf_trim_map = split_dnf_acc_by_inf(aut->get_acceptance()); if (d.cacc.uses_fin_acceptance()) d.cand_inf_trim_map = split_dnf_acc_by_inf(d.cand_acc); bdd_dict_ptr bd = aut->get_dict(); d.all_cand_acc.push_back(0U); for (unsigned n = 0; n < d.cand_nacc; ++n) { auto c = d.cacc.mark(n); size_t ss = d.all_silly_cand_acc.size(); for (size_t i = 0; i < ss; ++i) d.all_silly_cand_acc.push_back(d.all_silly_cand_acc[i] | c); size_t s = d.all_cand_acc.size(); for (size_t i = 0; i < s; ++i) { acc_cond::mark_t m = d.all_cand_acc[i] | c; if (d.cand_inf_trim(m) == m) d.all_cand_acc.push_back(m); else d.all_silly_cand_acc.push_back(m); } } d.all_ref_acc.push_back(0U); unsigned ref_nacc = aut->num_sets(); for (unsigned n = 0; n < ref_nacc; ++n) { auto c = aut->acc().mark(n); size_t s = d.all_ref_acc.size(); for (size_t i = 0; i < s; ++i) { acc_cond::mark_t m = d.all_ref_acc[i] | c; if (d.ref_inf_trim(m) != m) continue; d.all_ref_acc.push_back(m); } } unsigned ref_size = aut->num_states(); if (d.cand_size == -1U) for (unsigned i = 0; i < ref_size; ++i) if (sm.reachable_state(i)) ++d.cand_size; // Note that we start from -1U the // cand_size is one less than the // number of reachable states. for (unsigned i = 0; i < ref_size; ++i) { if (!sm.reachable_state(i)) continue; unsigned i_scc = sm.scc_of(i); bool is_weak = d.is_weak_scc[i_scc]; for (unsigned j = 0; j < d.cand_size; ++j) { for (unsigned k = 0; k < ref_size; ++k) { if (!sm.reachable_state(k)) continue; if (sm.scc_of(k) != i_scc) continue; for (unsigned l = 0; l < d.cand_size; ++l) { size_t sfp = is_weak ? 1 : d.all_ref_acc.size(); acc_cond::mark_t sccmarks = d.scc_marks[i_scc]; for (size_t fp = 0; fp < sfp; ++fp) { auto refhist = d.all_ref_acc[fp]; // refhist cannot have more sets than used in the SCC if (!is_weak && (sccmarks & refhist) != refhist) continue; size_t sf = d.all_cand_acc.size(); for (size_t f = 0; f < sf; ++f) { path p(j, i, l, k, d.all_cand_acc[f], refhist); d.pathid[p] = ++d.nvars; } } } } } } if (!state_based) { for (unsigned i = 0; i < d.cand_size; ++i) for (unsigned j = 0; j < d.cand_size; ++j) { bdd all = bddtrue; while (all != bddfalse) { bdd one = bdd_satoneset(all, ap, bddfalse); all -= one; transition t(i, one, j); d.transid[t] = ++d.nvars; d.revtransid.emplace(d.nvars, t); // Create the variable for the accepting transition // immediately afterwards. It helps parsing the // result. for (unsigned n = 0; n < d.cand_nacc; ++n) { transition_acc ta(i, one, d.cacc.mark(n), j); d.transaccid[ta] = ++d.nvars; d.revtransaccid.emplace(d.nvars, ta); } } } } else // state based { for (unsigned i = 0; i < d.cand_size; ++i) for (unsigned n = 0; n < d.cand_nacc; ++n) { ++d.nvars; for (unsigned j = 0; j < d.cand_size; ++j) { bdd all = bddtrue; while (all != bddfalse) { bdd one = bdd_satoneset(all, ap, bddfalse); all -= one; transition_acc ta(i, one, d.cacc.mark(n), j); d.transaccid[ta] = d.nvars; d.revtransaccid.emplace(d.nvars, ta); } } } for (unsigned i = 0; i < d.cand_size; ++i) for (unsigned j = 0; j < d.cand_size; ++j) { bdd all = bddtrue; while (all != bddfalse) { bdd one = bdd_satoneset(all, ap, bddfalse); all -= one; transition t(i, one, j); d.transid[t] = ++d.nvars; d.revtransid.emplace(d.nvars, t); } } } return ref_size; } typedef std::pair sat_stats; static sat_stats dtwa_to_sat(std::ostream& out, const_twa_graph_ptr ref, dict& d, bool state_based, bool colored) { #if DEBUG debug_dict = ref->get_dict(); #endif clause_counter nclauses; // Compute the AP used in the hard way. bdd ap = bddtrue; for (auto& t: ref->edges()) ap &= bdd_support(t.cond); // Count the number of atomic propositions int nap = 0; { bdd cur = ap; while (cur != bddtrue) { ++nap; cur = bdd_high(cur); } nap = 1 << nap; } scc_info sm(ref); sm.determine_unknown_acceptance(); // Number all the SAT variables we may need. unsigned ref_size = declare_vars(ref, d, ap, state_based, sm); // empty automaton is impossible if (d.cand_size == 0) { out << "p cnf 1 2\n-1 0\n1 0\n"; return std::make_pair(1, 2); } // An empty line for the header out << " \n"; #if DEBUG debug_ref_acc = &ref->acc(); debug_cand_acc = &d.cacc; dout << "ref_size: " << ref_size << '\n'; dout << "cand_size: " << d.cand_size << '\n'; #endif auto& racc = ref->acc(); dout << "symmetry-breaking clauses\n"; int j = 0; bdd all = bddtrue; while (all != bddfalse) { bdd s = bdd_satoneset(all, ap, bddfalse); all -= s; for (unsigned i = 0; i < d.cand_size - 1; ++i) for (unsigned k = i * nap + j + 2; k < d.cand_size; ++k) { transition t(i, s, k); int ti = d.transid[t]; dout << "¬" << t << '\n'; out << -ti << " 0\n"; ++nclauses; } ++j; } if (!nclauses.nb_clauses()) dout << "(none)\n"; dout << "(8) the candidate automaton is complete\n"; for (unsigned q1 = 0; q1 < d.cand_size; ++q1) { bdd all = bddtrue; while (all != bddfalse) { bdd s = bdd_satoneset(all, ap, bddfalse); all -= s; #if DEBUG dout; for (unsigned q2 = 0; q2 < d.cand_size; ++q2) { transition t(q1, s, q2); out << t << "δ"; if (q2 != d.cand_size) out << " ∨ "; } out << '\n'; #endif for (unsigned q2 = 0; q2 < d.cand_size; ++q2) { transition t(q1, s, q2); int ti = d.transid[t]; out << ti << ' '; } out << "0\n"; ++nclauses; } } dout << "(9) the initial state is reachable\n"; { unsigned init = ref->get_init_state_number(); dout << path(0, init) << '\n'; out << d.pathid[path(0, init)] << " 0\n"; ++nclauses; } if (colored) { unsigned nacc = d.cand_nacc; dout << "transitions belong to exactly one of the " << nacc << " acceptance set\n"; bdd all = bddtrue; while (all != bddfalse) { bdd l = bdd_satoneset(all, ap, bddfalse); all -= l; for (unsigned q1 = 0; q1 < d.cand_size; ++q1) for (unsigned q2 = 0; q2 < d.cand_size; ++q2) { for (unsigned i = 0; i < nacc; ++i) { transition_acc ti(q1, l, {i}, q2); int tai = d.transaccid[ti]; for (unsigned j = 0; j < nacc; ++j) if (i != j) { transition_acc tj(q1, l, {j}, q2); int taj = d.transaccid[tj]; out << -tai << ' ' << -taj << " 0\n"; ++nclauses; } } for (unsigned i = 0; i < nacc; ++i) { transition_acc ti(q1, l, {i}, q2); int tai = d.transaccid[ti]; out << tai << ' '; } out << "0\n"; ++nclauses; } } } if (!d.all_silly_cand_acc.empty()) { dout << "no transition with silly acceptance\n"; bdd all = bddtrue; while (all != bddfalse) { bdd l = bdd_satoneset(all, ap, bddfalse); all -= l; for (unsigned q1 = 0; q1 < d.cand_size; ++q1) for (unsigned q2 = 0; q2 < d.cand_size; ++q2) for (auto& s: d.all_silly_cand_acc) { dout << "no (" << q1 << ',' << bdd_format_formula(debug_dict, l) << ',' << s << ',' << q2 << ")\n"; for (unsigned v: s.sets()) { transition_acc ta(q1, l, d.cacc.mark(v), q2); int tai = d.transaccid[ta]; assert(tai != 0); out << ' ' << -tai; } for (unsigned v: d.cacc.comp(s).sets()) { transition_acc ta(q1, l, d.cacc.mark(v), q2); int tai = d.transaccid[ta]; assert(tai != 0); out << ' ' << tai; } out << " 0\n"; ++nclauses; } } } for (unsigned q1 = 0; q1 < d.cand_size; ++q1) for (unsigned q1p = 0; q1p < ref_size; ++q1p) { if (!sm.reachable_state(q1p)) continue; dout << "(10) augmenting paths based on Cand[" << q1 << "] and Ref[" << q1p << "]\n"; path p1(q1, q1p); int p1id = d.pathid[p1]; for (auto& tr: ref->out(q1p)) { unsigned dp = tr.dst; bdd all = tr.cond; while (all != bddfalse) { bdd s = bdd_satoneset(all, ap, bddfalse); all -= s; for (unsigned q2 = 0; q2 < d.cand_size; ++q2) { transition t(q1, s, q2); int ti = d.transid[t]; path p2(q2, dp); int succ = d.pathid[p2]; if (p1id == succ) continue; dout << p1 << " ∧ " << t << "δ → " << p2 << '\n'; out << -p1id << ' ' << -ti << ' ' << succ << " 0\n"; ++nclauses; } } } } // construction of constraints (11,12,13) for (unsigned q1p = 0; q1p < ref_size; ++q1p) { if (!sm.reachable_state(q1p)) continue; unsigned q1p_scc = sm.scc_of(q1p); for (unsigned q2p = 0; q2p < ref_size; ++q2p) { if (!sm.reachable_state(q2p)) continue; // We are only interested in transition that can form a // cycle, so they must belong to the same SCC. if (sm.scc_of(q2p) != q1p_scc) continue; bool is_weak = d.is_weak_scc[q1p_scc]; bool is_rej = sm.is_rejecting_scc(q1p_scc); for (unsigned q1 = 0; q1 < d.cand_size; ++q1) for (unsigned q2 = 0; q2 < d.cand_size; ++q2) { size_t sf = d.all_cand_acc.size(); size_t sfp = is_weak ? 1 : d.all_ref_acc.size(); acc_cond::mark_t sccmarks = d.scc_marks[q1p_scc]; for (size_t f = 0; f < sf; ++f) for (size_t fp = 0; fp < sfp; ++fp) { auto refhist = d.all_ref_acc[fp]; // refhist cannot have more sets than used in the SCC if (!is_weak && (sccmarks & refhist) != refhist) continue; path p(q1, q1p, q2, q2p, d.all_cand_acc[f], refhist); dout << "(11&12&13) paths from " << p << '\n'; int pid = d.pathid[p]; for (auto& tr: ref->out(q2p)) { unsigned dp = tr.dst; // Skip destinations not in the SCC. if (sm.scc_of(dp) != q1p_scc) continue; for (unsigned q3 = 0; q3 < d.cand_size; ++q3) { bdd all = tr.cond; acc_cond::mark_t curacc = tr.acc; while (all != bddfalse) { bdd l = bdd_satoneset(all, ap, bddfalse); all -= l; transition t(q2, l, q3); int ti = d.transid[t]; if (dp == q1p && q3 == q1) // (11,12) loop { bool rejloop = (is_rej || !racc.accepting (curacc | d.all_ref_acc[fp])); auto missing = d.cand_acc. missing(d.all_cand_acc[f], !rejloop); for (auto& v: missing) { #if DEBUG dout << (rejloop ? "(11) " : "(12) ") << p << " ∧ " << t << "δ → ("; const char* orsep = ""; for (int s: v) { if (s < 0) { transition_acc ta(q2, l, d.cacc.mark(-s - 1), q1); out << orsep << "¬" << ta; } else { transition_acc ta(q2, l, d.cacc.mark(s), q1); out << orsep << ta; } out << "FC"; orsep = " ∨ "; } out << ")\n"; #endif // DEBUG out << -pid << ' ' << -ti; for (int s: v) if (s < 0) { transition_acc ta(q2, l, d.cacc.mark(-s - 1), q1); int tai = d.transaccid[ta]; assert(tai != 0); out << ' ' << -tai; } else { transition_acc ta(q2, l, d.cacc.mark(s), q1); int tai = d.transaccid[ta]; assert(tai != 0); out << ' ' << tai; } out << " 0\n"; ++nclauses; } } // (13) augmenting paths (always). { size_t sf = d.all_cand_acc.size(); for (size_t f = 0; f < sf; ++f) { acc_cond::mark_t f2 = d.cand_inf_trim (p.acc_cand | d.all_cand_acc[f]); acc_cond::mark_t f2p = 0U; if (!is_weak) f2p = d.ref_inf_trim(p.acc_ref | curacc); path p2(p.src_cand, p.src_ref, q3, dp, f2, f2p); int p2id = d.pathid[p2]; if (pid == p2id) continue; #if DEBUG dout << "(13) " << p << " ∧ " << t << "δ "; auto biga_ = d.all_cand_acc[f]; for (unsigned m = 0; m < d.cand_nacc; ++m) { transition_acc ta(q2, l, d.cacc.mark(m), q3); const char* not_ = "¬"; if (d.cacc.has(biga_, m)) not_ = ""; out << " ∧ " << not_ << ta << "FC"; } out << " → " << p2 << '\n'; #endif out << -pid << ' ' << -ti << ' '; auto biga = d.all_cand_acc[f]; for (unsigned m = 0; m < d.cand_nacc; ++m) { transition_acc ta(q2, l, d.cacc.mark(m), q3); int tai = d.transaccid[ta]; if (biga.has(m)) tai = -tai; out << tai << ' '; } out << p2id << " 0\n"; ++nclauses; } } } } } } } } } out.seekp(0); out << "p cnf " << d.nvars << ' ' << nclauses.nb_clauses(); return std::make_pair(d.nvars, nclauses.nb_clauses()); } static twa_graph_ptr sat_build(const satsolver::solution& solution, dict& satdict, const_twa_graph_ptr aut, bool state_based) { auto autdict = aut->get_dict(); auto a = make_twa_graph(autdict); a->copy_ap_of(aut); if (state_based) a->prop_state_acc(true); a->prop_deterministic(true); a->set_acceptance(satdict.cand_nacc, satdict.cand_acc); a->new_states(satdict.cand_size); // Last transition set in the automaton. unsigned last_aut_trans = -1U; // Last transition read from the SAT result. const transition* last_sat_trans = nullptr; #if DEBUG std::fstream out("dtwa-sat.dbg", std::ios_base::trunc | std::ios_base::out); out.exceptions(std::ifstream::failbit | std::ifstream::badbit); std::set positive; #endif dout << "--- transition variables ---\n"; std::map state_acc; std::set seen_trans; for (int v: solution) { if (v < 0) // FIXME: maybe we can have (v < NNN)? continue; #if DEBUG positive.insert(v); #endif dict::rev_map::const_iterator t = satdict.revtransid.find(v); if (t != satdict.revtransid.end()) { // Skip (s,l,d2) if we have already seen some (s,l,d1). if (seen_trans.insert(src_cond(t->second.src, t->second.cond)).second) { acc_cond::mark_t acc = 0U; if (state_based) { auto i = state_acc.find(t->second.src); if (i != state_acc.end()) acc = i->second; } last_aut_trans = a->new_edge(t->second.src, t->second.dst, t->second.cond, acc); last_sat_trans = &t->second; dout << v << '\t' << t->second << "δ\n"; } } else { dict::rev_acc_map::const_iterator ta; ta = satdict.revtransaccid.find(v); // This assumes that the sat solvers output variables in // increasing order. if (ta != satdict.revtransaccid.end()) { dout << v << '\t' << ta->second << "F\n"; if (last_sat_trans && ta->second.src == last_sat_trans->src && ta->second.cond == last_sat_trans->cond && ta->second.dst == last_sat_trans->dst) { assert(!state_based); auto& v = a->edge_data(last_aut_trans).acc; v |= ta->second.acc; } else if (state_based) { auto& v = state_acc[ta->second.src]; v |= ta->second.acc; } } } } #if DEBUG dout << "--- pathid variables ---\n"; for (auto pit: satdict.pathid) if (positive.find(pit.second) != positive.end()) dout << pit.second << '\t' << pit.first << "C\n"; #endif a->merge_edges(); return a; } } twa_graph_ptr dtwa_sat_synthetize(const const_twa_graph_ptr& a, unsigned target_acc_number, const acc_cond::acc_code& target_acc, int target_state_number, bool state_based, bool colored) { if (target_state_number == 0) return nullptr; trace << "dtwa_sat_synthetize(..., nacc = " << target_acc_number << ", acc = \"" << target_acc << "\", states = " << target_state_number << ", state_based = " << state_based << ")\n"; dict d(a); d.cand_size = target_state_number; d.cand_nacc = target_acc_number; d.cand_acc = target_acc; satsolver solver; satsolver::solution_pair solution; timer_map t; t.start("encode"); sat_stats s = dtwa_to_sat(solver(), a, d, state_based, colored); t.stop("encode"); t.start("solve"); solution = solver.get_solution(); t.stop("solve"); twa_graph_ptr res = nullptr; if (!solution.second.empty()) res = sat_build(solution.second, d, a, state_based); // Always copy the environment variable into a static string, // so that we (1) look it up once, but (2) won't crash if the // environment is changed. static std::string log = []() { auto s = getenv("SPOT_SATLOG"); return s ? s : ""; }(); if (!log.empty()) { std::fstream out(log, std::ios_base::app | std::ios_base::out); out.exceptions(std::ifstream::failbit | std::ifstream::badbit); const timer& te = t.timer("encode"); const timer& ts = t.timer("solve"); out << target_state_number << ','; if (res) { twa_sub_statistics st = sub_stats_reachable(res); out << st.states << ',' << st.edges << ',' << st.transitions; } else { out << ",,"; } out << ',' << s.first << ',' << s.second << ',' << te.utime() << ',' << te.stime() << ',' << ts.utime() << ',' << ts.stime() << '\n'; } static bool show = getenv("SPOT_SATSHOW"); if (show && res) print_dot(std::cout, res); trace << "dtwa_sat_synthetize(...) = " << res << '\n'; return res; } namespace { // Chose a good reference automaton given two automata. // // The right automaton only is allowed to be null. In that // case the left automaton is returned. // // The selection relies on the fact that the SAT encoding is // quadratic in the number of input states, and exponential in the // number of input sets. static const_twa_graph_ptr best_aut(const const_twa_graph_ptr& left, const const_twa_graph_ptr& right) { if (right == nullptr) return left; auto lstates = left->num_states(); auto lsets = left->num_sets(); auto rstates = right->num_states(); auto rsets = right->num_sets(); if (lstates <= rstates && lsets <= rsets) return left; if (lstates >= rstates && lsets >= rsets) return right; long long unsigned lw = (1ULL << lsets) * lstates * lstates; long long unsigned rw = (1ULL << rsets) * rstates * rstates; return lw <= rw ? left : right; } } twa_graph_ptr dtwa_sat_minimize(const const_twa_graph_ptr& a, unsigned target_acc_number, const acc_cond::acc_code& target_acc, bool state_based, int max_states, bool colored) { int n_states = (max_states < 0) ? stats_reachable(a).states : max_states + 1; twa_graph_ptr prev = nullptr; for (;;) { auto src = best_aut(a, prev); auto next = dtwa_sat_synthetize(src, target_acc_number, target_acc, --n_states, state_based, colored); if (!next) return prev; else n_states = stats_reachable(next).states; prev = next; } SPOT_UNREACHABLE(); } twa_graph_ptr dtwa_sat_minimize_dichotomy(const const_twa_graph_ptr& a, unsigned target_acc_number, const acc_cond::acc_code& target_acc, bool state_based, int max_states, bool colored) { if (max_states < 1) max_states = stats_reachable(a).states - 1; int min_states = 1; twa_graph_ptr prev = nullptr; while (min_states <= max_states) { int target = (max_states + min_states) / 2; auto src = best_aut(a, prev); auto next = dtwa_sat_synthetize(src, target_acc_number, target_acc, target, state_based, colored); if (!next) { min_states = target + 1; } else { prev = next; max_states = stats_reachable(next).states - 1; } } return prev; } twa_graph_ptr sat_minimize(twa_graph_ptr a, const char* opt, bool state_based) { option_map om; auto err = om.parse_options(opt); if (err) { std::string e = "failed to parse option near "; e += err; throw std::runtime_error(e); } if (!is_deterministic(a)) throw std::runtime_error ("SAT-based minimization only work with deterministic automata"); bool dicho = om.get("dichotomy", 0); int states = om.get("states", -1); int max_states = om.get("max-states", -1); auto accstr = om.get_str("acc"); bool colored = om.get("colored", 0); // Assume we are going to use the input automaton acceptance... bool user_supplied_acc = false; acc_cond::acc_code target_acc = a->get_acceptance(); int nacc = a->num_sets(); if (accstr == "same") accstr.clear(); // ... unless the user specified otherwise if (!accstr.empty()) { user_supplied_acc = true; target_acc = acc_cond::acc_code(accstr.c_str()); // Just in case we were given something like // Fin(1) | Inf(3) // Rewrite it as // Fin(0) | Inf(1) // without holes in the set numbers acc_cond::mark_t used = target_acc.used_sets(); acc_cond a(used.max_set()); target_acc = target_acc.strip(a.comp(used), true); nacc = used.count(); } bool target_is_buchi = false; { acc_cond acccond(nacc); acccond.set_acceptance(target_acc); target_is_buchi = acccond.is_buchi(); } if (int preproc = om.get("preproc", 3)) { postprocessor post; auto sba = (state_based && a->prop_state_acc()) ? postprocessor::SBAcc : postprocessor::Any; post.set_pref(postprocessor::Deterministic | postprocessor::Complete | sba); post.set_type(postprocessor::Generic); postprocessor::optimization_level level; switch (preproc) { case 1: level = postprocessor::Low; break; case 2: level = postprocessor::Medium; break; case 3: level = postprocessor::High; break; default: throw std::runtime_error("preproc should be a value between 0 and 3."); } post.set_level(level); a = post.run(a, nullptr); // If we have WDBA, it is necessarily minimal because // postprocessor always run WDBA minimization in Deterministic // mode. If the desired output is a Büchi automaton, or not // desired acceptance was specified, stop here. There is not // point in minimizing a minimal automaton. if (a->prop_inherently_weak() && a->prop_deterministic() && (target_is_buchi || !user_supplied_acc)) return a; } else { complete_here(a); } if (states == -1 && max_states == -1) { if (state_based) max_states = sbacc(a)->num_states(); else max_states = a->num_states(); // If we have not user-supplied acceptance, the input // automaton is a valid one, so we start the search with one // less state. max_states -= !user_supplied_acc; } if (states == -1) { auto orig = a; if (!target_is_buchi || !a->acc().is_buchi() || colored) a = (dicho ? dtwa_sat_minimize_dichotomy : dtwa_sat_minimize) (a, nacc, target_acc, state_based, max_states, colored); else a = (dicho ? dtba_sat_minimize_dichotomy : dtba_sat_minimize) (a, state_based, max_states); if (!a && !user_supplied_acc) a = orig; } else { if (!target_is_buchi || !a->acc().is_buchi() || colored) a = dtwa_sat_synthetize(a, nacc, target_acc, states, state_based, colored); else a = dtba_sat_synthetize(a, states, state_based); } if (a) { if (state_based) a = scc_filter_states(a); else a = scc_filter(a); } return a; } }