spot: Add Picosat to Spot library & Update satsolver class
* Makefile.am: Add picosat to subdirs. * configure.ac: Add picosat/Makefile to AC_CONFIG_FILES. * README: Add picosat/ in the list of directories. * debian/copyright: Add picosat licence and details. * picosat/Makefile.am: Implement Makefile.am in picosat directory. * spot/Makefile.am: Tell the compiler to add libpico.la into libspot.la. * picosat/LICENSE: Add picosat licence. * picosat/NEWS: Add picosat NEWS. * picosat/VERSION: Add picosat VERSION. * picosat/picosat.c: Add picosat c file. * picosat/picosat.h: Add picosat header file. * spot/misc/satsolver.cc: Update functions. * spot/misc/satsolver.hh: Add documentation, clean code, change some functions visibility and separate templates functions. * spot/twaalgos/dtbasat.cc: Update dtba_to_sat function. * spot/twaalgos/dtwasat.cc: Update dtwa_to_sat function.
This commit is contained in:
parent
596bdec910
commit
32f040fa45
15 changed files with 9678 additions and 164 deletions
|
|
@ -42,7 +42,8 @@ libspot_la_LIBADD = \
|
|||
tl/libtl.la \
|
||||
twaalgos/libtwaalgos.la \
|
||||
twa/libtwa.la \
|
||||
../lib/libgnu.la
|
||||
../lib/libgnu.la \
|
||||
../picosat/libpico.la
|
||||
|
||||
# Dummy C++ source to cause C++ linking.
|
||||
nodist_EXTRA_libspot_la_SOURCES = _.cc
|
||||
|
|
|
|||
|
|
@ -23,66 +23,13 @@
|
|||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
#include <spot/misc/satsolver.hh>
|
||||
#include <picosat/picosat.h>
|
||||
#include <fstream>
|
||||
#include <limits>
|
||||
#include <sys/wait.h>
|
||||
|
||||
namespace spot
|
||||
{
|
||||
namespace
|
||||
{
|
||||
struct satsolver_command: formater
|
||||
{
|
||||
const char* satsolver;
|
||||
|
||||
satsolver_command()
|
||||
{
|
||||
satsolver = getenv("SPOT_SATSOLVER");
|
||||
if (!satsolver)
|
||||
{
|
||||
satsolver = "glucose -verb=0 -model %I >%O";
|
||||
return;
|
||||
}
|
||||
prime(satsolver);
|
||||
if (!has('I'))
|
||||
throw std::runtime_error("SPOT_SATSOLVER should contain %I to "
|
||||
"indicate how to use the input filename.");
|
||||
if (!has('O'))
|
||||
throw std::runtime_error("SPOT_SATSOLVER should contain %O to "
|
||||
"indicate how to use the output filename.");
|
||||
}
|
||||
|
||||
int
|
||||
run(printable* in, printable* out)
|
||||
{
|
||||
declare('I', in);
|
||||
declare('O', out);
|
||||
std::ostringstream s;
|
||||
format(s, satsolver);
|
||||
int res = system(s.str().c_str());
|
||||
if (res < 0 || (WIFEXITED(res) && WEXITSTATUS(res) == 127))
|
||||
{
|
||||
s << ": failed to execute";
|
||||
throw std::runtime_error(s.str());
|
||||
}
|
||||
// For POSIX shells, "The exit status of a command that
|
||||
// terminated because it received a signal shall be reported
|
||||
// as greater than 128."
|
||||
if (WIFEXITED(res) && WEXITSTATUS(res) >= 128)
|
||||
{
|
||||
s << ": terminated by signal";
|
||||
throw std::runtime_error(s.str());
|
||||
}
|
||||
if (WIFSIGNALED(res))
|
||||
{
|
||||
s << ": terminated by signal " << WTERMSIG(res);
|
||||
throw std::runtime_error(s.str());
|
||||
}
|
||||
return res;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
satsolver::solution
|
||||
satsolver_get_solution(const char* filename)
|
||||
{
|
||||
|
|
@ -122,17 +69,35 @@ namespace spot
|
|||
return sol;
|
||||
}
|
||||
|
||||
// In other functions, command_given() won't be called anymore as it is more
|
||||
// easy to check if psat_ was initialized or not.
|
||||
satsolver::satsolver()
|
||||
: cnf_tmp_(nullptr), cnf_stream_(nullptr)
|
||||
: cnf_tmp_(nullptr), cnf_stream_(nullptr), nclauses_(0), nvars_(0),
|
||||
psat_(nullptr)
|
||||
{
|
||||
start();
|
||||
if (cmd_.command_given())
|
||||
{
|
||||
start();
|
||||
}
|
||||
else
|
||||
{
|
||||
psat_ = picosat_init();
|
||||
picosat_set_seed(psat_, 0);
|
||||
}
|
||||
}
|
||||
|
||||
satsolver::~satsolver()
|
||||
{
|
||||
delete cnf_tmp_;
|
||||
delete cnf_stream_;
|
||||
delete nclauses_;
|
||||
if (psat_)
|
||||
{
|
||||
picosat_reset(psat_);
|
||||
psat_ = nullptr;
|
||||
}
|
||||
else
|
||||
{
|
||||
delete cnf_tmp_;
|
||||
delete cnf_stream_;
|
||||
}
|
||||
}
|
||||
|
||||
void satsolver::start()
|
||||
|
|
@ -140,70 +105,192 @@ namespace spot
|
|||
cnf_tmp_ = create_tmpfile("sat-", ".cnf");
|
||||
cnf_stream_ = new std::ofstream(cnf_tmp_->name(), std::ios_base::trunc);
|
||||
cnf_stream_->exceptions(std::ofstream::failbit | std::ofstream::badbit);
|
||||
nclauses_ = new clause_counter();
|
||||
|
||||
// Add empty line for the header
|
||||
*cnf_stream_ << " \n";
|
||||
}
|
||||
|
||||
// Must be called only when SPOT_SATSOLVER is given
|
||||
void satsolver::end_clause()
|
||||
{
|
||||
*cnf_stream_ << '\n';
|
||||
*nclauses_ += 1;
|
||||
nclauses_ += 1;
|
||||
if (nclauses_ < 0)
|
||||
throw std::runtime_error("too many SAT clauses (more than INT_MAX)");
|
||||
}
|
||||
|
||||
void satsolver::adjust_nvars(int nvars)
|
||||
{
|
||||
if (nvars < 0)
|
||||
throw std::runtime_error("variable number must be at least 0");
|
||||
|
||||
if (psat_)
|
||||
{
|
||||
picosat_adjust(psat_, nvars);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (nvars < nvars_)
|
||||
{
|
||||
throw std::runtime_error(
|
||||
"wrong number of variables, a bigger one was already added");
|
||||
}
|
||||
nvars_ = nvars;
|
||||
}
|
||||
}
|
||||
|
||||
void satsolver::add(std::initializer_list<int> values)
|
||||
{
|
||||
for (auto& v : values)
|
||||
{
|
||||
*cnf_stream_ << v << ' ';
|
||||
if (!v) // ..., 0)
|
||||
end_clause();
|
||||
if (psat_)
|
||||
{
|
||||
picosat_add(psat_, v);
|
||||
}
|
||||
else
|
||||
{
|
||||
*cnf_stream_ << v << ' ';
|
||||
if (!v) // ..., 0)
|
||||
end_clause();
|
||||
|
||||
if (nvars_ < v)
|
||||
nvars_ = v;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void satsolver::add(int v)
|
||||
{
|
||||
*cnf_stream_ << v << ' ';
|
||||
if (!v) // 0
|
||||
end_clause();
|
||||
if (psat_)
|
||||
{
|
||||
picosat_add(psat_, v);
|
||||
}
|
||||
else
|
||||
{
|
||||
*cnf_stream_ << v << ' ';
|
||||
if (!v) // 0
|
||||
end_clause();
|
||||
|
||||
if (v && nvars_ < v)
|
||||
nvars_ = v;
|
||||
}
|
||||
}
|
||||
|
||||
int satsolver::get_nb_clauses() const
|
||||
{
|
||||
return nclauses_->nb_clauses();
|
||||
if (psat_)
|
||||
return picosat_added_original_clauses(psat_);
|
||||
return nclauses_;
|
||||
}
|
||||
|
||||
std::pair<int, int> satsolver::stats(int nvars)
|
||||
int satsolver::get_nb_vars() const
|
||||
{
|
||||
int nclaus = nclauses_->nb_clauses();
|
||||
cnf_stream_->seekp(0);
|
||||
*cnf_stream_ << "p cnf " << nvars << ' ' << nclaus;
|
||||
return std::make_pair(nvars, nclaus);
|
||||
if (psat_)
|
||||
return picosat_variables(psat_);
|
||||
return nvars_;
|
||||
}
|
||||
|
||||
std::pair<int, int> satsolver::stats()
|
||||
{
|
||||
*cnf_stream_ << "p cnf 1 2\n-1 0\n1 0\n";
|
||||
return std::make_pair(1, 2);
|
||||
return std::make_pair(get_nb_vars(), get_nb_clauses());
|
||||
}
|
||||
|
||||
satsolver::solution
|
||||
satsolver::picosat_get_solution(int res)
|
||||
{
|
||||
satsolver::solution sol;
|
||||
if (res == PICOSAT_SATISFIABLE)
|
||||
{
|
||||
int nvars = get_nb_vars();
|
||||
for (int lit = 1; lit <= nvars; ++lit)
|
||||
{
|
||||
if (picosat_deref(psat_, lit) > 0)
|
||||
sol.push_back(lit);
|
||||
else
|
||||
sol.push_back(-lit);
|
||||
}
|
||||
}
|
||||
return sol;
|
||||
}
|
||||
|
||||
satsolver::solution_pair
|
||||
satsolver::get_solution()
|
||||
{
|
||||
delete cnf_stream_; // Close the file.
|
||||
cnf_stream_ = nullptr;
|
||||
|
||||
temporary_file* output = create_tmpfile("sat-", ".out");
|
||||
solution_pair p;
|
||||
if (psat_)
|
||||
{
|
||||
p.first = 0; // A subprocess was not executed so nothing failed.
|
||||
int res = picosat_sat(psat_, -1); // -1: no limit (number of decisions).
|
||||
p.second = picosat_get_solution(res);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Update header
|
||||
cnf_stream_->seekp(0);
|
||||
*cnf_stream_ << "p cnf " << get_nb_vars() << ' ' << get_nb_clauses();
|
||||
cnf_stream_->seekp(0, std::ios_base::end);
|
||||
if (!*cnf_stream_)
|
||||
throw std::runtime_error("Failed to update cnf header");
|
||||
|
||||
// Make this static, so the SPOT_SATSOLVER lookup is done only on
|
||||
// the first call to run_sat().
|
||||
static satsolver_command cmd;
|
||||
|
||||
p.first = cmd.run(cnf_tmp_, output);
|
||||
p.second = satsolver_get_solution(output->name());
|
||||
delete output;
|
||||
temporary_file* output = create_tmpfile("sat-", ".out");
|
||||
p.first = cmd_.run(cnf_tmp_, output);
|
||||
p.second = satsolver_get_solution(output->name());
|
||||
delete output;
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
satsolver_command::satsolver_command() : satsolver(nullptr)
|
||||
{
|
||||
satsolver = getenv("SPOT_SATSOLVER");
|
||||
if (!satsolver)
|
||||
return;
|
||||
|
||||
prime(satsolver);
|
||||
if (!has('I'))
|
||||
throw std::runtime_error("SPOT_SATSOLVER should contain %I to "
|
||||
"indicate how to use the input filename.");
|
||||
if (!has('O'))
|
||||
throw std::runtime_error("SPOT_SATSOLVER should contain %O to "
|
||||
"indicate how to use the output filename.");
|
||||
}
|
||||
|
||||
bool
|
||||
satsolver_command::command_given()
|
||||
{
|
||||
return satsolver != nullptr;
|
||||
}
|
||||
|
||||
int
|
||||
satsolver_command::run(printable* in, printable* out)
|
||||
{
|
||||
declare('I', in);
|
||||
declare('O', out);
|
||||
std::ostringstream s;
|
||||
format(s, satsolver);
|
||||
|
||||
int res = system(s.str().c_str());
|
||||
if (res < 0 || (WIFEXITED(res) && WEXITSTATUS(res) == 127))
|
||||
{
|
||||
s << ": failed to execute";
|
||||
throw std::runtime_error(s.str());
|
||||
}
|
||||
|
||||
// For POSIX shells, "The exit status of a command that
|
||||
// terminated because it received a signal shall be reported
|
||||
// as greater than 128."
|
||||
if (WIFEXITED(res) && WEXITSTATUS(res) >= 128)
|
||||
{
|
||||
s << ": terminated by signal";
|
||||
throw std::runtime_error(s.str());
|
||||
}
|
||||
|
||||
if (WIFSIGNALED(res))
|
||||
{
|
||||
s << ": terminated by signal " << WTERMSIG(res);
|
||||
throw std::runtime_error(s.str());
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,68 +26,58 @@
|
|||
#include <iosfwd>
|
||||
#include <initializer_list>
|
||||
|
||||
struct PicoSAT; // forward
|
||||
|
||||
namespace spot
|
||||
{
|
||||
class printable;
|
||||
|
||||
class clause_counter
|
||||
/// \brief Interface with a given sat solver.
|
||||
///
|
||||
/// When created, it checks if SPOT_SATSOLVER env var is set. If so,
|
||||
/// its value is parsed and saved internally. The env variable musb be set
|
||||
/// like this: "<satsolver> [its_options] %I > %O"
|
||||
/// where %I and %O are replaced by input and output files.
|
||||
///
|
||||
/// The run method permits of course to run the given sat solver.
|
||||
class satsolver_command: formater
|
||||
{
|
||||
private:
|
||||
int count_;
|
||||
const char* satsolver;
|
||||
|
||||
public:
|
||||
clause_counter()
|
||||
: count_(0)
|
||||
{
|
||||
}
|
||||
satsolver_command();
|
||||
|
||||
void check() const
|
||||
{
|
||||
if (count_ < 0)
|
||||
throw std::runtime_error("too many SAT clauses (more than INT_MAX)");
|
||||
}
|
||||
/// \brief Return true if a satsolver is given, false otherwise.
|
||||
bool command_given();
|
||||
|
||||
clause_counter& operator++()
|
||||
{
|
||||
++count_;
|
||||
check();
|
||||
return *this;
|
||||
}
|
||||
/// \brief Run the given satsolver.
|
||||
int run(printable* in, printable* out);
|
||||
|
||||
clause_counter& operator+=(int n)
|
||||
{
|
||||
count_ += n;
|
||||
check();
|
||||
return *this;
|
||||
}
|
||||
|
||||
int nb_clauses() const
|
||||
{
|
||||
return count_;
|
||||
}
|
||||
};
|
||||
|
||||
/// \brief Interface with a SAT solver.
|
||||
///
|
||||
/// Call start() to initialize the cnf file. This class provides the
|
||||
/// necessary functions to handle the cnf file, add clauses, count them,
|
||||
/// update the header, add some comments...
|
||||
/// It is not possible to write in the file without having to call these
|
||||
/// functions.
|
||||
///
|
||||
/// The satsolver called can be configured via the
|
||||
/// <code>SPOT_SATSOLVER</code> environment variable. It must be this set
|
||||
/// following this: "satsolver -verb=0 %I >%O".
|
||||
/// This class provides the necessary functions to add clauses, comments.
|
||||
/// Depending on SPOT_SATSOLVER, it will use either picosat solver (default)
|
||||
/// or the given satsolver.
|
||||
///
|
||||
/// Now that spot is distributed with a satsolver (PicoSAT), it is used by
|
||||
/// default. But another satsolver can be configured via the
|
||||
/// <code>SPOT_SATSOLVER</code> environment variable. It must be set following
|
||||
/// this: "satsolver [options] %I > %O"
|
||||
/// where %I and %O are replaced by input and output files.
|
||||
class SPOT_API satsolver
|
||||
{
|
||||
public:
|
||||
/// \brief Construct the sat solver and itinialize variables.
|
||||
/// If no satsolver is provided through SPOT_SATSOLVER env var, a
|
||||
/// distributed version of PicoSAT will be used.
|
||||
satsolver();
|
||||
~satsolver();
|
||||
|
||||
/// \brief Initialize private attributes
|
||||
void start();
|
||||
/// \brief Adjust the number of variables used in the cnf formula.
|
||||
void adjust_nvars(int nvars);
|
||||
|
||||
/// \brief Add a list of lit. to the current clause.
|
||||
void add(std::initializer_list<int> values);
|
||||
|
|
@ -98,58 +88,109 @@ namespace spot
|
|||
/// \breif Get the current number of clauses.
|
||||
int get_nb_clauses() const;
|
||||
|
||||
/// \breif Update cnf_file's header with the correct stats.
|
||||
std::pair<int, int> stats(int nvars);
|
||||
/// \brief Get the current number of variables.
|
||||
int get_nb_vars() const;
|
||||
|
||||
/// \breif Create an unsatisfiable cnf_file, return stats about it.
|
||||
/// \brief Returns std::pair<nvars, nclauses>;
|
||||
std::pair<int, int> stats();
|
||||
|
||||
/// \breif Add a comment in cnf file.
|
||||
/// \brief Add a comment.
|
||||
/// It should be used only in debug mode after providing a satsolver.
|
||||
template<typename T>
|
||||
void comment_rec(T single)
|
||||
{
|
||||
*cnf_stream_ << single << ' ';
|
||||
}
|
||||
void comment_rec(T single);
|
||||
|
||||
/// \breif Add a comment in cnf_file.
|
||||
/// \brief Add comments.
|
||||
/// It should be used only in debug mode after providing a satsolver.
|
||||
template<typename T, typename... Args>
|
||||
void comment_rec(T first, Args... args)
|
||||
{
|
||||
*cnf_stream_ << first << ' ';
|
||||
comment_rec(args...);
|
||||
}
|
||||
void comment_rec(T first, Args... args);
|
||||
|
||||
/// \breif Add a comment in the cnf_file, starting with 'c'.
|
||||
/// \brief Add a comment. It will start with "c ".
|
||||
/// It should be used only in debug mode after providing a satsolver.
|
||||
template<typename T>
|
||||
void comment(T single)
|
||||
{
|
||||
*cnf_stream_ << "c " << single << ' ';
|
||||
}
|
||||
void comment(T single);
|
||||
|
||||
/// \breif Add comment in the cnf_file, starting with 'c'.
|
||||
/// \brief Add comments. It will start with "c ".
|
||||
/// It should be used only in debug mode after providing a satsolver.
|
||||
template<typename T, typename... Args>
|
||||
void comment(T first, Args... args)
|
||||
{
|
||||
*cnf_stream_ << "c " << first << ' ';
|
||||
comment_rec(args...);
|
||||
}
|
||||
void comment(T first, Args... args);
|
||||
|
||||
typedef std::vector<int> solution;
|
||||
typedef std::pair<int, solution> solution_pair;
|
||||
|
||||
/// \brief Return std::vector<solving_return_code, solution>.
|
||||
solution_pair get_solution();
|
||||
|
||||
private:
|
||||
/// \breif End the current clause and increment the counter.
|
||||
private: // methods
|
||||
/// \brief Initialize cnf streams attributes.
|
||||
void start();
|
||||
|
||||
/// \brief End the current clause and increment the counter.
|
||||
void end_clause();
|
||||
|
||||
private:
|
||||
/// \brief Extract the solution of Picosat output.
|
||||
/// Must be called only if SPOT_SATSOLVER env variable is not set.
|
||||
satsolver::solution
|
||||
picosat_get_solution(int res);
|
||||
|
||||
private: // variables
|
||||
/// \brief A satsolver_command. Check if SPOT_SATSOLVER is given.
|
||||
satsolver_command cmd_;
|
||||
|
||||
// cnf streams and associated clause counter.
|
||||
// The next 2 pointers will be != nullptr if SPOT_SATSOLVER is given.
|
||||
temporary_file* cnf_tmp_;
|
||||
std::ostream* cnf_stream_;
|
||||
clause_counter* nclauses_;
|
||||
int nclauses_;
|
||||
int nvars_;
|
||||
|
||||
/// \brief Picosat satsolver instance.
|
||||
PicoSAT* psat_;
|
||||
};
|
||||
|
||||
/// \brief Extract the solution of a SAT solver output.
|
||||
SPOT_API satsolver::solution
|
||||
satsolver_get_solution(const char* filename);
|
||||
|
||||
}
|
||||
|
||||
namespace spot
|
||||
{
|
||||
template<typename T>
|
||||
void
|
||||
satsolver::comment_rec(T single)
|
||||
{
|
||||
if (!psat_)
|
||||
*cnf_stream_ << ' ' << single;
|
||||
}
|
||||
|
||||
template<typename T, typename... Args>
|
||||
void
|
||||
satsolver::comment_rec(T first, Args... args)
|
||||
{
|
||||
if (!psat_)
|
||||
{
|
||||
*cnf_stream_ << ' ' << first;
|
||||
comment_rec(args...);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void
|
||||
satsolver::comment(T single)
|
||||
{
|
||||
if (!psat_)
|
||||
*cnf_stream_ << "c " << single;
|
||||
}
|
||||
|
||||
template<typename T, typename... Args>
|
||||
void
|
||||
satsolver::comment(T first, Args... args)
|
||||
{
|
||||
if (!psat_)
|
||||
{
|
||||
*cnf_stream_ << "c " << first;
|
||||
comment_rec(args...);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -296,7 +296,7 @@ namespace spot
|
|||
typedef std::pair<int, int> sat_stats;
|
||||
|
||||
static
|
||||
sat_stats dtba_to_sat(satsolver solver,
|
||||
sat_stats dtba_to_sat(satsolver& solver,
|
||||
const const_twa_graph_ptr& ref,
|
||||
dict& d, bool state_based)
|
||||
{
|
||||
|
|
@ -322,9 +322,11 @@ namespace spot
|
|||
// Number all the SAT variables we may need.
|
||||
unsigned ref_size = declare_vars(ref, d, ap, state_based, sm);
|
||||
|
||||
// Tell the satsolver the number of variables
|
||||
solver.adjust_nvars(d.nvars);
|
||||
|
||||
// empty automaton is impossible
|
||||
if (d.cand_size == 0)
|
||||
return solver.stats();
|
||||
assert(d.cand_size > 0);
|
||||
|
||||
#if DEBUG
|
||||
debug_dict = ref->get_dict();
|
||||
|
|
@ -602,7 +604,7 @@ namespace spot
|
|||
}
|
||||
}
|
||||
}
|
||||
return solver.stats(d.nvars);
|
||||
return solver.stats();
|
||||
}
|
||||
|
||||
static twa_graph_ptr
|
||||
|
|
|
|||
|
|
@ -598,7 +598,7 @@ namespace spot
|
|||
typedef std::pair<int, int> sat_stats;
|
||||
|
||||
static
|
||||
sat_stats dtwa_to_sat(satsolver solver, const_twa_graph_ptr ref,
|
||||
sat_stats dtwa_to_sat(satsolver& solver, const_twa_graph_ptr ref,
|
||||
dict& d, bool state_based, bool colored)
|
||||
{
|
||||
#if DEBUG
|
||||
|
|
@ -628,9 +628,11 @@ namespace spot
|
|||
// Number all the SAT variables we may need.
|
||||
unsigned ref_size = declare_vars(ref, d, ap, state_based, sm);
|
||||
|
||||
// Tell the satsolver the number of variables
|
||||
solver.adjust_nvars(d.nvars);
|
||||
|
||||
// empty automaton is impossible
|
||||
if (d.cand_size == 0)
|
||||
return solver.stats();
|
||||
assert(d.cand_size > 0);
|
||||
|
||||
#if DEBUG
|
||||
debug_ref_acc = &ref->acc();
|
||||
|
|
@ -995,7 +997,7 @@ namespace spot
|
|||
}
|
||||
}
|
||||
}
|
||||
return solver.stats(d.nvars);
|
||||
return solver.stats();
|
||||
}
|
||||
|
||||
static twa_graph_ptr
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue