Skip to content

Commit

Permalink
add options to dump the smt
Browse files Browse the repository at this point in the history
  • Loading branch information
wu-haoze committed May 12, 2023
1 parent b243cef commit 8157c1c
Show file tree
Hide file tree
Showing 16 changed files with 694 additions and 12 deletions.
3 changes: 3 additions & 0 deletions .gitignore
@@ -1,3 +1,6 @@
*~
#*

# Prerequisites
*.d

Expand Down
4 changes: 4 additions & 0 deletions CMakeLists.txt
Expand Up @@ -66,6 +66,8 @@ set(SMT_SWITCH_DIR "${PROJECT_SOURCE_DIR}/deps/smt-switch")
# rely on cmake modules from smt-switch (required anyway)
set(CMAKE_MODULE_PATH ${SMT_SWITCH_DIR}/cmake)

set(CMAKE_FIND_LIBRARY_SUFFIXES ".a")

find_package(GMP REQUIRED)

# Check that dependencies are there
Expand Down Expand Up @@ -155,6 +157,8 @@ set(SOURCES
"${PROJECT_SOURCE_DIR}/engines/kinduction.cpp"
"${PROJECT_SOURCE_DIR}/engines/mbic3.cpp"
"${PROJECT_SOURCE_DIR}/engines/syguspdr.cpp"
"${PROJECT_SOURCE_DIR}/engines/local_searcher.cpp"
"${PROJECT_SOURCE_DIR}/engines/options_handler.cpp"
"${PROJECT_SOURCE_DIR}/frontends/btor2_encoder.cpp"
"${PROJECT_SOURCE_DIR}/frontends/smv_encoder.cpp"
"${PROJECT_SOURCE_DIR}/frontends/smv_node.cpp"
Expand Down
4 changes: 2 additions & 2 deletions contrib/setup-smt-switch.sh
Expand Up @@ -3,7 +3,7 @@
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
DEPS=$DIR/../deps

SMT_SWITCH_VERSION=445b5bc5172cc4a56db121e5ba4c7a5e14147bd5
SMT_SWITCH_VERSION=c9a72faa99dca7b8d3ba16e37

usage () {
cat <<EOF
Expand Down Expand Up @@ -59,7 +59,7 @@ mkdir -p $DEPS

if [ ! -d "$DEPS/smt-switch" ]; then
cd $DEPS
git clone https://github.com/makaimann/smt-switch
git clone https://github.com/anwu1219/smt-switch
cd smt-switch
git checkout -f $SMT_SWITCH_VERSION
./contrib/setup-btor.sh
Expand Down
80 changes: 74 additions & 6 deletions engines/bmc.cpp
Expand Up @@ -14,7 +14,10 @@
**
**/

#include <filesystem>

#include "bmc.h"

#include "utils/logger.h"

using namespace smt;
Expand Down Expand Up @@ -50,6 +53,71 @@ void Bmc::initialize()
solver_->assert_formula(unroller_.at_time(ts_.init(), 0));
}

void Bmc::dump_query_until(int reached_k, int k)
{
initialize();

//NOTE: there is a corner case where an instance is trivially
//unsatisfiable, i.e., safe, when the conjunction of initial state
//predicate and transition (+ any constraints) is already unsat. We
//could also check this using unsat core functionality of solver (if
//supported), and check if bad state predicate is in core

logger.log(1, "BMC bound_start_: {} ", bound_start_);
logger.log(1, "BMC bound_step_: {} ", bound_step_);

// Options 'bmc_exponential_step_' results in doubling the bound in every step
const bool exp_step = options_.bmc_exponential_step_;

for (int i = 0; i <= k; ++i) {
if (i > 0) {
logger.log(2, " BMC adding transition for i-1 = {}", i-1);
solver_->assert_formula(unroller_.at_time(ts_.trans(), i-1));
if (options_.bmc_neg_init_step_) {
logger.log(2, " BMC adding negated init constraint for step {}", i);
Term not_init = solver_->make_term(PrimOp::Not, unroller_.at_time(ts_.init(), i));
solver_->assert_formula(not_init);
}
}

if (i <= reached_k) {
// Optional: add negated bad state predicates to bounds where no cex was found
assert(!options_.bmc_neg_bad_step_);
if (options_.bmc_neg_bad_step_all_) {
Term not_bad;
logger.log(2, " BMC adding negated bad state constraint for i = {}", i);
not_bad = solver_->make_term(PrimOp::Not, unroller_.at_time(bad_, i));
solver_->assert_formula(not_bad);
}
}
}

// BMC is guaranteed to find a cex if we make sure to check all bounds (default).
// This behavior can be overridden by 'options_.bmc_single_bad_state_'
const int cex_guarantee = !options_.bmc_single_bad_state_;

Term clause;
if (cex_guarantee) {
// Make sure we cover *all* states by adding disjunctive bad state predicate
clause = solver_->make_term(false);
for (int j = reached_k + 1; j <= k; j++) {
logger.log(2, " BMC adding bad state constraint for j = {}", j);
clause = solver_->make_term(PrimOp::Or, clause, unroller_.at_time(bad_, j));
}
} else {
// Add a single bad state predicate (bugs might be missed)
logger.log(2, " BMC adding bad state constraint for k = {}", k);
clause = unroller_.at_time(bad_, k);
}

solver_->assert_formula(clause);

std::string filename = options_.working_directory_ + "/step" + std::to_string(k) +
"_reached" + std::to_string(reached_k) + ".smt2";
logger.log(2, " Dumping full BMC problem for k = {}, {}", k, filename);
solver_->dump_smt2(filename);
}

ProverResult Bmc::check_until(int k)
{
initialize();
Expand Down Expand Up @@ -79,7 +147,7 @@ ProverResult Bmc::check_until(int k)
bool Bmc::step(int i)
{
logger.log(1, "\nBMC checking at bound: {}", i);

if (i <= reached_k_) {
return true;
}
Expand Down Expand Up @@ -118,9 +186,9 @@ bool Bmc::step(int i)
logger.log(2, " BMC adding bad state constraint for i = {}", i);
clause = unroller_.at_time(bad_, i);
}
solver_->assert_formula(clause);

solver_->assert_formula(clause);

Result r = solver_->check_sat();
if (r.is_sat()) {
logger.log(1, " BMC check at bound {} satisfiable", i);
Expand Down Expand Up @@ -198,9 +266,9 @@ int Bmc::bmc_interval_get_cex_ub(const int lb, const int ub)
{
const Term true_term = solver_->make_term(true);
assert(lb <= ub);

logger.log(2, " BMC get cex upper bound: lower bound = {}, upper bound = {} ", lb, ub);

int j;
for (j = lb; j <= ub; j++) {
Term bad_state_at_j = unroller_.at_time(bad_, j);
Expand Down
2 changes: 2 additions & 0 deletions engines/bmc.h
Expand Up @@ -34,6 +34,7 @@ class Bmc : public Prover
void initialize() override;

ProverResult check_until(int k) override;
void dump_query_until(int reached_k, int k) override;

protected:
bool step(int i);
Expand All @@ -45,6 +46,7 @@ class Bmc : public Prover
unsigned int bound_step_;
// Used in binary search for cex: number of times we called 'solver->push()'
unsigned int bin_search_frames_;

// Get an upper bound on the cex, which is located in interval '[lb,ub]'
int bmc_interval_get_cex_ub(const int lb, const int ub);
// Add negated bad state predicate for all bounds in interval '[start,end]'.
Expand Down
209 changes: 209 additions & 0 deletions engines/local_searcher.cpp
@@ -0,0 +1,209 @@
#include "engines/local_searcher.h"

#include "engines/options_handler.h"
#include "utils/logger.h"

#include "smt-switch/smtlib_reader.h"
#include "smt/available_solvers.h"

#include <chrono>
#include <filesystem>
#include <cmath>

using namespace smt;

namespace pono {
LocalSearcher::LocalSearcher(const PonoOptions &opt)
: opt_(opt)
{
initializeConfig();
}

LocalSearcher::~LocalSearcher(){
for (auto &option : options_)
delete option;
options_.clear();
}

void LocalSearcher::initializeConfig() {
OptionsHandler::loadOptions(opt_.config_input_, options_);

sumOfWeights_ = 0;
for (auto &option : options_) {
currentConfig_[option] = option->default_value_;
if (option->weight_)
sumOfWeights_ += std::pow(WEIGHT_BASE_, option->weight_);
}

logger.log(2, "Config initialized");
dumpCurrentConfig();
}

void LocalSearcher::localSearch(std::string filename, unsigned iterations) {
logger.log(1, "Optimizing local search for {} iterations (repeat: {})", iterations, opt_.solver_runs_);
double currentCost = evaluateCurrentConfig(filename, 3);
double lastAcceptedCost = currentCost;
acceptCurrentConfig();
bestConfig_ = currentConfig_; bestIter_ = 0; bestCost_ = currentCost;
initialCost_ = currentCost;

printStatus(0, currentCost);

unsigned printEvery = 5;
// MCMC Sampling
for (unsigned iter = 1; iter <= iterations; ++iter) {
proposeConfigUpdate();
currentCost = evaluateCurrentConfig(filename, opt_.solver_runs_);
if (decideToAcceptCurrentProposal(currentCost, lastAcceptedCost)) {
lastAcceptedCost = currentCost;
acceptCurrentConfig();
if (currentCost < bestCost_) {
bestConfig_ = currentConfig_; bestIter_ = iter; bestCost_ = currentCost;
}
}
if (iter % printEvery == 0)
printStatus(iter, currentCost);
}
}

void LocalSearcher::writeBestConfig(std::string filename) {
logger.log(1, "Writing best configuration to {}", filename);
logger.log(1, "\tIteration: {}, initial cost: {}, best cost: {}",
bestIter_, initialCost_, bestCost_);
std::vector<SolverOption *> options;
for (const auto &option : options_) {
options.push_back(new SolverOption
(option->option_name_,
lastAcceptedConfig_[option],
option->values_,
option->weight_));
}
OptionsHandler::writeOptions(filename, options);
for (auto &option : options)
delete option;
}


void LocalSearcher::proposeConfigUpdate() {
logger.log(2, "Proposing configuration update");
currentConfig_ = lastAcceptedConfig_;

// Select an option
int rnd = (int) rand() % sumOfWeights_;
SolverOption *option = nullptr;
for (auto &o : options_) {
if (o->weight_) {
int v = std::pow(WEIGHT_BASE_, o->weight_);
if (rnd < v) {
option = o;
break;
}
rnd -= v;
}
}

if (option->weight_ == 0) {
logger.log(0, "Cannot select option with zero weights");
exit(0);
}

// Select a value
std::string val1 = currentConfig_[option];
std::string val2 = option->alternative(val1);
currentConfig_[option] = val2;
logger.log(2, "Proposed update for {}: {} -> {} ",
option->option_name_, val1, val2);
}

double LocalSearcher::evaluateCurrentConfig(std::string filename, unsigned solverRuns) {
logger.log(2, "Evaluating configuration...");

std::string configStr = currentConfigtoString();
if (configToCost_.count(configStr)) {
double score = configToCost_[configStr];
logger.log(2, "Configuration cached, cost: {}", score);
return score;
} else {
unsigned long long sum = 0;
unsigned long long runtime;
for (unsigned i = 0; i < solverRuns; ++i) {
SmtSolver s = create_solver_for(opt_.smt_solver_,
opt_.engine_,
false,
opt_.ceg_prophecy_arrays_);

loadConfig(s, currentConfig_);
SmtLibReader reader(s);
auto begin = std::chrono::high_resolution_clock::now();
reader.parse(opt_.filename_);

auto end = std::chrono::high_resolution_clock::now();

runtime = std::chrono::duration_cast<std::chrono::milliseconds>(end-begin).count();
logger.log(2, "\tAttempt {}: cost {}", i, runtime);
sum += runtime;
}
sum /= solverRuns;

configToCost_[configStr] = sum;
logger.log(2, "Evaluating configuration - done, cost: {}", sum);
return sum;
}
}
void LocalSearcher::loadConfig(smt::SmtSolver &solver,
const std::unordered_map<SolverOption *, std::string> &config) const {
for (const auto &pair : config) {
solver->set_opt(pair.first->option_name_, pair.second);
}
}

bool LocalSearcher::decideToAcceptCurrentProposal(double currentCost,
double lastAcceptedCost) const {
double mcmcBeta = 1;

logger.log(2, "Current cost: {}, last accepted cost: {}",
currentCost, lastAcceptedCost);
if (currentCost < lastAcceptedCost) {
logger.log(2, "\tPobability to accept: {}", 1);
return true;
} else {
// The smaller the difference between the proposed phase pattern and the
// current phase pattern, the more likely to accept the proposal.
double prob = std::exp(- currentCost / lastAcceptedCost * mcmcBeta);
logger.log(2, "\tPobability to accept: {}", prob);

return ((double)rand() / RAND_MAX) < prob;
}
}

void LocalSearcher::acceptCurrentConfig() {
lastAcceptedConfig_ = currentConfig_;
logger.log(2, "\tCurrent configuration accepted");
}

void LocalSearcher::dumpCurrentConfig() const {
logger.log(0, "Dumping current configuration:");
for (auto &pair : currentConfig_) {
std::string s;
pair.first->dump(s);
logger.log(0, s);
logger.log(0, " current value: {}", pair.second);
}
}

std::string LocalSearcher::currentConfigtoString() const {
std::string s = "";
for (auto &pair : currentConfig_) {
s += pair.second + ",";
}
return s;
}

void LocalSearcher::printStatus(unsigned iter,
double currentCost) const {
logger.log(1, "---- Iteration: {}, current cost: {}, best cost: {} ----\n",
iter, currentCost, bestCost_);
}


} // namespace pono

0 comments on commit 8157c1c

Please sign in to comment.