spot/spot/misc/satsolver.cc
Alexandre Duret-Lutz c766f58d5d sat_minimize: improve logs and document Python bindings
* spot/priv/satcommon.cc, spot/priv/satcommon.hh: Make it possible to
set the log file without setting the environment variable.  Adjust
print_log to take the input state and print it as a new column.
* spot/twaalgos/dtbasat.cc, spot/twaalgos/dtwasat.cc: Adjust all
calls to print_log.  Fix log output for incremental approaches.
Prefer purge_unreachable_states() over stats_reachable().  Do
not call scc_filter() on colored automata.
* spot/twaalgos/dtwasat.hh: Document the new "log" option.
* NEWS: Mention the changes.
* tests/python/satmin.ipynb: New file.
* tests/Makefile.am: Add it.
* doc/org/satmin.org, doc/org/tut.org: Link to it.
* doc/org/satmin.org, bin/man/spot-x.x: Adjust description
of CSV files.
* bench/dtgbasat/gen.py, bench/dtgbasat/tabl.pl,
bench/dtgbasat/tabl1.pl, bench/dtgbasat/tabl2.pl,
bench/dtgbasat/tabl3.pl, bench/dtgbasat/tabl4.pl: Adjust for
the new column.
* spot/misc/satsolver.cc, spot/misc/satsolver.hh (stats): Make it
const.
* python/spot/__init__.py (sat_minimize): Add display_log and
return_log options.
* tests/python/ipnbdoctest.py: Adjust to not compare SAT-minimization
logs as they contain timings.
2018-03-30 18:01:59 +02:00

388 lines
9.6 KiB
C++

// -*- coding: utf-8 -*-
// Copyright (C) 2013-2018 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 <http://www.gnu.org/licenses/>.
#include "config.h"
#include <spot/misc/formater.hh>
#include <cstdlib>
#include <sstream>
#include <stdexcept>
#include <spot/misc/satsolver.hh>
#include <picosat/picosat.h>
#include <fstream>
#include <limits>
#include <cassert>
#include <sys/wait.h>
namespace spot
{
// 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), nclauses_(0), nvars_(0),
nassumptions_vars_(0), nsols_(0), psat_(nullptr), xcnf_tmp_(nullptr),
xcnf_stream_(nullptr), path_("")
{
// Check SPOT_XCNF env var.
static std::string path = []()
{
auto s = getenv("SPOT_XCNF");
return s ? s : "";
}();
if (!path.empty())
path_ = path;
bool satsolver_set = cmd_.command_given();
bool xcnf_set = xcnf_mode();
if (satsolver_set)
{
start();
}
else if (xcnf_set) // If !satsolver_set and xcnf_set.
{
throw std::runtime_error("XNCF generation requires an external SAT solver"
" to be defined with SPOT_SATSOLVER");
}
else
{
psat_ = picosat_init();
picosat_set_seed(psat_, 0);
}
}
satsolver::~satsolver()
{
if (psat_)
{
picosat_reset(psat_);
psat_ = nullptr;
}
else
{
delete cnf_tmp_;
delete cnf_stream_;
}
if (xcnf_mode())
{
xcnf_tmp_->close();
xcnf_stream_->close();
delete xcnf_tmp_;
delete xcnf_stream_;
}
}
bool
satsolver::xcnf_mode()
{
static bool res = !path_.empty();
return res;
}
void satsolver::start()
{
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);
// Add empty line for the header
*cnf_stream_ << " \n";
if (xcnf_mode()) // If xcnf mode.
{
xcnf_tmp_ = new std::ofstream(path_ + "/incr.tmp", std::ios_base::trunc);
xcnf_tmp_->exceptions(std::ofstream::failbit | std::ofstream::badbit);
*xcnf_tmp_ << " \n+\n";
xcnf_stream_ = new std::ofstream(path_ + "/incr.xcnf", std::ios::trunc);
xcnf_stream_->exceptions(std::ofstream::failbit | std::ofstream::badbit);
}
}
// Must be called only when SPOT_SATSOLVER is given
void satsolver::end_clause()
{
*cnf_stream_ << '\n';
nclauses_ += 1;
if (nclauses_ < 0)
throw std::runtime_error(": too many SAT clauses (more than INT_MAX).");
if (xcnf_mode())
*xcnf_tmp_ << '\n';
}
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 + nassumptions_vars_);
}
else
{
if (nvars + nassumptions_vars_ < nvars_)
{
throw std::runtime_error(
": wrong number of variables, a bigger one was already added.");
}
nvars_ = nvars + nassumptions_vars_;
}
nsols_ = nvars;
}
void satsolver::set_nassumptions_vars(int nassumptions_vars)
{
nassumptions_vars_ = nassumptions_vars;
}
void satsolver::add(std::initializer_list<int> values)
{
for (auto& v : values)
{
if (psat_)
{
picosat_add(psat_, v);
}
else
{
if (xcnf_mode())
*xcnf_tmp_ << v << ' ';
*cnf_stream_ << v << ' ';
if (!v) // ..., 0)
end_clause();
if (nvars_ < v)
nvars_ = v;
}
}
}
void satsolver::add(int v)
{
if (psat_)
{
picosat_add(psat_, v);
}
else
{
if (xcnf_mode())
*xcnf_tmp_ << v << ' ';
*cnf_stream_ << v << ' ';
if (!v) // 0
end_clause();
if (v && nvars_ < v)
nvars_ = v;
}
}
int satsolver::get_nb_clauses() const
{
if (psat_)
return picosat_added_original_clauses(psat_);
return nclauses_;
}
int satsolver::get_nb_vars() const
{
if (psat_)
return picosat_variables(psat_);
return nvars_;
}
std::pair<int, int> satsolver::stats() const
{
return std::make_pair(get_nb_vars(), get_nb_clauses());
}
void satsolver::assume(int lit)
{
if (psat_)
picosat_assume(psat_, lit);
else
throw std::runtime_error(
"satsolver::assume(...) can not be used with an external satsolver");
}
satsolver::solution
spot::satsolver::satsolver_get_sol(const char* filename)
{
satsolver::solution sol;
bool empty = true;
std::istream* in;
in = new std::ifstream(filename);
int c;
while ((c = in->get()) != EOF)
{
// If a line does not start with 'v ', ignore it.
if (c != 'v' || in->get() != ' ')
{
in->ignore(std::numeric_limits<std::streamsize>::max(), '\n');
continue;
}
// Otherwise, read integers one by one.
int i;
while (*in >> i)
{
if (i == 0)
goto done;
if (i > 0 && empty)
{
empty = false;
sol = satsolver::solution(get_nb_vars(), false);
}
if (i > 0 && !empty)
sol[i - 1] = true;
}
if (!in->eof())
// If we haven't reached end-of-file, then we just attempted
// to extract something that wasn't an integer. Clear the
// fail bit so that will loop over.
in->clear();
}
done:
delete in;
if (empty)
{
sol.clear();
assert(sol.empty());
}
return sol;
}
satsolver::solution
satsolver::picosat_get_sol(int res)
{
satsolver::solution sol;
if (res == PICOSAT_SATISFIABLE)
for (int lit = 1; lit <= nsols_; ++lit)
sol.push_back(picosat_deref(psat_, lit) > 0);
return sol;
}
satsolver::solution_pair
satsolver::get_solution()
{
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_sol(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.");
temporary_file* output = create_tmpfile("sat-", ".out");
p.first = cmd_.run(cnf_tmp_, output);
p.second = satsolver_get_sol(output->name());
delete output;
}
if (xcnf_mode() && !p.second.empty())
{
// Update xcnf_tmp_ header
xcnf_tmp_->seekp(0);
*xcnf_tmp_ << "p cnf " << (get_nb_vars() + 1);
xcnf_tmp_->seekp(0, std::ios_base::end);
if (!*xcnf_tmp_)
throw std::runtime_error("Failed to update xcnf_tmp_ header");
// Copy xcnf_tmp_ into xcnf_stream_
xcnf_tmp_->close();
std::ifstream ifile(path_ + "/incr.tmp", std::ios::in);
*xcnf_stream_ << ifile.rdbuf();
*xcnf_stream_ << "X\n";
// Erase xcnf_tmp_ content
delete xcnf_tmp_;
xcnf_tmp_ = new std::ofstream(path_ + "/incr.tmp", std::ios_base::trunc);
xcnf_tmp_->exceptions(std::ofstream::failbit | std::ofstream::badbit);
*xcnf_tmp_ << " \n";
*xcnf_tmp_ << "+\n";
}
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 use %I as "
"input filename.");
if (!has('O'))
throw std::runtime_error("SPOT_SATSOLVER should use %O as "
"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;
}
}