spot/spot/twaalgos/dtbasat.cc
Alexandre Duret-Lutz 3d05ecb4ac remove many useless includes
Removal suggestions from clang-include-cleaner-17 applied manually.

* spot/gen/automata.cc, spot/ltsmin/ltsmin.cc, spot/misc/bitvect.cc,
spot/misc/intvcomp.cc, spot/misc/satsolver.cc, spot/misc/tmpfile.cc,
spot/priv/trim.cc, spot/priv/weight.cc, spot/ta/taexplicit.cc,
spot/ta/tgtaexplicit.cc, spot/ta/tgtaproduct.cc,
spot/taalgos/emptinessta.cc, spot/taalgos/minimize.cc,
spot/taalgos/reachiter.cc, spot/taalgos/statessetbuilder.cc,
spot/taalgos/tgba2ta.cc, spot/tl/apcollect.cc, spot/tl/contain.cc,
spot/tl/exclusive.cc, spot/tl/formula.cc, spot/tl/mark.cc,
spot/tl/randomltl.cc, spot/tl/relabel.cc, spot/tl/remove_x.cc,
spot/twa/acc.cc, spot/twa/bdddict.cc, spot/twa/taatgba.cc,
spot/twa/twagraph.cc, spot/twaalgos/aiger.cc,
spot/twaalgos/alternation.cc, spot/twaalgos/canonicalize.cc,
spot/twaalgos/cobuchi.cc, spot/twaalgos/complement.cc,
spot/twaalgos/compsusp.cc, spot/twaalgos/dbranch.cc,
spot/twaalgos/degen.cc, spot/twaalgos/determinize.cc,
spot/twaalgos/dot.cc, spot/twaalgos/dtbasat.cc,
spot/twaalgos/dtwasat.cc, spot/twaalgos/emptiness.cc,
spot/twaalgos/forq_contains.cc, spot/twaalgos/game.cc,
spot/twaalgos/genem.cc, spot/twaalgos/gv04.cc, spot/twaalgos/hoa.cc,
spot/twaalgos/isunamb.cc, spot/twaalgos/isweakscc.cc,
spot/twaalgos/lbtt.cc, spot/twaalgos/ltl2tgba_fm.cc,
spot/twaalgos/magic.cc, spot/twaalgos/mealy_machine.cc,
spot/twaalgos/minimize.cc, spot/twaalgos/neverclaim.cc,
spot/twaalgos/parity.cc, spot/twaalgos/powerset.cc,
spot/twaalgos/product.cc, spot/twaalgos/randomgraph.cc,
spot/twaalgos/randomize.cc, spot/twaalgos/relabel.cc,
spot/twaalgos/remfin.cc, spot/twaalgos/remprop.cc,
spot/twaalgos/sccinfo.cc, spot/twaalgos/se05.cc,
spot/twaalgos/sepsets.cc, spot/twaalgos/simulation.cc,
spot/twaalgos/split.cc, spot/twaalgos/strength.cc,
spot/twaalgos/stutter.cc, spot/twaalgos/synthesis.cc,
spot/twaalgos/tau03.cc, spot/twaalgos/tau03opt.cc,
spot/twaalgos/translate.cc, spot/twacube/cube.cc: Remove useless
includes.
2023-11-29 22:38:21 +01:00

1027 lines
34 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// -*- coding: utf-8 -*-
// Copyright (C) by the Spot authors, see the AUTHORS file for details.
//
// This file is part of Spot, a model checking library.
//
// Spot is free software; you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 3 of the License, or
// (at your option) any later version.
//
// Spot is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
// License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "config.h"
#include <map>
#include <spot/misc/bddlt.hh>
#include <spot/misc/satsolver.hh>
#include <spot/misc/timer.hh>
#include <spot/priv/satcommon.hh>
#include <spot/twaalgos/dtbasat.hh>
#include <spot/twaalgos/langmap.hh>
#include <spot/twaalgos/sccinfo.hh>
// 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 TRACE macro is set to 1, the CNF
// file will be output with a comment before each clause, and an
// additional output file (dtba-sat.dbg) will be created with a list
// of all positive variables in the result and their meaning.
#define TRACE 0
#if TRACE
#define dout out << "c "
#define cnf_comment(...) solver.comment(__VA_ARGS__)
#define trace std::cerr
#else
#define cnf_comment(...) while (0) solver.comment(__VA_ARGS__)
#define dout while (0) std::cout
#define trace dout
#endif
namespace spot
{
namespace
{
static bdd_dict_ptr debug_dict;
struct path
{
int src_ref;
int dst_ref;
path(int src_ref, int dst_ref)
: src_ref(src_ref), dst_ref(dst_ref)
{
}
path(int src_ref)
: src_ref(src_ref), dst_ref(src_ref)
{
}
bool operator<(const path& other) const
{
if (this->src_ref < other.src_ref)
return true;
if (this->src_ref > other.src_ref)
return false;
return this->dst_ref < other.dst_ref;
}
};
struct dict
{
std::vector<bdd> alpha_vect;
std::map<path, unsigned> path_map;
std::map<bdd, unsigned, bdd_less_than> alpha_map;
vars_helper helper;
int nvars = 0;
unsigned cand_size;
unsigned ref_size;
int
transid(unsigned src, unsigned cond, unsigned dst)
{
return helper.get_t(src, cond, dst);
}
int
transid(unsigned src, bdd& cond, unsigned dst)
{
#if TRACE
try
{
return helper.get_t(src, alpha_map.at(cond), dst);
}
catch (const std::out_of_range& c)
{
std::cerr << "label of transid " << fmt_t(src, cond, dst)
<< " not found.\n";
throw c;
}
#else
return helper.get_t(src, alpha_map[cond], dst);
#endif
}
int
transacc(unsigned src, unsigned cond, unsigned dst)
{
return helper.get_ta(src, cond, dst);
}
int
transacc(unsigned src, bdd& cond, unsigned dst)
{
#if TRACE
try
{
return helper.get_ta(src, alpha_map.at(cond), dst);
}
catch (const std::out_of_range& c)
{
std::cerr << "label of transacc " << fmt_t(src, cond, dst)
<< " not found.\n";
throw c;
}
#else
return helper.get_ta(src, alpha_map[cond], dst);
#endif
}
int
pathid_ref(unsigned src_cand, unsigned src_ref, unsigned dst_cand,
unsigned dst_ref)
{
#if TRACE
try
{
return helper.get_prc(
path_map.at(path(src_ref, dst_ref)), src_cand, dst_cand, false);
}
catch (const std::out_of_range& c)
{
std::cerr << "path(" << src_ref << ',' << dst_ref << ") of pathid_ref"
<< ' ' << fmt_p(src_cand, src_ref, dst_cand, dst_ref)
<< " not found.\n";
throw c;
}
#else
return helper.get_prc(
path_map[path(src_ref, dst_ref)], src_cand, dst_cand, false);
#endif
}
#if TRACE
int
pathid_ref(unsigned path, unsigned src_cand, unsigned dst_cand)
{
return helper.get_prc(path, src_cand, dst_cand, false);
}
int
pathid_cand(unsigned path, unsigned src_cand, unsigned dst_cand)
{
return helper.get_prc(path, src_cand, dst_cand, true);
}
#endif
int
pathid_cand(unsigned src_cand, unsigned src_ref, unsigned dst_cand,
unsigned dst_ref)
{
#if TRACE
try
{
return helper.get_prc(
path_map.at(path(src_ref, dst_ref)), src_cand, dst_cand, true);
}
catch (const std::out_of_range& c)
{
std::cerr << "path(" << src_ref << ',' << dst_ref
<< ") of pathid_cand "
<< fmt_p(src_cand, src_ref, dst_cand, dst_ref) << " not found.\n";
throw c;
}
#else
return helper.get_prc(
path_map[path(src_ref, dst_ref)], src_cand, dst_cand, true);
#endif
}
std::string
fmt_t(unsigned src, bdd& cond, unsigned dst)
{
return helper.format_t(debug_dict, src, cond, dst);
}
std::string
fmt_t(unsigned src, unsigned cond, unsigned dst)
{
return helper.format_t(debug_dict, src, alpha_vect[cond], dst);
}
std::string
fmt_p(unsigned src_cand, unsigned src_ref, unsigned dst_cand,
unsigned dst_ref)
{
return helper.format_p(src_cand, src_ref, dst_cand, dst_ref);
}
};
void declare_vars(const const_twa_graph_ptr& aut,
dict& d,
bdd ap,
bool state_based,
scc_info& sm)
{
d.ref_size = aut->num_states();
if (d.cand_size == -1U)
for (unsigned i = 0; i < d.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.
// In order to optimize memory usage, src_cand & dst_cand have been
// removed from path struct (the reasons are: they were no optimization
// on them and their values are known from the beginning).
//
// However, since some optimizations are based on the following i and k,
// it is necessary to associate to each path constructed, an ID number.
//
// Given this ID, src_cand, dst_cand and a boolean that tells we want
// ref or cand var, the corresponding litteral can be retrieved thanks
// to get_prc(...), a vars_helper's method.
unsigned path_size = 0;
for (unsigned i = 0; i < d.ref_size; ++i)
{
if (!sm.reachable_state(i))
continue;
unsigned i_scc = sm.scc_of(i);
bool is_trivial = sm.is_trivial(i_scc);
for (unsigned k = 0; k < d.ref_size; ++k)
{
if (!sm.reachable_state(k))
continue;
if ((sm.scc_of(k) != i_scc || is_trivial)
&& !(i == k))
continue;
d.path_map[path(i, k)] = path_size++;
}
}
// Fill dict's bdd vetor (alpha_vect) and save each bdd and it's
// corresponding index in alpha_map. This is necessary beacause
// some loops start from a precise bdd. Therefore, it's useful
// to know its corresponding index to deal with vars_helper.
unsigned j = 0;
for (bdd one: minterms_of(bddtrue, ap))
{
d.alpha_vect.push_back(one);
d.alpha_map[d.alpha_vect[j]] = j;
++j;
}
// Initialize vars_helper by giving it all the necessary information.
// 1: nacc_size is 1 (with Büchi) | true: means dtbasat, i-e, not dtwasat.
d.helper.init(d.cand_size, d.alpha_vect.size(), d.cand_size,
1, path_size, state_based, true);
// Based on all previous informations, helper knows all litterals.
d.helper.declare_all_vars(++d.nvars);
}
typedef std::pair<int, int> sat_stats;
static
sat_stats dtba_to_sat(satsolver& solver,
const const_twa_graph_ptr& ref,
dict& d,
bool state_based)
{
// Compute the AP used.
bdd ap = ref->ap_vars();
// 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, scc_info_options::NONE);
// Number all the SAT variables we may need.
declare_vars(ref, d, ap, state_based, sm);
// Store alpha_vect's size once for all.
unsigned alpha_size = d.alpha_vect.size();
// Tell the satsolver the number of variables.
solver.adjust_nvars(d.nvars);
// Empty automaton is impossible.
assert(d.cand_size > 0);
#if TRACE
debug_dict = ref->get_dict();
solver.comment("d.ref_size", d.ref_size, '\n');
solver.comment("d.cand_size", d.cand_size, '\n');
#endif
cnf_comment("symmetry-breaking clauses\n");
unsigned j = 0;
for (unsigned l = 0; l < alpha_size; ++l, ++j)
for (unsigned i = 0; i < d.cand_size - 1; ++i)
for (unsigned k = i * nap + j + 2; k < d.cand_size; ++k)
{
cnf_comment("¬", d.fmt_t(i, l, k), '\n');
solver.add({-d.transid(i, l, k), 0});
}
if (!solver.get_nb_clauses())
cnf_comment("(none)\n");
cnf_comment("(1) the candidate automaton is complete\n");
for (unsigned q1 = 0; q1 < d.cand_size; ++q1)
for (unsigned l = 0; l < alpha_size; ++l)
{
#if TRACE
solver.comment("");
for (unsigned q2 = 0; q2 < d.cand_size; q2++)
{
solver.comment_rec(d.fmt_t(q1, l, q2), "δ");
if (q2 != d.cand_size)
solver.comment_rec(" ");
}
solver.comment_rec('\n');
#endif
for (unsigned q2 = 0; q2 < d.cand_size; q2++)
solver.add(d.transid(q1, l, q2));
solver.add(0);
}
cnf_comment("(2) the initial state is reachable\n");
{
unsigned init = ref->get_init_state_number();
cnf_comment(d.fmt_p(0, init, 0, init), '\n');
solver.add({d.pathid_ref(0, init, 0, init), 0});
}
for (unsigned q1 = 0; q1 < d.cand_size; ++q1)
{
for (unsigned q1p = 0; q1p < d.ref_size; ++q1p)
{
// Added to comply with the variable declaration, i-e to avoid
// using undeclared variables.
if (!sm.reachable_state(q1p))
continue;
cnf_comment("(3) augmenting paths based on Cand[", q1, "] and Ref[",
q1p, "]\n");
for (auto& tr: ref->out(q1p))
{
unsigned dp = tr.dst;
for (bdd s: minterms_of(tr.cond, ap))
for (unsigned q2 = 0; q2 < d.cand_size; q2++)
{
int prev = d.pathid_ref(q1, q1p, q1, q1p);
int succ = d.pathid_ref(q2, dp, q2, dp);
if (prev == succ)
continue;
cnf_comment(prev, "", d.fmt_t(q1, s, q2), "δ →",
d.fmt_p(q2, dp, q2, dp), '\n');
solver.add({-prev, -d.transid(q1, s, q2), succ, 0});
}
}
}
}
const acc_cond& ra = ref->acc();
// construction of contraints (4,5) : all loops in the product
// where no accepting run is detected in the ref. automaton,
// must also be marked as not accepting in the cand. automaton
for (unsigned q1p = 0; q1p < d.ref_size; ++q1p)
{
if (!sm.reachable_state(q1p))
continue;
unsigned q1p_scc = sm.scc_of(q1p);
if (sm.is_trivial(q1p_scc))
continue;
for (unsigned q2p = 0; q2p < d.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;
for (unsigned q1 = 0; q1 < d.cand_size; ++q1)
for (unsigned q2 = 0; q2 < d.cand_size; ++q2)
{
std::string f_p = d.fmt_p(q1, q1p, q2, q2p);
cnf_comment("(4&5) matching paths from reference based on",
f_p, '\n');
int pid1 = d.pathid_ref(q1, q1p, q2, q2p);
for (auto& tr: ref->out(q2p))
{
unsigned dp = tr.dst;
// Skip destinations not in the SCC.
if (sm.scc_of(dp) != q1p_scc)
continue;
if (ra.accepting(tr.acc))
continue;
for (unsigned q3 = 0; q3 < d.cand_size; ++q3)
{
if (dp == q1p && q3 == q1) // (4) looping
{
for (bdd s: minterms_of(tr.cond, ap))
{
#if TRACE
std::string f_t = d.fmt_t(q2, s, q1);
cnf_comment(f_p, "R ∧", f_t, "δ → ¬", f_t,
"F\n");
#endif
solver.add({-pid1,
-d.transid(q2, s, q1),
-d.transacc(q2, s, q1),
0});
}
}
else // (5) not looping
{
int pid2 = d.pathid_ref(q1, q1p, q3, dp);
if (pid1 == pid2)
continue;
for (bdd s: minterms_of(tr.cond, ap))
{
cnf_comment(f_p, "R ∧", d.fmt_t(q2, s, q3),
"δ →", d.fmt_p(q1, q1p, q3, dp),
"R\n");
solver.add({-pid1,
-d.transid(q2, s, q3),
pid2,
0});
}
}
}
}
}
}
}
// construction of contraints (6,7): all loops in the product
// where accepting run is detected in the ref. automaton, must
// also be marked as accepting in the candidate.
for (unsigned q1p = 0; q1p < d.ref_size; ++q1p)
{
if (!sm.reachable_state(q1p))
continue;
unsigned q1p_scc = sm.scc_of(q1p);
if (sm.is_trivial(q1p_scc))
continue;
for (unsigned q2p = 0; q2p < d.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;
for (unsigned q1 = 0; q1 < d.cand_size; ++q1)
for (unsigned q2 = 0; q2 < d.cand_size; ++q2)
{
std::string f_p = d.fmt_p(q1, q1p, q2, q2p);
cnf_comment("(6&7) matching paths from candidate based on",
f_p, '\n');
int pid1;
if (q1 == q2 && q1p == q2p)
pid1 = d.pathid_ref(q1, q1p, q2, q2p);
else
pid1 = d.pathid_cand(q1, q1p, q2, q2p);
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++)
{
if (dp == q1p && q3 == q1) // (6) looping
{
// We only care about the looping case if
// it is accepting in the reference.
if (!ra.accepting(tr.acc))
continue;
for (bdd s: minterms_of(tr.cond, ap))
{
#if TRACE
std::string f_t = d.fmt_t(q2, s, q1);
cnf_comment(f_p, "C ∧", f_t, "δ →", f_t,
"F\n");
#endif
solver.add({-pid1,
-d.transid(q2, s, q1),
d.transacc(q2, s, q1),
0});
}
}
else // (7) no loop
{
int pid2 = d.pathid_cand(q1, q1p, q3, dp);
if (pid1 == pid2)
continue;
for (bdd s: minterms_of(tr.cond, ap))
{
#if TRACE
std::string f_t = d.fmt_t(q2, s, q3);
cnf_comment(f_p, "C ∧", f_t, "δ ∧ ¬", f_t,
"F →", d.fmt_p(q1, q1p, q3, dp),
"C\n");
#endif
solver.add({-pid1,
-d.transid(q2, s, q3),
d.transacc(q2, s, q3),
pid2,
0});
}
}
}
}
}
}
}
return solver.stats();
}
static twa_graph_ptr
sat_build(const satsolver::solution& solution, dict& satdict,
const_twa_graph_ptr aut, bool state_based)
{
trace << "sat_build(...)\n";
auto autdict = aut->get_dict();
auto a = make_twa_graph(autdict);
a->copy_ap_of(aut);
a->set_buchi();
if (state_based)
a->prop_state_acc(true);
a->prop_universal(true);
a->new_states(satdict.cand_size);
#if TRACE
std::fstream out("dtba-sat.dbg",
std::ios_base::trunc | std::ios_base::out);
out.exceptions(std::ofstream::failbit | std::ofstream::badbit);
#endif
std::set<int> acc_states;
std::set<src_cond> seen_trans;
unsigned alpha_size = satdict.alpha_vect.size();
unsigned cand_size = satdict.cand_size;
for (unsigned i = 0; i < cand_size; ++i)
for (unsigned j = 0; j < alpha_size; ++j)
for (unsigned k = 0; k < cand_size; ++k)
{
if (solution[satdict.transid(i, j, k) - 1])
{
// Ignore unuseful transitions because of reduced cand_size.
if (i >= cand_size)
continue;
// Skip (s,l,d2) if we have already seen some (s,l,d1).
if (seen_trans.insert(src_cond(i, satdict.alpha_vect[j])).second)
{
bool accept = false;
if (state_based)
accept = acc_states.find(i) != acc_states.end();
if (!accept)
accept = solution[satdict.transacc(i, j, k) - 1];
a->new_acc_edge(i, k, satdict.alpha_vect[j], accept);
if (state_based && accept)
acc_states.insert(i);
}
}
}
#if TRACE
dout << "--- transition variables ---\n";
for (unsigned i = 0; i < cand_size; ++i)
for (unsigned j = 0; j < alpha_size; ++j)
for (unsigned k = 0; k < cand_size; ++k)
{
int var = satdict.transid(i, j, k);
std::string f_t = satdict.fmt_t(i, j, k);
if (solution[var - 1])
dout << ' ' << var << "\t " << f_t << '\n';
else
dout << -var << "\t¬" << f_t << '\n';
}
dout << "--- transition_acc variables ---\n";
if (state_based)
{
dout << "In state_based mode with Büchi automaton, there is only 1 "
"litteral for each src, regardless of dst or cond!\n";
for (unsigned i = 0; i < cand_size; ++i)
{
int var = satdict.transacc(i, 0, 0);
std::string f_t = satdict.fmt_t(i, 0, 0);
if (solution[var - 1])
dout << ' ' << var << "\t " << f_t << '\n';
else
dout << -var << "\t¬" << f_t << '\n';
}
}
else
for (unsigned i = 0; i < cand_size; ++i)
for (unsigned j = 0; j < alpha_size; ++j)
for (unsigned k = 0; k < cand_size; ++k)
{
int var = satdict.transacc(i, j, k);
std::string f_t = satdict.fmt_t(i, j, k);
if (solution[var - 1])
dout << ' ' << var << "\t " << f_t << '\n';
else
dout << -var << "\t¬" << f_t << '\n';
}
dout << "--- ref pathid variables ---\n";
std::map<int, std::string> cand_vars;
for (auto it = satdict.path_map.begin(); it != satdict.path_map.end();
++it)
for (unsigned k = 0; k < cand_size; ++k)
for (unsigned l = 0; l < cand_size; ++l)
{
// false:reference | true:cand
int cand_v = satdict.pathid_cand(it->second, k, l);
int ref_v = satdict.pathid_ref(it->second, k, l);
std::string f_p = satdict.fmt_p(k, it->first.src_ref, l,
it->first.dst_ref);
cand_vars[cand_v] = f_p;
if (solution[ref_v - 1])
dout << ' ' << ref_v << "\t " << f_p << '\n';
else
dout << -ref_v << "\t¬" << f_p << '\n';
}
dout << "--- cand pathid variables ---\n";
for (auto it = cand_vars.begin(); it != cand_vars.end(); ++it)
{
if (solution[it->first - 1])
dout << ' ' << it->first << "\t " << it->second << '\n';
else
dout << -it->first << "\t¬" << it->second << '\n';
}
#endif
a->merge_edges();
a->purge_unreachable_states();
return a;
}
}
twa_graph_ptr
dtba_sat_synthetize(const const_twa_graph_ptr& a,
int target_state_number, bool state_based)
{
if (!a->is_existential())
throw std::runtime_error
("dtba_sat_synthetize() does not support alternating automata");
if (!a->acc().is_buchi())
throw std::runtime_error
("dtba_sat_synthetize() can only work with Büchi acceptance");
if (target_state_number == 0)
return nullptr;
trace << "dtba_sat_synthetize(..., states = " << target_state_number
<< ", state_based = " << state_based << ")\n";
dict d;
d.cand_size = target_state_number;
satsolver solver;
satsolver::solution_pair solution;
timer_map t;
t.start("encode");
dtba_to_sat(solver, a, d, state_based);
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);
print_log(t, a->num_states(),
target_state_number, res, solver); // If SPOT_SATLOG is set.
trace << "dtba_sat_synthetize(...) = " << res << '\n';
return res;
}
static twa_graph_ptr
dichotomy_dtba_research(int max,
dict& d,
satsolver& solver,
const_twa_graph_ptr& prev,
bool state_based)
{
trace << "dichotomy_dtba_research(...)\n";
int min = 1;
int target = 0;
twa_graph_ptr res = nullptr;
while (min < max)
{
target = (max + min) / 2;
trace << "min:" << min << ", max:" << max << ", target:" << target
<< '\n';
timer_map t1;
t1.start("encode");
solver.assume(d.nvars + target);
t1.stop("encode");
trace << "solver.assume(" << d.nvars + target << ")\n";
t1.start("solve");
satsolver::solution_pair solution = solver.get_solution();
t1.stop("solve");
if (solution.second.empty())
{
trace << "UNSAT\n";
max = target;
print_log(t1, prev->num_states(), d.cand_size - target,
nullptr, solver);
}
else
{
trace << "SAT\n";
res = sat_build(solution.second, d, prev, state_based);
min = d.cand_size - res->num_states() + 1;
print_log(t1, prev->num_states(), d.cand_size - target,
res, solver);
}
}
trace << "End with max:" << max << ", min:" << min << '\n';
if (!res)
{
trace << "All assumptions are UNSAT, let's try without...\n";
timer_map t1;
t1.start("encode");
t1.stop("encode");
t1.start("solve");
t1.stop("solve");
satsolver::solution_pair solution = solver.get_solution();
trace << (solution.second.empty() ? "UNSAT!\n" : "SAT\n");
res = solution.second.empty() ? nullptr :
sat_build(solution.second, d, prev, state_based);
print_log(t1, prev->num_states(), d.cand_size - target, res, solver);
}
return res ? res : std::const_pointer_cast<spot::twa_graph>(prev);
}
twa_graph_ptr
dtba_sat_minimize_assume(const const_twa_graph_ptr& a,
bool state_based,
int max_states,
int sat_incr_steps)
{
if (!a->acc().is_buchi())
throw std::runtime_error
("dtba_sat_minimize_assume() can only work with Büchi acceptance");
if (sat_incr_steps < 0)
throw std::runtime_error("with 'assume' algorithm, sat_incr_steps value "
" must be >= 0");
const_twa_graph_ptr prev = a;
dict d;
d.cand_size = (max_states < 0) ? prev->num_states() - 1 : max_states;
if (d.cand_size == 0)
return nullptr;
trace << "dtba_sat_minimize_assume(..., states = " << d.cand_size
<< ", state_based = " << state_based << ")\n";
trace << "sat_incr_steps: " << sat_incr_steps << '\n';
twa_graph_ptr next = spot::make_twa_graph(spot::make_bdd_dict());
while (next && d.cand_size > 0)
{
// Warns the satsolver of the number of assumptions.
int n_assumptions = (int) d.cand_size <= sat_incr_steps ?
d.cand_size - 1 : sat_incr_steps;
trace << "number of assumptions:" << n_assumptions << '\n';
satsolver solver;
solver.set_nassumptions_vars(n_assumptions);
// First iteration of classic solving.
timer_map t1;
t1.start("encode");
dtba_to_sat(solver, prev, d, state_based);
// Compute the AP used.
bdd ap = prev->ap_vars();
// Add all assumptions clauses.
unsigned dst = d.cand_size - 1;
unsigned alpha_size = d.alpha_vect.size();
for (int i = 1; i <= n_assumptions; i++, dst--)
{
cnf_comment("Next iteration:", dst, "\n");
int assume_lit = d.nvars + i;
cnf_comment("Add clauses to forbid the dst state.\n");
for (unsigned l = 0; l < alpha_size; ++l)
for (unsigned j = 0; j < d.cand_size; ++j)
{
cnf_comment(assume_lit, "→ ¬", d.fmt_t(j, l, dst), '\n');
solver.add({-assume_lit, -d.transid(j, l, dst), 0});
}
// The assumption which has just been encoded implies the preceding
// ones.
if (i != 1)
{
cnf_comment(assume_lit, "", assume_lit - 1, '\n');
solver.add({-assume_lit, assume_lit - 1, 0});
}
}
if (n_assumptions)
{
trace << "solver.assume(" << d.nvars + n_assumptions << ")\n";
solver.assume(d.nvars + n_assumptions);
}
t1.stop("encode");
t1.start("solve");
satsolver::solution_pair solution = solver.get_solution();
t1.stop("solve");
if (solution.second.empty() && n_assumptions) // UNSAT
{
print_log(t1, prev->num_states(),
d.cand_size - n_assumptions, nullptr, solver);
trace << "UNSAT\n";
return dichotomy_dtba_research(n_assumptions, d, solver,
prev, state_based);
}
trace << "SAT, restarting from zero\n";
next = solution.second.empty() ? nullptr :
sat_build(solution.second, d, prev, state_based);
print_log(t1, prev->num_states(),
d.cand_size - n_assumptions, next, solver);
if (next)
{
prev = next;
d = dict();
d.cand_size = prev->num_states() - 1;
if (d.cand_size == 0)
next = nullptr;
}
}
return prev == a ? nullptr : std::const_pointer_cast<spot::twa_graph>(prev);
}
twa_graph_ptr
dtba_sat_minimize_incr(const const_twa_graph_ptr& a,
bool state_based, int max_states, int sat_incr_steps)
{
if (!a->acc().is_buchi())
throw std::runtime_error
(": dtba_sat_minimize_incr() can only work with Büchi acceptance.");
const_twa_graph_ptr prev = a;
dict d;
d.cand_size = (max_states < 0) ? prev->num_states() - 1 : max_states;
if (d.cand_size == 0)
return nullptr;
trace << "dtba_sat_minimize_incr(..., states = " << d.cand_size
<< ", state_based = " << state_based << ")\n";
bool naive = sat_incr_steps < 0;
trace << "sat_incr_steps: " << sat_incr_steps << '\n';
twa_graph_ptr next = spot::make_twa_graph(spot::make_bdd_dict());
while (next && d.cand_size > 0)
{
// First iteration of classic solving.
satsolver solver;
timer_map t1;
t1.start("encode");
dtba_to_sat(solver, prev, d, state_based);
t1.stop("encode");
t1.start("solve");
satsolver::solution_pair solution = solver.get_solution();
t1.stop("solve");
next = solution.second.empty() ? nullptr :
sat_build(solution.second, d, prev, state_based);
print_log(t1, prev->num_states(), d.cand_size, next, solver);
trace << "First iteration done\n";
// Compute the AP used.
bdd ap = prev->ap_vars();
// Incremental solving loop.
unsigned orig_cand_size = d.cand_size;
unsigned alpha_size = d.alpha_vect.size();
for (int k = 0; next && d.cand_size > 0 && (naive || k < sat_incr_steps);
++k)
{
prev = next;
int reach_states = prev->num_states();
cnf_comment("Next iteration: ", reach_states - 1, "\n");
trace << "Encoding the deletion of state " << reach_states - 1 << '\n';
timer_map t2;
t2.start("encode");
// Add new constraints.
for (unsigned i = reach_states - 1; i < d.cand_size; ++i)
for (unsigned l = 0; l < alpha_size; ++l)
for (unsigned j = 0; j < orig_cand_size; ++j)
solver.add({-d.transid(j, l, i), 0});
t2.stop("encode");
d.cand_size = reach_states - 1;
t2.start("solve");
satsolver::solution_pair solution = solver.get_solution();
t2.stop("solve");
next = solution.second.empty() ? nullptr :
sat_build(solution.second, d, prev, state_based);
print_log(t2, prev->num_states(),
d.cand_size, next, solver);
}
if (next)
{
trace << "Starting from scratch\n";
prev = next;
d = dict();
d.cand_size = prev->num_states() - 1;
if (d.cand_size == 0)
next = nullptr;
}
}
return prev == a ? nullptr : std::const_pointer_cast<spot::twa_graph>(prev);
}
twa_graph_ptr
dtba_sat_minimize(const const_twa_graph_ptr& a,
bool state_based, int max_states)
{
int n_states = (max_states < 0) ? a->num_states() : max_states + 1;
twa_graph_ptr prev = nullptr;
for (;;)
{
auto next =
dtba_sat_synthetize(prev ? prev : a, --n_states, state_based);
if (!next)
return prev;
else
n_states = next->num_states();
prev = next;
}
SPOT_UNREACHABLE();
}
twa_graph_ptr
dtba_sat_minimize_dichotomy(const const_twa_graph_ptr& a,
bool state_based, bool langmap, int max_states)
{
trace << "Dichomoty\n";
if (max_states < 0)
max_states = a->num_states() - 1;
int min_states = 1;
if (langmap)
{
trace << "Langmap\n";
std::vector<unsigned> v = language_map(a);
min_states = get_number_of_distinct_vals(v);
}
trace << "min_states=" << min_states << '\n';
twa_graph_ptr prev = nullptr;
while (min_states <= max_states)
{
int target = (max_states + min_states) / 2;
auto next = dtba_sat_synthetize(prev ? prev : a, target, state_based);
if (!next)
{
min_states = target + 1;
}
else
{
prev = next;
max_states = next->num_states() - 1;
}
}
return prev;
}
}