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.
This commit is contained in:
Alexandre Duret-Lutz 2018-03-30 11:31:46 +02:00
parent 5266010889
commit c766f58d5d
21 changed files with 5076 additions and 177 deletions

View file

@ -28,7 +28,6 @@
#include <spot/twaalgos/dtbasat.hh>
#include <spot/twaalgos/langmap.hh>
#include <spot/twaalgos/sccinfo.hh>
#include <spot/twaalgos/stats.hh>
// If you set the SPOT_TMPKEEP environment variable the temporary
// file used to communicate with the sat solver will be left in
@ -708,6 +707,7 @@ namespace spot
}
#endif
a->merge_edges();
a->purge_unreachable_states();
return a;
}
}
@ -744,7 +744,8 @@ namespace spot
if (!solution.second.empty())
res = sat_build(solution.second, d, a, state_based);
print_log(t, target_state_number, res, solver); // If SPOT_SATLOG is set.
print_log(t, a->num_states(),
target_state_number, res, solver); // If SPOT_SATLOG is set.
trace << "dtba_sat_synthetize(...) = " << res << '\n';
return res;
@ -754,7 +755,6 @@ namespace spot
dichotomy_dtba_research(int max,
dict& d,
satsolver& solver,
timer_map& t1,
const_twa_graph_ptr& prev,
bool state_based)
{
@ -768,36 +768,47 @@ namespace spot
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 - stats_reachable(res).states + 1;
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...";
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);
}
t1.stop("solve");
print_log(t1, d.cand_size - target, res, solver); // SPOT_SATLOG.
return res ? res : std::const_pointer_cast<spot::twa_graph>(prev);
}
@ -816,8 +827,7 @@ namespace spot
const_twa_graph_ptr prev = a;
dict d;
d.cand_size = (max_states < 0) ?
stats_reachable(prev).states - 1 : max_states;
d.cand_size = (max_states < 0) ? prev->num_states() - 1 : max_states;
if (d.cand_size == 0)
return nullptr;
@ -829,7 +839,7 @@ namespace spot
while (next && d.cand_size > 0)
{
// Warns the satsolver of the number of assumptions.
int n_assumptions = (int) d.cand_size < sat_incr_steps ?
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;
@ -867,34 +877,36 @@ namespace spot
solver.add({-assume_lit, assume_lit - 1, 0});
}
}
t1.stop("encode");
t1.start("solve");
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, t1, prev,
state_based);
return dichotomy_dtba_research(n_assumptions, d, solver,
prev, state_based);
}
t1.stop("solve");
trace << "SAT, restarting from zero\n";
next = solution.second.empty() ? nullptr :
sat_build(solution.second, d, prev, state_based);
print_log(t1, d.cand_size - n_assumptions, next, solver); // SPOT_SATLOG.
print_log(t1, prev->num_states(),
d.cand_size - n_assumptions, next, solver);
if (next)
{
prev = next;
d = dict();
d.cand_size = stats_reachable(prev).states - 1;
d.cand_size = prev->num_states() - 1;
if (d.cand_size == 0)
next = nullptr;
}
@ -912,8 +924,7 @@ namespace spot
(": 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) ?
stats_reachable(prev).states - 1 : max_states;
d.cand_size = (max_states < 0) ? prev->num_states() - 1 : max_states;
if (d.cand_size == 0)
return nullptr;
@ -937,7 +948,7 @@ namespace spot
t1.stop("solve");
next = solution.second.empty() ? nullptr :
sat_build(solution.second, d, prev, state_based);
print_log(t1, d.cand_size, next, solver); // If SPOT_SATLOG is set.
print_log(t1, prev->num_states(), d.cand_size, next, solver);
trace << "First iteration done\n";
@ -950,27 +961,27 @@ namespace spot
for (int k = 0; next && d.cand_size > 0 && (naive || k < sat_incr_steps);
++k)
{
t1.start("encode");
prev = next;
int reach_states = stats_reachable(prev).states;
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;
t1.stop("encode");
t1.start("solve");
t2.start("solve");
satsolver::solution_pair solution = solver.get_solution();
t1.stop("solve");
t2.stop("solve");
next = solution.second.empty() ? nullptr :
sat_build(solution.second, d, prev, state_based);
print_log(t1, d.cand_size, next, solver); // If SPOT_SATLOG is set.
print_log(t2, prev->num_states(),
d.cand_size, next, solver);
}
if (next)
@ -978,7 +989,7 @@ namespace spot
trace << "Starting from scratch\n";
prev = next;
d = dict();
d.cand_size = stats_reachable(prev).states - 1;
d.cand_size = prev->num_states() - 1;
if (d.cand_size == 0)
next = nullptr;
}
@ -991,8 +1002,7 @@ namespace spot
dtba_sat_minimize(const const_twa_graph_ptr& a,
bool state_based, int max_states)
{
int n_states = (max_states < 0) ?
stats_reachable(a).states : max_states + 1;
int n_states = (max_states < 0) ? a->num_states() : max_states + 1;
twa_graph_ptr prev = nullptr;
for (;;)
@ -1002,7 +1012,7 @@ namespace spot
if (!next)
return prev;
else
n_states = stats_reachable(next).states;
n_states = next->num_states();
prev = next;
}
SPOT_UNREACHABLE();
@ -1014,7 +1024,7 @@ namespace spot
{
trace << "Dichomoty\n";
if (max_states < 0)
max_states = stats_reachable(a).states - 1;
max_states = a->num_states() - 1;
int min_states = 1;
if (langmap)
{
@ -1036,7 +1046,7 @@ namespace spot
else
{
prev = next;
max_states = stats_reachable(next).states - 1;
max_states = next->num_states() - 1;
}
}
return prev;

View file

@ -36,7 +36,6 @@
#include <spot/twaalgos/sbacc.hh>
#include <spot/twaalgos/sccfilter.hh>
#include <spot/twaalgos/sccinfo.hh>
#include <spot/twaalgos/stats.hh>
// If you set the SPOT_TMPKEEP environment variable the temporary
// file used to communicate with the sat solver will be left in
@ -624,7 +623,7 @@ namespace spot
{
unsigned nacc = d.cand_nacc;
cnf_comment("transitions belong to exactly one of the", nacc,
"acceptance set\n");
"acceptance sets\n");
for (unsigned l = 0; l < alpha_size; ++l)
for (unsigned q1 = 0; q1 < d.cand_size; ++q1)
for (unsigned q2 = 0; q2 < d.cand_size; ++q2)
@ -982,6 +981,7 @@ namespace spot
#endif
a->merge_edges();
a->purge_unreachable_states();
return a;
}
}
@ -1023,7 +1023,7 @@ namespace spot
if (!solution.second.empty())
res = sat_build(solution.second, d, a, state_based);
print_log(t, target_state_number, res, solver); // if SPOT_SATLOG is set.
print_log(t, a->num_states(), target_state_number, res, solver);
trace << "dtwa_sat_synthetize(...) = " << res << '\n';
return res;
@ -1066,7 +1066,6 @@ namespace spot
dichotomy_dtwa_research(int max,
dict& d,
satsolver& solver,
timer_map& t1,
const_twa_graph_ptr& prev,
bool state_based)
{
@ -1081,35 +1080,46 @@ namespace spot
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 - stats_reachable(res).states + 1;
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...";
trace << "All assumptions are UNSAT, let's try without...\n";
timer_map t1;
t1.start("encode");
t1.stop("encode");
t1.start("solve");
satsolver::solution_pair solution = solver.get_solution();
t1.stop("solve");
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);
}
t1.stop("solve");
print_log(t1, d.cand_size - target , res, solver); // SPOT_SATLOG.
return res ? res : std::const_pointer_cast<spot::twa_graph>(prev);
}
@ -1128,8 +1138,7 @@ namespace spot
const_twa_graph_ptr prev = a;
dict d(prev);
d.cand_size = (max_states < 0) ?
stats_reachable(prev).states - 1 : max_states;
d.cand_size = (max_states < 0) ? prev->num_states() - 1 : max_states;
d.cand_nacc = target_acc_number;
d.cand_acc = target_acc;
if (d.cand_size == 0)
@ -1144,9 +1153,9 @@ namespace spot
while (next && d.cand_size > 0)
{
// Warns the satsolver of the number of assumptions.
int n_assumptions = (int) d.cand_size < sat_incr_steps ?
int n_assumptions = (int) d.cand_size <= sat_incr_steps ?
d.cand_size - 1 : sat_incr_steps;
trace << "number of assumptions:" << n_assumptions << '\n';
trace << "number of assumptions: " << n_assumptions << '\n';
satsolver solver;
solver.set_nassumptions_vars(n_assumptions);
@ -1182,35 +1191,39 @@ namespace spot
solver.add({-assume_lit, assume_lit - 1, 0});
}
}
t1.stop("encode");
t1.start("solve");
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";
twa_graph_ptr res = dichotomy_dtwa_research(n_assumptions, d, solver,
t1, prev, state_based);
prev, state_based);
return res == a ? nullptr : res;
}
t1.stop("solve");
trace << "SAT, restarting from zero\n";
next = solution.second.empty() ? nullptr :
sat_build(solution.second, d, prev, state_based);
print_log(t1, d.cand_size - n_assumptions, next, solver); // SPOT_SATLOG.
print_log(t1, prev->num_states(),
d.cand_size - n_assumptions, next, solver);
if (next)
{
prev = next;
trace << "prev has " << prev->num_states() << '\n';
d = dict(prev);
d.cand_size = stats_reachable(prev).states - 1;
d.cand_size = prev->num_states() - 1;
d.cand_nacc = target_acc_number;
d.cand_acc = target_acc;
if (d.cand_size == 0)
@ -1233,7 +1246,7 @@ namespace spot
dict d(prev);
d.cand_size = (max_states < 0) ?
stats_reachable(prev).states - 1 : max_states;
prev->num_states() - 1 : max_states;
d.cand_nacc = target_acc_number;
d.cand_acc = target_acc;
if (d.cand_size == 0)
@ -1260,9 +1273,9 @@ namespace spot
t1.stop("solve");
next = solution.second.empty() ? nullptr :
sat_build(solution.second, d, prev, state_based);
print_log(t1, d.cand_size, next, solver); // If SPOT_SATLOG is set.
print_log(t1, prev->num_states(), d.cand_size, next, solver);
trace << "First iteraton done\n";
trace << "First iteration done\n";
// Compute the AP used.
bdd ap = prev->ap_vars();
@ -1273,27 +1286,25 @@ namespace spot
for (int k = 0; next && d.cand_size > 0 && (naive || k < sat_incr_steps);
++k)
{
t1.start("encode");
prev = next;
int reach_states = stats_reachable(prev).states;
cnf_comment("Next iteration:", reach_states - 1, "\n");
int reach_states = prev->num_states();
trace << "Encoding the deletion of state " << reach_states - 1 << '\n';
cnf_comment("Next iteration:", 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;
t1.stop("encode");
t1.start("solve");
t2.start("solve");
solution = solver.get_solution();
t1.stop("solve");
t2.stop("solve");
next = solution.second.empty() ? nullptr :
sat_build(solution.second, d, prev, state_based);
print_log(t1, d.cand_size, next, solver); // If SPOT_SATLOG is set.
print_log(t2, prev->num_states(), d.cand_size, next, solver);
}
if (next)
@ -1301,7 +1312,7 @@ namespace spot
trace << "Starting from scratch\n";
prev = next;
d = dict(prev);
d.cand_size = stats_reachable(prev).states - 1;
d.cand_size = prev->num_states() - 1;
d.cand_nacc = target_acc_number;
d.cand_acc = target_acc;
if (d.cand_size == 0)
@ -1320,8 +1331,7 @@ namespace spot
bool state_based, int max_states,
bool colored)
{
int n_states = (max_states < 0) ?
stats_reachable(a).states : max_states + 1;
int n_states = (max_states < 0) ? a->num_states() : max_states + 1;
twa_graph_ptr prev = nullptr;
for (;;)
@ -1333,7 +1343,7 @@ namespace spot
if (!next)
return prev;
else
n_states = stats_reachable(next).states;
n_states = next->num_states();
prev = next;
}
SPOT_UNREACHABLE();
@ -1348,7 +1358,7 @@ namespace spot
{
trace << "Dichotomy\n";
if (max_states < 1)
max_states = stats_reachable(a).states - 1;
max_states = a->num_states() - 1;
int min_states = 1;
if (langmap)
{
@ -1373,7 +1383,7 @@ namespace spot
else
{
prev = next;
max_states = stats_reachable(next).states - 1;
max_states = next->num_states() - 1;
}
}
return prev;
@ -1401,10 +1411,10 @@ namespace spot
bool sat_langmap = om.get("sat-langmap", 0);
int states = om.get("states", -1);
int max_states = om.get("max-states", -1);
auto accstr = om.get_str("acc");
std::string accstr = om.get_str("acc");
bool colored = om.get("colored", 0);
int preproc = om.get("preproc", 3);
set_satlog_filename(om.get_str("log"));
// Set default sat-incr-steps value if not provided and used.
if (sat_incr == 1 && !sat_incr_steps) // Assume
@ -1551,7 +1561,7 @@ namespace spot
if (a)
{
if (state_based)
if (state_based || colored)
a = scc_filter_states(a);
else
a = scc_filter(a);

View file

@ -1,5 +1,5 @@
// -*- coding: utf-8 -*-
// Copyright (C) 2013, 2014, 2015 Laboratoire de Recherche et
// Copyright (C) 2013, 2014, 2015, 2018 Laboratoire de Recherche et
// Développement de l'Epita.
//
// This file is part of Spot, a model checking library.
@ -140,6 +140,7 @@ namespace spot
/// fixed number of states before starting from scratch
/// incr < 0 // use satsolver incrementally, never restart
/// colored = 1 // build a colored TωA
/// log = "filename"
///
SPOT_API twa_graph_ptr
sat_minimize(twa_graph_ptr aut, const char* opt, bool state_based = false);