old version

This commit is contained in:
YuhangQ 2022-10-21 19:34:18 +08:00
parent 03691a7e97
commit 356415c113
330 changed files with 43304 additions and 113 deletions

View File

@ -1,6 +1,8 @@
{
"files.associations": {
"*.ejs": "html",
"iostream": "cpp"
"iostream": "cpp",
"*.tcc": "cpp",
"new": "cpp"
}
}

487
acec.cpp
View File

@ -4,86 +4,362 @@
#include "circuit.hpp"
#include "sweep.hpp"
#include "libopenf4.h"
//#include "libopenf4.h"
#include "src/cadical.hpp"
#include "polynomial.h"
extern "C" {
#include "aiger.h"
}
#define pvar(i) ((string((i) < 0 ? "N" : "") + "A" + std::to_string(abs(i))).c_str())
Sweep *S;
Sweep* sweep_init(int argv, char* args[]) {
Sweep *S = new Sweep();
S->aig = aiger_init();
S->solver = new CaDiCaL::Solver;
S->det_v_sz = 0;
S->file = fopen(args[1], "r");
// S->rounds = atoi(args[2]);
// if (argv > 3) {
// S->det_v = new int[argv -3];
// for (int i = 3; i < argv; i++)
// S->det_v[S->det_v_sz++] = atoi(args[i]);
// }
return S;
}
Var* pvar(int id) {
static std::map<int, Var*> mp;
if(mp.count(id)) return mp[id];
std::string name = "A" + std::to_string(abs(id));
if(id < 0) name = "N" + name;
Var* var = new Var(name.c_str(), id > 0 ? S->topo_index[id] : (100000 + id) );
return mp[id] = var;
}
bool subsitute(Polynomial* &a, Polynomial* b) {
Polynomial* result = divide_by_term(a, b->get_lt());
auto ltm = new Monomial(minus_one, b->get_lt());
push_mstack(ltm);
auto ltp = build_poly();
Polynomial* to_minus = multiply_poly(result, ltp);
Polynomial* new_result = add_poly(a, to_minus);
for(int i=1; i<b->size(); i++) {
push_mstack_end(b->get_mon(i));
}
Polynomial* tail = build_poly();
Polynomial* new_tail = multiply_poly(result, tail);
mpz_t c; mpz_init(c);
mpz_mul(c, b->get_mon(0)->coeff, minus_one);
Polynomial* tail_with_c = multiply_poly_with_constant(new_tail, c);
Polynomial* done = add_poly(tail_with_c, new_result);
// printf("=======subsitute======\n");
// printf("a: "); a->print(stdout);
// printf("b: "); b->print(stdout);
// printf("result: "); result->print(stdout);
// printf("to_minus: "); to_minus->print(stdout);
// printf("new_result: "); new_result->print(stdout);
// printf("tail: "); tail->print(stdout);
// printf("new_tail: "); new_tail->print(stdout);
// printf("tail_with_c: "); tail_with_c->print(stdout);
// printf("done: "); done->print(stdout);
a = done;
return result->is_constant_zero_poly();
}
void getPolyOfGate(Sweep* s, int gateid, Polynomial* &resultA, Polynomial* &resultB) {
circuit *c = &(s->C[abs(gateid)]);
gateid = abs(gateid) * (c->neg ? -1 : 1);
if(c->type == And) {
add_to_vstack(pvar(gateid));
push_mstack(new Monomial(minus_one, build_term_from_stack()));
for(int i=0; i<c->sz; i++) {
add_to_vstack(pvar(c->to[i]));
}
push_mstack(new Monomial(one, build_term_from_stack()));
resultA = build_poly();
} else if(c->type == Xor) {
add_to_vstack(pvar(gateid));
push_mstack(new Monomial(minus_one, build_term_from_stack()));
add_to_vstack(pvar(c->to[0]));
add_to_vstack(pvar(c->to[1]));
push_mstack(new Monomial(minus_two, build_term_from_stack()));
add_to_vstack(pvar(c->to[0]));
push_mstack(new Monomial(one, build_term_from_stack()));
add_to_vstack(pvar(c->to[1]));
push_mstack(new Monomial(one, build_term_from_stack()));
resultA = build_poly();
} else if(c->type == HA) {
add_to_vstack(pvar(gateid));
push_mstack(new Monomial(minus_one, build_term_from_stack()));
add_to_vstack(pvar(c->to[0]));
add_to_vstack(pvar(c->to[1]));
push_mstack(new Monomial(minus_two, build_term_from_stack()));
add_to_vstack(pvar(c->to[0]));
push_mstack(new Monomial(one, build_term_from_stack()));
add_to_vstack(pvar(c->to[1]));
push_mstack(new Monomial(one, build_term_from_stack()));
resultA = build_poly();
add_to_vstack(pvar(c->carrier));
push_mstack(new Monomial(minus_one, build_term_from_stack()));
for(int i=0; i<c->sz; i++) {
add_to_vstack(pvar(c->to[i]));
}
push_mstack(new Monomial(one, build_term_from_stack()));
resultB = build_poly();
} else if(c->type == FA) {
add_to_vstack(pvar(gateid));
push_mstack(new Monomial(minus_one, build_term_from_stack()));
add_to_vstack(pvar(c->to[0]));
push_mstack(new Monomial(one, build_term_from_stack()));
add_to_vstack(pvar(c->to[1]));
push_mstack(new Monomial(one, build_term_from_stack()));
add_to_vstack(pvar(c->to[2]));
push_mstack(new Monomial(one, build_term_from_stack()));
add_to_vstack(pvar(c->to[0]));
add_to_vstack(pvar(c->to[1]));
push_mstack(new Monomial(minus_two, build_term_from_stack()));
add_to_vstack(pvar(c->to[1]));
add_to_vstack(pvar(c->to[2]));
push_mstack(new Monomial(minus_two, build_term_from_stack()));
add_to_vstack(pvar(c->to[0]));
add_to_vstack(pvar(c->to[2]));
push_mstack(new Monomial(minus_two, build_term_from_stack()));
add_to_vstack(pvar(c->to[0]));
add_to_vstack(pvar(c->to[1]));
add_to_vstack(pvar(c->to[2]));
push_mstack(new Monomial(four, build_term_from_stack()));
resultA = build_poly();
add_to_vstack(pvar(c->carrier));
push_mstack(new Monomial(minus_one, build_term_from_stack()));
add_to_vstack(pvar(c->to[0]));
add_to_vstack(pvar(c->to[1]));
push_mstack(new Monomial(one, build_term_from_stack()));
add_to_vstack(pvar(c->to[0]));
add_to_vstack(pvar(c->to[2]));
push_mstack(new Monomial(one, build_term_from_stack()));
add_to_vstack(pvar(c->to[1]));
add_to_vstack(pvar(c->to[2]));
push_mstack(new Monomial(one, build_term_from_stack()));
add_to_vstack(pvar(c->to[0]));
add_to_vstack(pvar(c->to[1]));
add_to_vstack(pvar(c->to[2]));
push_mstack(new Monomial(minus_two, build_term_from_stack()));
resultB = build_poly();
} else if(c->type == Majority) {
add_to_vstack(pvar(gateid));
push_mstack(new Monomial(minus_one, build_term_from_stack()));
add_to_vstack(pvar(c->to[0]));
add_to_vstack(pvar(c->to[1]));
push_mstack(new Monomial(one, build_term_from_stack()));
add_to_vstack(pvar(c->to[0]));
add_to_vstack(pvar(c->to[2]));
push_mstack(new Monomial(one, build_term_from_stack()));
add_to_vstack(pvar(c->to[1]));
add_to_vstack(pvar(c->to[2]));
push_mstack(new Monomial(one, build_term_from_stack()));
add_to_vstack(pvar(c->to[0]));
add_to_vstack(pvar(c->to[1]));
add_to_vstack(pvar(c->to[2]));
push_mstack(new Monomial(minus_two, build_term_from_stack()));
resultA = build_poly();
}
}
bool poly_cmp(Polynomial *a, Polynomial *b) {
return a->get_lt()->get_var()->get_level() > b->get_lt()->get_var()->get_level();
}
std::map<Polynomial*, Polynomial*> another;
int main(int argv, char* args[]) {
Sweep *S = sweep_init(argv, args);
init_mpz(19260817);
S = sweep_init(argv, args);
S->solve();
using namespace std;
//using namespace F4;
// Create polynomial array.
vector<string> polynomialArray;
// Create variable name array.
vector<string> variableName;
std::map<std::string, Polynomial*> nas;
for(int i=1; i<=S->maxvar; i++) {
add_to_vstack(pvar(i));
push_mstack(new Monomial(one, build_term_from_stack()));
add_to_vstack(pvar(-i));
push_mstack(new Monomial(one, build_term_from_stack()));
push_mstack(new Monomial(minus_one, build_term_from_stack()));
Polynomial *poly = build_poly();
nas[pvar(-i)->get_name()] = poly;
nas[pvar(i)->get_name()] = poly;
}
set<string> vars;
std::vector<Polynomial*> polys;
int t = S->maxvar;
std::set<int> reserved_vars;
std::vector<Polynomial*> subsitute_vars;
for (int i = 1; i <= S->maxvar; i++) {
circuit *c = &(S->C[i]);
if (!c->sz || S->del[i] == 2) continue;
if(c->type == Xor) {
reserved_vars.insert(i);
// for(int j=0; j<c->sz; j++)
// reserved_vars.insert(abs(c->to[j]));
}
}
for (int i = 1; i <= S->maxvar; i++) {
circuit *c = &(S->C[i]);
if (!c->sz || S->del[i] == 2) continue;
if(!reserved_vars.count(i)) {
Polynomial *p, *q;
getPolyOfGate(S, i, p, q);
while(p->get_lt()->get_var_name()[0] == 'N') {
Polynomial *to_div = nas[p->get_lt()->get_var_name()];
subsitute(p, to_div);
}
subsitute_vars.push_back(p);
}
}
printf("reserved: %d ; subsitute: %d\n", reserved_vars.size(), subsitute_vars.size());
for (int i = 1; i <= S->maxvar; i++) {
circuit *c = &(S->C[i]);
if (!c->sz || S->del[i] == 2) continue;
}
for (int i = 1; i <= S->maxvar; i++) {
circuit *c = &(S->C[i]);
if (!c->sz || S->del[i] == 2) continue;
if(c->type == And) {
char term[1024];
sprintf(term, "-%s+%s", pvar(i * (S->C[i].neg ? -1 : 1)), pvar(c->to[0]));
for(int i=1; i<c->sz; i++) {
sprintf(term, "%s*%s", term, pvar(c->to[i]));
}
polynomialArray.push_back(term);
printf("%s\n", term);
}
if(c->type == Xor) {
string last = pvar(c->to[0]);
char term[1024];
for(int i=1; i<c->sz-1; i++) {
string tmp = pvar(++t);
sprintf(term, "-%s-2*%s*%s+%s+%s", tmp.c_str(), last.c_str(), pvar(c->to[i]), last.c_str(), pvar(c->to[i]));
polynomialArray.push_back(term);
printf("%s\n", term);
last = tmp;
}
sprintf(term, "-%s-2*%s*%s+%s+%s", pvar(i * (S->C[i].neg ? -1 : 1)), last.c_str(), pvar(c->to[c->sz-1]), last.c_str(), pvar(c->to[c->sz-1]));
polynomialArray.push_back(term);
printf("%s\n", term);
if(!reserved_vars.count(i)) continue;
//if(c->sz == 2 && abs(c->to[0]) <= 14 && abs(c->to[1]) <= 14) continue;
Polynomial *resultA, *resultB = nullptr;
getPolyOfGate(S, i, resultA, resultB);
printf(" original:\t"); resultA->print(stdout);
while(resultA->get_lt()->get_var_name()[0] == 'N') {
Polynomial *to_div = nas[resultA->get_lt()->get_var_name()];
subsitute(resultA, to_div);
}
printf(" pos-var:\t"); resultA->print(stdout);
for(auto subs : subsitute_vars) {
//printf("delete: "); subs->print(stdout);
subsitute(resultA, subs);
}
printf(" xor-relative:\t"); resultA->print(stdout);
polys.push_back(resultA);
if (c->type == HA || c->type == FA) {
char term[1024];
sprintf(term, "-%s-2*%s+%s", pvar(i * (S->C[i].neg ? -1 : 1)), pvar(c->carrier), pvar(c->to[0]));
for(int i=1; i<c->sz; i++) {
sprintf(term, "%s+%s", term, pvar(c->to[i]));
}
polynomialArray.push_back(term);
printf("%s\n", term);
printf("( %d %d )", i * (S->C[i].neg ? -1 : 1), c->carrier);
}
else {
printf("%d", i * (S->C[i].neg ? -1 : 1));
}
if(c->type == Majority) {
char term[1024];
sprintf(term, "-%s-2*%s+%s", pvar(++t), pvar(i * (S->C[i].neg ? -1 : 1)), pvar(c->to[0]));
for(int i=1; i<c->sz; i++) {
sprintf(term, "%s+%s", term, pvar(c->to[i]));
}
polynomialArray.push_back(term);
printf("%s\n", term);
printf(" = %s ( ", gate_type[c->type].c_str());
for (int j = 0; j < c->sz; j++) {
printf("%d ", c->to[j]);
}
puts(")");
}
add_to_vstack(pvar(32));
push_mstack(new Monomial(one, build_term_from_stack()));
Polynomial *result = build_poly();
std::sort(polys.begin(), polys.end(), poly_cmp);
for(auto p : polys) {
printf("now: "); result->print(stdout);
printf("div: "); p->print(stdout);
subsitute(result, p);
}
return 0;
}
// for (int i = 1; i <= S->maxvar; i++) {
// circuit *c = &(S->C[i]);
// if (!c->sz || S->del[i] == 2) continue;
// if(c->type == And) {
// char term[1024];
// sprintf(term, "-%s+%s", pvar(i * (S->C[i].neg ? -1 : 1)), pvar(c->to[0]));
// for(int i=1; i<c->sz; i++) {
// sprintf(term, "%s*%s", term, pvar(c->to[i]));
// }
// polynomialArray.push_back(term);
// printf("%s\n", term);
// }
// if(c->type == Xor) {
// string last = pvar(c->to[0]);
// char term[1024];
// for(int i=1; i<c->sz-1; i++) {
// string tmp = pvar(++t);
// sprintf(term, "-%s-2*%s*%s+%s+%s", tmp.c_str(), last.c_str(), pvar(c->to[i]), last.c_str(), pvar(c->to[i]));
// polynomialArray.push_back(term);
// printf("%s\n", term);
// last = tmp;
// }
// sprintf(term, "-%s-2*%s*%s+%s+%s", pvar(i * (S->C[i].neg ? -1 : 1)), last.c_str(), pvar(c->to[c->sz-1]), last.c_str(), pvar(c->to[c->sz-1]));
// polynomialArray.push_back(term);
// printf("%s\n", term);
// }
// if (c->type == HA || c->type == FA) {
// char term[1024];
// sprintf(term, "-%s-2*%s+%s", pvar(i * (S->C[i].neg ? -1 : 1)), pvar(c->carrier), pvar(c->to[0]));
// for(int i=1; i<c->sz; i++) {
// sprintf(term, "%s+%s", term, pvar(c->to[i]));
// }
// polynomialArray.push_back(term);
// printf("%s\n", term);
// }
// if(c->type == Majority) {
// char term[1024];
// sprintf(term, "-%s-2*%s+%s", pvar(++t), pvar(i * (S->C[i].neg ? -1 : 1)), pvar(c->to[0]));
// for(int i=1; i<c->sz; i++) {
// sprintf(term, "%s+%s", term, pvar(c->to[i]));
// }
// polynomialArray.push_back(term);
// printf("%s\n", term);
// }
// if (c->type == HA || c->type == FA) {
// printf("( %d %d )", i * (S->C[i].neg ? -1 : 1), c->carrier);
@ -98,24 +374,24 @@ int main(int argv, char* args[]) {
// printf("%d ", c->to[j]);
// }
// puts(")");
}
// }
for(int i=t; i>=1; i--) {
variableName.push_back(pvar(i));
variableName.push_back(pvar(-i));
char term[1024];
sprintf(term, "-%s+%s^2", pvar(i), pvar(i));
polynomialArray.emplace_back(term);
printf("%s\n", term);
sprintf(term, "%s+%s", pvar(i), pvar(-i));
polynomialArray.emplace_back(term);
printf("%s\n", term);
}
// for(int i=t; i>=1; i--) {
// variableName.push_back(pvar(i));
// variableName.push_back(pvar(-i));
// char term[1024];
// sprintf(term, "-%s+%s^2", pvar(i), pvar(i));
// polynomialArray.emplace_back(term);
// printf("%s\n", term);
// sprintf(term, "%s+%s", pvar(i), pvar(-i));
// polynomialArray.emplace_back(term);
// printf("%s\n", term);
// }
char term[1024];
sprintf(term, "-%s+1", pvar(S->maxvar));
polynomialArray.emplace_back(term);
printf("%s\n", term);
// char term[1024];
// sprintf(term, "-%s+1", pvar(S->maxvar));
// polynomialArray.emplace_back(term);
// printf("%s\n", term);
// for(int i = 0; i < 6; i++)
// {
@ -131,17 +407,72 @@ int main(int argv, char* args[]) {
// polynomialArray.emplace_back("x0*x1*x2*x3*x4*x5-1");
// Compute a reduce groebner basis.
vector<string> basis = groebnerBasisF4(7, variableName.size(), variableName, polynomialArray, 1, 0);
//vector<string> basis = groebnerBasisF4(7, variableName.size(), variableName, polynomialArray, 1, 0);
for(size_t i = 0; i < basis.size(); i++)
{
cout << basis[i] << endl;
}
// for(size_t i = 0; i < basis.size(); i++)
// {
// cout << basis[i] << endl;
// }
for(int i=t; i>=1; i--) {
printf("(declare-fun %s () Bool)", pvar(i));
printf("(declare-fun %s () Bool)", pvar(-i));
}
return 0;
}
// for(int i=1; i<=t; i++) {
// printf("(declare-fun %s () Bool)\n", pvar(i).c_str());
// }
// for (int i = 1; i <= S->maxvar; i++) {
// circuit *c = &(S->C[i]);
// if (!c->sz || S->del[i] == 2) continue;
// int var = i * (S->C[i].neg ? -1 : 1);
// if(c->type == And) {
// string clause = "(assert (= " + pvar(var) + " (and";
// for(int i=0; i<c->sz; i++) {
// clause += " " + pvar(c->to[i]);
// }
// clause += ")))";
// cout << clause << endl;
// }
// if(c->type == Xor) {
// string clause = "(assert (= " + pvar(var) + " (xor";
// for(int i=0; i<c->sz; i++) {
// clause += " " + pvar(c->to[i]);
// }
// clause += ")))";
// cout << clause << endl;
// }
// if (c->type == HA || c->type == FA) {
// string clause = "(assert (= (+ " + pvar(var) + " (* 2 "+pvar(c->carrier)+")) (+ ";
// for(int i=0; i<c->sz; i++) {
// clause += " " + pvar(c->to[i]);
// }
// clause += ")))";
// cout << clause << endl;
// }
// if(c->type == Majority) {
// string ta = pvar(c->to[0]);
// string tb = pvar(c->to[1]);
// string tc = pvar(c->to[2]);
// string clause = "(assert (= "+pvar(var)+" (or (and "+ta+" "+tb+") (and "+ta+" "+tc+") (and "+tb+" "+tc+"))))";
// cout << clause << endl;
// }
// // if (c->type == HA || c->type == FA) {
// // printf("( %d %d )", i * (S->C[i].neg ? -1 : 1), c->carrier);
// // }
// // else {
// // printf("%d", i * (S->C[i].neg ? -1 : 1));
// // }
// // printf(" = %s ( ", gate_type[c->type].c_str());
// // for (int j = 0; j < c->sz; j++) {
// // printf("%d ", c->to[j]);
// // }
// // puts(")");
// }
// printf("(assert (= false %s))\n", pvar(S->maxvar).c_str());
// printf("(check-sat)\n");
//printf("(get-model)\n");

0
acec.log Normal file
View File

View File

@ -46,8 +46,8 @@ bool Sweep::match_majority(int x) {
if (C[-v].outs <= 1) del[-v] = 2;
if (C[-w].outs <= 1) del[-w] = 2;
C[x][0] = a; C[x][1] = b;
C[x].push(C[-w][1]);
C[x][0] = -a; C[x][1] = -b;
C[x].push(-C[-w][1]);
C[x].type = Majority;
return true;
}
@ -163,7 +163,6 @@ void Sweep::match_FA() {
for(int i=1; i<=maxvar; i++) {
if (!C[i].sz || del[i] == 2) continue;
//printf("X: %d\n", i);
circuit *c = &C[i];
@ -347,7 +346,7 @@ void Sweep::to_dot_graph(const char* filename, int end_point) {
/**
*
*/
if(abs(c->to[j]) <= num_inputs) continue;
if(abs(c->to[j]) <= 14) continue;
sprintf(str, "A%d->A%d[arrowhead=%s]\n", abs(c->to[j]), u, !line_positive(c->to[j]) ? "dot" : "none");
file << str;
@ -365,6 +364,7 @@ void Sweep::to_dot_graph(const char* filename, int end_point) {
}
void Sweep::identify() {
del = new int[maxvar + 1];
for (int i = 1; i <= maxvar; i++) del[i] = 0;
recalculate_outs();
@ -373,21 +373,19 @@ void Sweep::identify() {
if (match_xor(i)) continue;
}
recalculate_outs();
// recalculate_outs();
for (int i = 1; i <= maxvar; i++) {
if (!C[i].sz || del[i] == 2) continue;
if (match_majority(i)) continue;
}
// for (int i = 1; i <= maxvar; i++) {
// if (!C[i].sz || del[i] == 2) continue;
// if (match_majority(i)) continue;
// }
recalculate_outs();
adjust_not();
match_HA();
//adjust_not();
//match_HA();
to_multi_input_gates();
match_FA();
//to_multi_input_gates();
//match_FA();
// printf("digraph \"graph\" {\n");
// for (int i = 1; i <= maxvar; i++) {
@ -411,8 +409,35 @@ void Sweep::identify() {
// }
// printf("}\n");
//to_dot_graph("graph1.dot", abs(C[maxvar].to[0]));
//to_dot_graph("graph2.dot", abs(C[maxvar].to[1]));
recalculate_outs();
// for (int i = 1; i <= maxvar; i++) {
// circuit *c = &C[i];
// if (!c->sz || del[i] == 2) continue;
// if (c->type == HA || c->type == FA)
// printf("( %d %d )", i * (C[i].neg ? -1 : 1), c->carrier);
// else
// printf("%d", i * (C[i].neg ? -1 : 1));
// printf(" = %s ( ", gate_type[c->type].c_str());
// for (int j = 0; j < c->sz; j++) {
// printf("%d ", c->to[j]);
// }
// puts(")");
// }
inv_C = new vec<int>[maxvar + 1];
for (int i = 1; i <= maxvar; i++) {
circuit *c = &C[i];
if (!c->sz || del[i] == 2) continue;
for (int j = 0; j < c->sz; j++) {
inv_C[abs(c->to[j])].push(i);
}
}
// to_dot_graph("graph1.dot", 291);
// to_dot_graph("graph2.dot", 175);
// recalculate_outs();
// for (int i = 1; i <= maxvar; i++) {
@ -420,7 +445,5 @@ void Sweep::identify() {
// if (del[i] == 2) continue;
// printf("%d : %d (outs)\n", i, c->outs);
// }
}

408
cms.log Normal file
View File

@ -0,0 +1,408 @@
-293 -292 0
-293 176 0
292 -176 293 0
-294 292 0
-294 -176 0
-292 176 294 0
289 231 292 0
-173 154 176 0
-289 -288 0
-289 287 0
288 -287 289 0
228 197 231 0
-173 172 0
-173 -157 0
-172 157 173 0
-151 124 154 0
-288 -285 0
-288 -242 0
285 242 288 0
-287 -286 0
-287 -249 0
286 249 287 0
225 208 228 0
-197 -196 0
-197 195 0
196 -195 197 0
-172 171 0
-172 -160 0
-171 160 172 0
-150 127 157 0
-151 150 0
-151 -127 0
-150 127 151 0
121 88 124 0
-285 -284 0
-285 283 0
284 -283 285 0
-242 -241 0
-242 240 0
241 -240 242 0
-286 -285 0
-286 -248 0
285 248 286 0
-249 -248 0
-249 -242 0
248 242 249 0
222 216 225 0
205 200 208 0
-196 -193 0
-196 -180 0
193 180 196 0
-195 -194 0
-195 -187 0
194 187 195 0
-171 170 0
-171 -163 0
-170 163 171 0
149 130 160 0
-150 -149 0
-150 -130 0
149 130 150 0
-119 116 127 0
-121 -120 0
-121 -115 0
120 115 121 0
85 78 88 0
-284 -281 0
-284 -255 0
281 255 284 0
-283 -282 0
-283 -262 0
282 262 283 0
-241 -238 0
-241 97 0
238 -97 241 0
-240 -239 0
-240 -232 0
239 232 240 0
245 193 248 0
-219 42 222 0
213 20 216 0
-205 -204 0
-205 203 0
204 -203 205 0
-41 27 200 0
190 31 193 0
-180 179 0
-180 -116 0
-179 116 180 0
-194 -193 0
-194 -186 0
193 186 194 0
-187 -186 0
-187 -180 0
186 180 187 0
-170 169 0
-170 -166 0
-169 166 170 0
-147 144 163 0
-149 -148 0
-149 -143 0
148 143 149 0
112 109 130 0
114 91 119 0
-116 106 0
-116 105 0
-106 -105 116 0
-120 -119 0
-120 116 0
119 -116 120 0
-115 -114 0
-115 -91 0
114 91 115 0
-82 81 85 0
75 48 78 0
-281 -280 0
-281 279 0
280 -279 281 0
-255 -254 0
-255 253 0
254 -253 255 0
-282 -281 0
-282 -261 0
281 261 282 0
-262 -261 0
-262 -255 0
261 255 262 0
-235 105 238 0
-97 96 0
-97 95 0
-96 -95 97 0
-239 -238 0
-239 -58 0
238 58 239 0
-232 97 0
-232 -58 0
-97 58 232 0
186 180 245 0
82 21 219 0
-42 5 0
-42 4 0
-5 -4 42 0
-213 -212 0
-213 211 0
212 -211 213 0
17 16 20 0
-204 63 0
-204 -31 0
-63 31 204 0
-203 -202 0
-203 -201 0
202 201 203 0
38 37 41 0
-27 26 0
-27 25 0
-26 -25 27 0
63 54 190 0
26 25 31 0
-179 -178 0
-179 -177 0
178 177 179 0
-183 62 186 0
-169 168 0
-169 167 0
-168 -167 169 0
-140 137 166 0
142 133 147 0
-144 12 0
-144 6 0
-12 -6 144 0
-148 -147 0
-148 144 0
147 -144 148 0
-143 -142 0
-143 -133 0
142 133 143 0
103 94 112 0
106 105 109 0
-114 -113 0
-114 -104 0
113 104 114 0
73 70 91 0
-106 12 0
-106 2 0
-12 -2 106 0
-105 6 0
-105 5 0
-6 -5 105 0
-82 12 0
-82 9 0
-12 -9 82 0
-81 -80 0
-81 -79 0
80 79 81 0
-75 -74 0
-75 -61 0
74 61 75 0
45 36 48 0
-280 -277 0
-280 -101 0
277 101 280 0
-279 -278 0
-279 -269 0
278 269 279 0
-254 251 0
-254 144 0
-251 -144 254 0
-253 -252 0
-253 -250 0
252 250 253 0
258 238 261 0
106 55 235 0
-96 15 0
-96 8 0
-15 -8 96 0
-95 13 0
-95 3 0
-13 -3 95 0
53 52 58 0
-21 11 0
-21 7 0
-11 -7 21 0
-212 67 0
-212 62 0
-67 -62 212 0
-211 -210 0
-211 -209 0
210 209 211 0
-17 10 0
-17 8 0
-10 -8 17 0
-16 14 0
-16 13 0
-14 -13 16 0
-63 5 0
-63 2 0
-5 -2 63 0
-202 54 0
-202 -31 0
-54 31 202 0
-201 63 0
-201 54 0
-63 -54 201 0
-38 15 0
-38 2 0
-15 -2 38 0
-37 6 0
-37 3 0
-6 -3 37 0
-26 8 0
-26 7 0
-8 -7 26 0
-25 13 0
-25 10 0
-13 -10 25 0
-54 53 0
-54 52 0
-53 -52 54 0
-178 105 0
-178 55 0
-105 -55 178 0
-177 106 0
-177 55 0
-106 -55 177 0
67 28 183 0
-62 15 0
-62 6 0
-15 -6 62 0
-168 12 0
-168 8 0
-12 -8 168 0
-167 13 0
-167 5 0
-13 -5 167 0
135 134 140 0
-137 12 0
-137 11 0
-12 -11 137 0
-142 -141 0
-142 -136 0
141 136 142 0
-101 98 133 0
-103 -102 0
-103 -97 0
102 97 103 0
-58 55 94 0
-113 -112 0
-113 -109 0
112 109 113 0
-104 -103 0
-104 -94 0
103 94 104 0
60 51 73 0
-67 66 70 0
-80 67 0
-80 -66 0
-67 66 80 0
-79 63 0
-79 62 0
-63 -62 79 0
-74 -73 0
-74 -70 0
73 70 74 0
-61 -60 0
-61 -51 0
60 51 61 0
-42 41 45 0
33 24 36 0
-277 -276 0
-277 275 0
276 -275 277 0
96 95 101 0
-278 -277 0
-278 -268 0
277 268 278 0
-269 -268 0
-269 -101 0
268 101 269 0
-251 137 0
-251 135 0
-137 -135 251 0
-252 251 0
-252 98 0
-251 -98 252 0
-250 144 0
-250 98 0
-144 -98 250 0
-97 58 258 0
-55 15 0
-55 11 0
-15 -11 55 0
-53 8 0
-53 3 0
-8 -3 53 0
-52 13 0
-52 7 0
-13 -7 52 0
-67 12 0
-67 4 0
-12 -4 67 0
-210 62 0
-210 28 0
-62 -28 210 0
-209 67 0
-209 28 0
-67 -28 209 0
-28 11 0
-28 3 0
-11 -3 28 0
-135 8 0
-135 5 0
-8 -5 135 0
-134 15 0
-134 13 0
-15 -13 134 0
-141 -140 0
-141 137 0
140 -137 141 0
-136 135 0
-136 134 0
-135 -134 136 0
-98 11 0
-98 5 0
-11 -5 98 0
-102 -101 0
-102 98 0
101 -98 102 0
-60 -59 0
-60 -54 0
59 54 60 0
-31 28 51 0
63 62 66 0
-33 -32 0
-33 -27 0
32 27 33 0
-21 20 24 0
-276 -273 0
-276 134 0
273 -134 276 0
-275 -274 0
-275 -270 0
274 270 275 0
-265 251 268 0
-59 -58 0
-59 55 0
58 -55 59 0
-32 -31 0
-32 28 0
31 -28 32 0
137 135 273 0
-274 -273 0
-274 169 0
273 -169 274 0
-270 169 0
-270 134 0
-169 -134 270 0
144 98 265 0
-293 -294 0
293 294 0
-251 252 0
251 -252 0
-253 255 0
253 -255 0
-254 266 0
254 -266 0
-176 292 0
176 -292 0
c Solver::solve( )

1
f4cpp

@ -1 +0,0 @@
Subproject commit 645458ca02450215bd0e891fc3081d8cbc79c51f

291
fraig.cpp Normal file
View File

@ -0,0 +1,291 @@
#include "sweep.hpp"
#include "circuit.hpp"
#include "src/cadical.hpp"
extern "C" {
#include "aiger.h"
}
void Sweep::propagate(int* input, int* result) {
std::queue<int> q;
for(int i=0; i<aig->num_inputs; i++) {
result[aig->inputs[i].lit] = input[i];
result[aig->inputs[i].lit+1] = !input[i];
q.push(aiger_lit2var(aig->inputs[i].lit));
}
static int* topo_counter = new int[maxvar + 1];
memset(topo_counter, 0, (maxvar + 1) * sizeof(int));
while(!q.empty()) {
int cur = q.front();
q.pop();
for(auto& edge : graph.edge[cur]) {
int v = edge.to;
topo_counter[v]++;
if(topo_counter[v] == 2) {
q.push(v);
//cout << "redge: " << v << " " << graph.redge[v][0].to << " " << graph.redge[v][1].to << endl;
Graph::Edge rhs0_edge = graph.redge[v][0];
int rhs0 = aiger_var2lit(rhs0_edge.to);
Graph::Edge rhs1_edge = graph.redge[v][1];
int rhs1 = aiger_var2lit(rhs1_edge.to);
result[aiger_var2lit(v)] = result[rhs0 + rhs0_edge.neg] & result[rhs1 + rhs1_edge.neg];
result[aiger_var2lit(v)+1] = !result[aiger_var2lit(v)];
//cout << "var: " << aiger_var2lit(v) << " = " << rhs0 + rhs0_edge.neg << " " << rhs1 + rhs1_edge.neg << " : " << result[aiger_var2lit(v)] << endl;
}
}
}
}
void Sweep::circuit_propagate(int *input, int* result) {
std::queue<int> q;
for(int i=1; i<=aig->num_inputs; i++) {
result[i] = input[i - 1];
q.push(i);
// printf("%d %d\n", i, result[i]);
}
for (int i = 1; i <= maxvar; i++) seen[i] = 0;
while(!q.empty()) {
int x = q.front();
q.pop();
for (int i = 0; i < inv_C[x].size(); i++) {
int v = inv_C[x][i];
seen[v]++;
if (seen[v] == C[v].sz) {
q.push(v);
if (C[v].type == And) {
result[v] = 1;
for (int j = 0; j < C[v].sz; j++) {
int x = C[v][j];
result[v] &= x > 0 ? result[abs(x)] : !result[abs(x)];
}
}
else if (C[v].type == Xor) {
result[v] = 0;
for (int j = 0; j < C[v].sz; j++) {
int x = C[v][j];
result[v] ^= x > 0 ? result[abs(x)] : !result[abs(x)];
}
}
else if (C[v].type == Majority) {
int s[2] = {0, 0};
for (int j = 0; j < C[v].sz; j++) {
int x = C[v][j];
s[x > 0 ? result[abs(x)] : !result[abs(x)]]++;
}
result[v] = s[1] > s[0] ? 1 : 0;
}
else if (C[v].type == HA) {
assert(C[v].sz == 2);
int s = 0;
for (int j = 0; j < C[v].sz; j++) {
int x = C[v][j];
s += x > 0 ? result[abs(x)] : !result[abs(x)];
}
result[v] = s % 2;
result[abs(C[v].carrier)] = s >> 1;
if (C[v].carrier < 0) result[abs(C[v].carrier)] = !result[abs(C[v].carrier)];
q.push(abs(C[v].carrier));
}
else if (C[v].type == FA) {
assert(C[v].sz == 3);
int s = 0;
for (int j = 0; j < C[v].sz; j++) {
int x = C[v][j];
s += x > 0 ? result[abs(x)] : !result[abs(x)];
}
result[v] = s % 2;
result[abs(C[v].carrier)] = s >> 1;
if (C[v].carrier < 0) result[abs(C[v].carrier)] = !result[abs(C[v].carrier)];
q.push(abs(C[v].carrier));
}
if (C[v].neg) result[v] = !result[v];
// printf("%d %d\n", v, result[v]);
}
}
}
}
// int Sweep::seen_cut_points(aiger* aig, int x, int y) {
// std::queue<int> q;
// int *used =
// x = x & 1 ? -aiger_lit2var(x) : aiger_lit2var(x);
// y = y & 1 ? -aiger_lit2var(y) : aiger_lit2var(y);
// if (!used[abs(x)])
// q.push(abs(x)), used[abs(x)] = true;
// if(!used[abs(y)])
// q.push(abs(y)), used[abs(y)] = true;
// }
bool Sweep::check_var_equal(int x, int y, int assume) {
std::queue<int> q;
x = x & 1 ? -aiger_lit2var(x) : aiger_lit2var(x);
y = y & 1 ? -aiger_lit2var(y) : aiger_lit2var(y);
if (!used[abs(x)])
q.push(abs(x)), used[abs(x)] = true;
if(!used[abs(y)])
q.push(abs(y)), used[abs(y)] = true;
int can_del = 2;
while(!q.empty()) {
int u = q.front();
q.pop();
/*
(u => a & b) <=> ~u | (a & b) <=> (~u | a) & (~u | b)
(u <= a & b) <=> ~(a & b) | u <=> ~a | ~b | u
*/
if(graph.redge[u].size() == 0) continue;
int a = graph.redge[u][0].to;
int b = graph.redge[u][1].to;
//cout << "AND " << aiger_var2lit(u) << " " << (aiger_var2lit(a) + graph.redge[u][0].neg) << " " << (aiger_var2lit(b) + graph.redge[u][1].neg) << endl;
if(graph.redge[u][0].neg) a = -a;
if(graph.redge[u][1].neg) b = -b;
solver->add(-u), solver->add( a), solver->add(0);
solver->add(-u), solver->add( b), solver->add(0);
solver->add(-a), solver->add(-b), solver->add(u), solver->add(0);
a = abs(a), b = abs(b);
if(!used[a])
q.push(a), used[a] = true, can_del++;
if(!used[b])
q.push(b), used[b] = true, can_del++;
}
//Assume -NV
solver->add(-assume), solver->add(-x), solver->add(-y), solver->add(0);
solver->add(-assume), solver->add(x), solver->add(y), solver->add(0);
solver->assume (assume);
int result = solver->solve ();
// if (result == 20) printf("can delete %d vars\n", can_del);
return result == 20;
}
bool Sweep::check_constant(int x, int val) {
std::queue<int> q;
x = x & 1 ? -aiger_lit2var(x) : aiger_lit2var(x);
if (!used[abs(x)]) q.push(abs(x)), used[abs(x)] = true;
while(!q.empty()) {
int u = q.front();
q.pop();
if(graph.redge[u].size() == 0) continue;
int a = graph.redge[u][0].to;
int b = graph.redge[u][1].to;
if(graph.redge[u][0].neg) a = -a;
if(graph.redge[u][1].neg) b = -b;
solver->add(-u), solver->add( a), solver->add(0);
solver->add(-u), solver->add( b), solver->add(0);
solver->add(-a), solver->add(-b), solver->add(u), solver->add(0);
a = abs(a), b = abs(b);
if(!used[a]) q.push(a), used[a] = true;
if(!used[b]) q.push(b), used[b] = true;
}
solver->assume (x * (val ? -1 : 1));
int result = solver->solve();
return result == 20;
}
void Sweep::random_simulation() {
int* result = new int[2 * maxvar + 2];
int* circuit_res = new int[maxvar + 1];
int* input_vector = new int[aig->num_inputs];
for(int i=0; i<rounds; i++)
{
if (i % 10000 == 0) printf("================================= %d ===========================\n", i);
for(int i=0; i<aig->num_inputs; i++) {
input_vector[i] = rand() % 2;
}
for (int i=0; i<det_v_sz; i++) {
if (abs(det_v[i]) <= aig->num_inputs) input_vector[abs(det_v[i]) - 1] = det_v[i] > 0 ? 1 : 0;
}
memset(result, -1, (2 * maxvar + 2) * sizeof(int));
memset(circuit_res, -1, (maxvar + 1) * sizeof(int));
propagate(input_vector, result);
circuit_propagate(input_vector, circuit_res);
if (result[output] != !circuit_res[output >> 1]) {
printf("wrong %d %d %d\n", output, !result[output], circuit_res[output >> 1]);
for (int i = 1; i <= maxvar; i++) {
if (circuit_res[i] == -1) continue;
if (result[i * 2] != circuit_res[i]) printf("%d %d %d\n", i, result[i * 2], circuit_res[i]);
printf("%d %d\n", i, result[i << 1]);
}
return;
}
std::vector<std::vector<int>> tmp;
for(auto& clas : classes) {
std::vector<int> v0;
std::vector<int> v1;
for(auto &v : clas) {
if(result[v]) v1.push_back(v), sum_pos[v] += 1;
else v0.push_back(v), sum_neg[v] += 1;
}
if(v0.size() > 1) tmp.push_back(v0);
if(v1.size() > 1) tmp.push_back(v1);
}
classes = tmp;
}
printf("c Number of equivalent classes: %d\n", classes.size());
// printf("classes: \n");
// for(auto& clas : classes) {
// int lit = clas[0];
// printf("[ ");
// for(auto &v : clas) {
// printf("%d ", v);
// }
// printf("] %d %d\n", sum_pos[lit], sum_neg[lit]);
// }
}
void Sweep::simulation() {
//printf("c =================================== detect =================================\n");
seen = new int[maxvar + 1];
classes.push_back(std::vector<int>());
for(int i=2; i<=maxvar*2+1; i++) {
classes[0].push_back(i);
}
random_simulation();
}

104
hCaD_V2/BUILD.md Normal file
View File

@ -0,0 +1,104 @@
# CaDiCaL Build
Use `./configure && make` to configure and build `cadical` in the default
`build` sub-directory.
This will also build the library `libcadical.a` as well as the model based
tester `mobical`:
build/cadical
build/mobical
build/libcadical.a
The header file of the library is in
src/cadical.hpp
The build process requires GNU make. Using the generated `makefile` with
GNU make compiles separate object files, which can be cached (for instance
with `ccache`). In order to force parallel build you can use the '-j'
option either for 'configure' or with 'make'. If the environment variable
'MAKEFLAGS' is set, e.g., 'MAKEFLAGS=-j ./configure', the same effect
is achieved and the generated makefile will use those flags.
Options
-------
You might want to check out options of `./configure -h`, such as
./configure -c # include assertion checking code
./configure -l # include code to really see what the solver is doing
./configure -a # both above and in addition `-g` for debugging.
You can easily use multiple build directories, e.g.,
mkdir debug; cd debug; ../configure -g; make
which compiles and builds a debugging version in the sub-directory `debug`,
since `-g` was specified as parameter to `configure`. The object files,
the library and the binaries are all independent of those in the default
build directory `build`.
All source files reside in the `src` directory. The library `libcadical.a`
is compiled from all the `.cpp` files except `cadical.cpp` and
`mobical.cpp`, which provide the applications, i.e., the stand alone solver
`cadical` and the model based tester `mobical`.
Manual Build
------------
If you can not or do not want to rely on our `configure` script nor on our
build system based on GNU `make`, then this is easily doable as follows.
mkdir build
cd build
for f in ../src/*.cpp; do g++ -O3 -DNDEBUG -DNBUILD -c $f; done
ar rc libcadical.a `ls *.o | grep -v ical.o`
g++ -o cadical cadical.o -L. -lcadical
g++ -o mobical mobical.o -L. -lcadical
Note that application object files are excluded from the library.
Of course you can use different compilation options as well.
Since `build.hpp` is not generated in this flow the `-DNBUILD` flag is
necessary though, which avoids dependency of `version.cpp` on `build.hpp`.
Consequently you will only get very basic version information compiled into
the library and binaries (guaranteed is in essence just the version number
of the library).
And if you really do not care about compilation time nor caching and just
want to build the solver once manually then the following also works.
g++ -O3 -DNDEBUG -DNBUILD -o cadical `ls *.cpp | grep -v mobical`
Further note that the `configure` script provides some feature checks and
might generate additional compiler flags necessary for compilation. You
might need to set those yourself or just use a modern C++11 compiler.
This manual build process using object files is fast enough in combination
with caching solutions such as `ccache`. But it lacks the ability of our
GNU make solution to run compilation in parallel without additional parallel
process scheduling solutions.
Cross-Compilation
-----------------
We have preliminary support for cross-compilation using MinGW32 (only
tested for a Linux compilation host and Windows-64 target host at this
point).
There are two steps necessary to make this work. First make
sure to be able to execute binaries compiled with the cross-compiler
directly. For instance in order to use `wine` to execute the binaries
on Linux you might want to look into the `binfmt_misc` module and
registering the appropriate interpreter for `DOSWin`. As second step
you simply tell the `configure` script to use the cross-compiler.
CXXFLAGS=-static CXX=i686-w64-mingw32-g++ ./configure
Note the use of '-static', which was necessary for me since by default
`wine` did not find `libstdc++` if dynamically linked.

4
hCaD_V2/CONTRIBUTING Normal file
View File

@ -0,0 +1,4 @@
At this point we want to keep complete ownership in one hand
to particularly avoid any additional co-authorship claims.
Thus please refrain from generating pull requests. Use the issue
tracker or send email to 'biere@jku.at' instead.

22
hCaD_V2/LICENSE Normal file
View File

@ -0,0 +1,22 @@
MIT License
Copyright (c) 2016-2021 Armin Biere, Johannes Kepler University Linz, Austria
Copyright (c) 2020-2021 Mathias Fleury, Johannes Kepler University Linz, Austria
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

54
hCaD_V2/README.md Normal file
View File

@ -0,0 +1,54 @@
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![Build Status](https://travis-ci.com/arminbiere/cadical.svg?branch=master)](https://travis-ci.com/arminbiere/cadical)
CaDiCaL Simplified Satisfiability Solver
===============================================================================
The goal of the development of CaDiCaL was to obtain a CDCL solver,
which is easy to understand and change, while at the same time not being
much slower than other state-of-the-art CDCL solvers.
Originally we wanted to also radically simplify the design and internal data
structures, but that goal was only achieved partially, at least for instance
compared to Lingeling.
However, the code is much better documented and CaDiCaL actually became in
general faster than Lingeling even though it is missing some preprocessors
(mostly parity and cardinality constraint reasoning), which would be crucial
to solve certain instances.
Use `./configure && make` to configure and build `cadical` and the library
`libcadical.a` in the default `build` sub-directory. The header file of
the library is [`src/cadical.hpp`](src/cadical.hpp) and includes an example
for API usage.
See [`BUILD.md`](BUILD.md) for options and more details related to the build
process and [`test/README.md`](test/README.md) for testing the library and
the solver.
The solver has the following usage `cadical [ dimacs [ proof ] ]`.
See `cadical -h` for more options.
If you want to cite CaDiCaL please use the solver description in the
latest SAT competition proceedings:
<pre>
@inproceedings{BiereFazekasFleuryHeisinger-SAT-Competition-2020-solvers,
author = {Armin Biere and Katalin Fazekas and Mathias Fleury and Maximillian Heisinger},
title = {{CaDiCaL}, {Kissat}, {Paracooba}, {Plingeling} and {Treengeling}
Entering the {SAT Competition 2020}},
pages = {51--53},
editor = {Tomas Balyo and Nils Froleyks and Marijn Heule and
Markus Iser and Matti J{\"a}rvisalo and Martin Suda},
booktitle = {Proc.~of {SAT Competition} 2020 -- Solver and Benchmark Descriptions},
volume = {B-2020-1},
series = {Department of Computer Science Report Series B},
publisher = {University of Helsinki},
year = 2020,
}
</pre>
You might also find more information on CaDiCaL at <http://fmv.jku.at/cadical>.
Armin Biere

1
hCaD_V2/VERSION Normal file
View File

@ -0,0 +1 @@
1.4.1

View File

@ -0,0 +1,2 @@
#!/bin/sh
exec ./cadical --target=0 --walk=false $1 $2/proof.out

494
hCaD_V2/configure vendored Executable file
View File

@ -0,0 +1,494 @@
#!/bin/sh
#--------------------------------------------------------------------------#
# Run './configure' to produce a 'makefile' in the 'build' sub-directory or
# in any immediate sub-directory different from the 'src', 'scripts' and
# 'test' directories.
#--------------------------------------------------------------------------#
rm -f configure.log
#--------------------------------------------------------------------------#
# Common default options.
all=no
debug=no
logging=no
check=no
competition=no
coverage=no
profile=no
contracts=yes
tracing=yes
unlocked=yes
pedantic=no
options=""
quiet=no
m32=no
#--------------------------------------------------------------------------#
if [ -f ./scripts/colors.sh ]
then
. ./scripts/colors.sh
elif [ -f ../scripts/colors.sh ]
then
. ../scripts/colors.sh
else
BAD=""
HILITE=""
BOLD=""
NORMAL=""
fi
die () {
if [ -f configure.log ]
then
checklog=" (check also 'configure.log')"
else
checklog=""
fi
cecho "${BOLD}configure:${NORMAL} ${BAD}error:${NORMAL} $*${checklog}"
exit 1
}
msg () {
cecho "${BOLD}configure:${NORMAL} $*"
}
# if we can find the 'color.sh' script source it and overwrite color codes
for dir in . ..
do
[ -f $dir/scripts/colors.sh ] || continue
. $dir/scripts/colors.sh || exit 1
break
done
#--------------------------------------------------------------------------#
# Parse and handle command line options.
usage () {
cat << EOF
usage: configure [ <option> ... ]
where '<option>' is one of the following
-h|--help print this command line summary
-g|--debug compile with debugging information
-c|--check compile with assertion checking (default for '-g')
-l|--log[ging] include logging code (but disabled by default)
-a|--all short cut for all above, e.g., '-g -l' (thus also '-c')
-q|--quiet exclude message and profiling code (logging too)
-p|--pedantic add '--pedantic' and '-Werror' compilation flag
-s|--symbols add '-ggdb3' (even for optimized compilation)
--coverage compile with '-ftest-coverage -fprofile-arcs' for 'gcov'
--profile compile with '-pg' to profile with 'gprof'
--no-contracts compile without API contract checking code
--no-tracing compile without API call tracing code
--competition configure for the competition
('--quiet', '--no-contracts', '--no-tracing')
-f... pass '-f<option>[=<val>]' options to the makefile
-m32 pass '-m32' to the compiler (compile for 32 bit)
-ggdb3 pass '-ggdb3' to makefile (like '-s')
-O|-O[123] pass '-O' or '-O[123]' to the makefile
-static... pass '-static...' option to makefile
The environment variable CXX can be used to set a different C++
compiler than the default 'g++'. Similarly you can add additional
compilation options by setting CXXFLAGS. For example
CXX=clang++ CXXFLAGS=-fPIC ./configure
will enforce to use 'clang++' as C++ compiler and also produce
position independent code. In order to be shell independent we also
allow to have the following form. Thus for instance
./configure CXX="g++-8" CXXFLAGS="-fPIC -fsanitize=address"
will have the same effect as
CXX="g++-8" ./configure -fPIC -fsanitize=address
The following configuration options might be usefull during porting the
code to a new platform and are usually not necessary to change.
--no-unlocked force compilation without unlocked IO
EOF
exit 0
}
#--------------------------------------------------------------------------#
while [ $# -gt 0 ]
do
case $1 in
-h|--help) usage;;
-a|--all) all=yes;;
-g|--debug) debug=yes;;
-c|--check) check=yes;;
-l|--log|--logging) logging=yes;;
-p|--pedantic) pedantic=yes;;
-q|--quiet) quiet=yes;;
--no-contracts | --no-contract) contracts=no;;
--no-tracing | --no-trace) tracing=no;;
--coverage) coverage=yes;;
--profile) profile=yes;;
--competition) competition=yes;;
--no-unlocked) unlocked=no;;
-m32) options="$options $1";m32=yes;;
-f*|-ggdb3|-O|-O1|-O2|-O3) options="$options $1";;
-s|--symbols) options="$options -ggdb3";;
-static*) options="$options $1";;
CXX=*)
CXX="`expr \"$1\" : 'CXX=\(.*\)'`"
;;
CXXFLAGS=*)
CXXFLAGS="`expr \"$1\" : 'CXXFLAGS=\(.*\)'`"
;;
*) die "invalid option '$1' (try '-h')";;
esac
shift
done
#--------------------------------------------------------------------------#
if [ $quiet = yes ]
then
[ $logging = yes ] && die "can not combine '-q' with '-l'"
fi
if [ $all = yes ]
then
[ $check = yes ] && die "'-a' subsumes '-c'"
[ $debug = yes ] && die "'-a' subsumes '-g'"
[ $logging = yes ] && die "'-a' subsumes '-l'"
check=yes
debug=yes
logging=yes
elif [ $debug = yes ]
then
[ $check = yes ] && die "'-g' subsumes '-c'"
check=yes
fi
#--------------------------------------------------------------------------#
# Generate and enter 'build' directory if not already in sub-directory.
build_in_default_build_sub_directory () {
if [ -d build ]
then
msg "reusing default 'build' directory"
else
mkdir build 2>/dev/null || \
die "failed to generate 'build' directory"
msg "making default 'build' directory"
fi
cd build
msg "building in default ${HILITE}'`pwd`'${NORMAL}"
build=build
}
if [ -f configure -a -f makefile.in -a -d src ]
then
root="`pwd`"
build_in_default_build_sub_directory
elif [ -f ../configure -a -f ../makefile.in -a -d ../src ]
then
cwd="`pwd`"
build=`basename "$cwd"`
root=`dirname "$cwd"`
case x"$build" in
xsrc|xtest|xscripts)
cd ..
build_in_default_build_sub_directory
;;
*)
msg "building in ${HILITE}'$build'${NORMAL} sub-directory"
;;
esac
else
die "call 'configure' from root of CaDiCaL source or a sub-directory"
fi
msg "root directory '$root'"
#--------------------------------------------------------------------------#
src="$root/src"
if [ -d "$src" ]
then
msg "source directory '$src'"
else
die "could not find source director '$src'"
fi
#--------------------------------------------------------------------------#
# Prepare '@CXX@' and '@CXXFLAGS@' parameters for 'makefile.in'
[ x"$CXX" = x ] && CXX=g++
[ x"$CXXFLAGS" = x ] || CXXFLAGS="$CXXFLAGS "
case x"$CXX" in
x*g++*|x*clang++*) CXXFLAGS="${CXXFLAGS}-Wall -Wextra";;
*) CXXFLAGS="${CXXFLAGS}-W";;
esac
if [ $debug = yes ]
then
CXXFLAGS="$CXXFLAGS -g"
else
case x"$CXX" in
x*g++*|x*clang++*) CXXFLAGS="$CXXFLAGS -O3";;
*) CXXFLAGS="$CXXFLAGS -O";;
esac
fi
if [ $m32 = yes ]
then
case x"$CXX" in
x*g++*)
options="$options -mpc64"
msg "forcing portable 64-bit FPU mode ('-mpc64') for '$CXX'"
;;
esac
fi
if [ $competition = yes ]
then
quiet=yes
contracts=no
tracing=no
fi
[ $check = no ] && CXXFLAGS="$CXXFLAGS -DNDEBUG"
[ $logging = yes ] && CXXFLAGS="$CXXFLAGS -DLOGGING"
[ $quiet = yes ] && CXXFLAGS="$CXXFLAGS -DQUIET"
[ $profile = yes ] && CXXFLAGS="$CXXFLAGS -pg"
[ $coverage = yes ] && CXXFLAGS="$CXXFLAGS -ftest-coverage -fprofile-arcs"
if [ $pedantic = yes ]
then
CXXFLAGS="$CXXFLAGS --pedantic -Werror -std=c++11"
case x"$CXX" in
x*g++*|x*clang++*) CXXFLAGS="${CXXFLAGS} -Wp,-D_GLIBCXX_ASSERTIONS"
;;
esac
fi
[ $contracts = no ] && CXXFLAGS="$CXXFLAGS -DNCONTRACTS"
[ $tracing = no ] && CXXFLAGS="$CXXFLAGS -DNTRACING"
CXXFLAGS="$CXXFLAGS$options"
#--------------------------------------------------------------------------#
case x"$CXX" in
x*g++* | x*clang++*) WERROR="-Werror -pedantic";;
*) WERROR="";;
esac
# Check that compilation flags work.
feature=./configure-hello-world
cat <<EOF > $feature.cpp
#include <iostream>
int main () { std::cout << "hello world" << std::endl; }
EOF
if $CXX $CXXFLAGS $WERROR -o $feature.exe $feature.cpp 2>>configure.log
then
if [ ! "`$feature.exe 2>>configure.log|tr -d '\r'`" = "hello world" ]
then
die "execution of '$feature.exe' failed"
fi
else
die "test compilation '$feature.cpp'"
fi
#--------------------------------------------------------------------------#
# Since C99/C++11 is becoming the standard newer versions of 'g++' (7.3 for
# instance) discourage certain GCC extensions, particularly the GCC version
# of variadic macros, if the same concept exists in the standard. This
# forced us to replace all GCC style 'ARGS...' macros with '...' and
# '__VA_ARGS__'. Otherwise compiling the library with '--pedantic -Werror'
# would fail (configuration flag '-p'). However, older versions of 'gcc'
# (such as 4.8) would disallow these new forms of macros unless we
# explicitly enforce the new standard with '-std=c++11'. Here we try to
# figure out whether we need that flag. In earlier versions we used
# '-std=c++0x' but due to issues with older MacOS builds we switched to
# '-std=c++11' instead.
feature=./configure-requires-c++11
cat <<EOF > $feature.cpp
#include <cstdio>
#include <vector>
// old variadic macro usage 'ARGS...' / '#ARGS' discouraged in g++-7...
// new variadic macro usage '...' / '__VA_ARGS__' available in C99/C++11
//
#define MACRO(FMT, ...) printf (FMT "\n", __VA_ARGS__)
// we use ranged for loops which became available in gcc 4.6 and for
// the gcc 4.6 as well as 4.8 requires '-std=c++11' too.
//
unsigned f (const std::vector<unsigned> & a) {
unsigned res = 0;
for (auto i : a) res += i;
return res;
}
int main () { MACRO ("%d", 42); return 0; }
EOF
if $CXX $CXXFLAGS $WERROR -o $feature.exe $feature.cpp 2>>configure.log
then
if [ "`$feature.exe 2>>configure.log|tr -d '\r'`" = 42 ]
then
msg "compiler supports all required C99/C++11 extensions"
else
die "checking compilation without '-std=c++11' failed"
fi
else
CXXFLAGS="$CXXFLAGS -std=c++11"
if $CXX $CXXFLAGS -o $feature.exe $feature.cpp 2>>configure.log
then
if [ "`$feature.exe 2>>configure.log|tr -d '\r'`" = 42 ]
then
msg "using '-std=c++11' for all required C99/C++11 extensions"
else
die "checking compilation with '-std=c++11' failed"
fi
else
die "compiler does not support C99/C++11 even with '-std=c++11'"
fi
fi
#--------------------------------------------------------------------------#
# Unlocked IO is much faster but not necessarily supported.
if [ $unlocked = yes ]
then
feature=./configure-have-unlocked-io
cat <<EOF > $feature.cpp
#include <cstdio>
int main () {
const char * path = "$feature.log";
FILE * file = fopen (path, "w");
if (!file) return 1;
if (putc_unlocked (42, file) != 42) return 1;
if (fclose (file)) return 1;
file = fopen (path, "r");
if (!file) return 1;
if (getc_unlocked (file) != 42) return 1;
if (fclose (file)) return 1;
return 0;
}
EOF
if $CXX $CXXFLAGS -o $feature.exe $feature.cpp 2>>configure.log
then
if $feature.exe
then
msg "unlocked IO with '{putc,getc}_unlocked' seems to work"
else
msg "not using unlocked IO (running '$feature.exe' failed)"
unlocked=no
fi
else
msg "not using unlocked IO (failed to compile '$feature.cpp')"
unlocked=no
fi
else
msg "not using unlocked IO (since '--no-unlocked' specified)"
fi
[ $unlocked = no ] && CXXFLAGS="$CXXFLAGS -DNUNLOCKED"
#--------------------------------------------------------------------------#
# Instantiate '../makefile.in' template to produce 'makefile' in 'build'.
msg "compiling with ${HILITE}'$CXX $CXXFLAGS'${NORMAL}"
rm -f makefile
sed \
-e "2c\\
# This 'makefile' is generated from '../makefile.in'." \
-e "s,@CXX@,$CXX," \
-e "s#@CXXFLAGS@#$CXXFLAGS#" \
../makefile.in > makefile
msg "generated '$build/makefile' from '../makefile.in'"
#--------------------------------------------------------------------------#
build="`pwd`"
makefile="`dirname "$build"`/makefile"
cat <<EOF > "$makefile"
CADICALBUILD=$build
all:
\$(MAKE) -C "\$(CADICALBUILD)"
clean:
@if [ -d "\$(CADICALBUILD)" ]; \\
then \\
if [ -f "\$(CADICALBUILD)"/makefile ]; \\
then \\
touch "\$(CADICALBUILD)"/build.hpp; \\
\$(MAKE) -C "\$(CADICALBUILD)" clean; \\
fi; \\
rm -rf "\$(CADICALBUILD)"; \\
fi
rm -f "$src/makefile"
rm -f "$makefile"
test:
\$(MAKE) -C "\$(CADICALBUILD)" test
cadical:
\$(MAKE) -C "\$(CADICALBUILD)" cadical
mobical:
\$(MAKE) -C "\$(CADICALBUILD)" mobical
update:
\$(MAKE) -C "\$(CADICALBUILD)" update
.PHONY: all cadical clean mobical test update
EOF
msg "generated '../makefile' as proxy to ..."
msg "... '$build/makefile'"
#--------------------------------------------------------------------------#
if [ -f $src/makefile ]
then
msg "removing '$src/makefile'"
rm -f $src/makefile
fi
msg "linking '$root/makefile'"
ln -s $root/makefile $src/makefile
#--------------------------------------------------------------------------#
msg "now run ${HILITE}'make'${NORMAL} to compile CaDiCaL"
msg "optionally run 'make test'"

24
hCaD_V2/makefile Normal file
View File

@ -0,0 +1,24 @@
CADICALBUILD=/home/chenzh/experiments/cec/equal/hCaD_V2/build
all:
$(MAKE) -C "$(CADICALBUILD)"
clean:
@if [ -d "$(CADICALBUILD)" ]; \
then \
if [ -f "$(CADICALBUILD)"/makefile ]; \
then \
touch "$(CADICALBUILD)"/build.hpp; \
$(MAKE) -C "$(CADICALBUILD)" clean; \
fi; \
rm -rf "$(CADICALBUILD)"; \
fi
rm -f "/home/chenzh/experiments/cec/equal/hCaD_V2/src/makefile"
rm -f "/home/chenzh/experiments/cec/equal/hCaD_V2/makefile"
test:
$(MAKE) -C "$(CADICALBUILD)" test
cadical:
$(MAKE) -C "$(CADICALBUILD)" cadical
mobical:
$(MAKE) -C "$(CADICALBUILD)" mobical
update:
$(MAKE) -C "$(CADICALBUILD)" update
.PHONY: all cadical clean mobical test update

84
hCaD_V2/makefile.in Normal file
View File

@ -0,0 +1,84 @@
#==========================================================================#
# This is a 'makefile.in' template with '@CXX@' and '@CXXFLAGS@' parameters.
# This makefile requires GNU make.
#==========================================================================#
# The '../scripts/make-build-header.sh' script searches for the next two
# lines to figure out the compiler and compilation flags. This information
# is then used to generate corresponding macros in 'build.hpp'.
CXX=@CXX@
CXXFLAGS=@CXXFLAGS@
############################################################################
# It is usually not necessary to change anything below this line! #
############################################################################
APP=cadical.cpp mobical.cpp
SRC=$(sort $(wildcard ../src/*.cpp))
SUB=$(subst ../src/,,$(SRC))
LIB=$(filter-out $(APP),$(SUB))
OBJ=$(LIB:.cpp=.o)
DIR=../$(shell pwd|sed -e 's,.*/,,')
COMPILE=$(CXX) $(CXXFLAGS) -I$(DIR)
#--------------------------------------------------------------------------#
all: libcadical.a cadical mobical
#--------------------------------------------------------------------------#
.SUFFIXES: .cpp .o
%.o: ../src/%.cpp ../src/*.hpp makefile
$(COMPILE) -c $<
#--------------------------------------------------------------------------#
# Application binaries (the stand alone solver 'cadical' and the model based
# tester 'mobical') and the library are the main build targets.
cadical: cadical.o libcadical.a makefile
$(COMPILE) -o $@ $< -L. -lcadical
mobical: mobical.o libcadical.a makefile
$(COMPILE) -o $@ $< -L. -lcadical
libcadical.a: $(OBJ) makefile
ar rc $@ $(OBJ)
#--------------------------------------------------------------------------#
# Note that 'build.hpp' is generated and resides in the build directory.
build.hpp: always
../scripts/make-build-header.sh > build.hpp
version.o: build.hpp
update:
../scripts/update-version.sh
#--------------------------------------------------------------------------#
# These two 'C' interfaces include '.h' headers and thus require explicitly
# defined additional dependencies.
ccadical.o: ../src/ccadical.h
ipasir.o: ../src/ipasir.h ../src/ccadical.h
#--------------------------------------------------------------------------#
analyze: all
$(COMPILE) --analyze ../src/*.cpp
clean:
rm -f *.o *.a cadical mobical makefile build.hpp
rm -f *.gcda *.gcno *.gcov gmon.out
test: all
CADICALBUILD="$(DIR)" $(MAKE) -j1 -C ../test
#--------------------------------------------------------------------------#
.PHONY: all always analyze clean test update

42
hCaD_V2/scripts/README.md Normal file
View File

@ -0,0 +1,42 @@
# CaDiCaL Scripts
Scripts needed for the build process
./make-build-header.sh # generates 'build.hpp'
./get-git-id.sh # get GIT id (needed by 'make-build-header.sh')
./update-version.sh # synchronizes VERSION in '../src/version.cpp'
and a script which builds and tests all configurations
./build-and-test-all-configurations.sh
CXX=clang++ ./build-and-test-all-configurations.sh
CXX=g++-4.8 ./build-and-test-all-configurations.sh
where as the code shows the compiler (default `g++`) is specified through
the environment variable `CXX` (as for `../configure`). Then there are
scripts for producing a source release
./make-src-release.sh # archive as 'cadical-VERSION-GITID.tar.xz'
./prepare-sc2021-submission.sh # star-exec format for SAT competition
and scripts for testing and debugging
./generate-embedded-options-default-list.sh # in 'c --opt=val' format
./generate-options-range-list.sh # 'cnfuzz' option file format
./run-cadical-and-check-proof.sh # wrapper to check proofs too
./run-simplifier-and-extend-solution.sh # to check simplifier
./extend-solution.sh # called by previous script
a script to check whether all options are actually used
./check-options-occur.sh
a script to update the example in the `../src/cadical.hpp` header
./update-example-in-cadical-header-file.sh
and finally a script to normalize white space of the source code
./normalize-white-space.sh
The `color.sh` script is used by the `run.sh` scripts in `test`.

View File

@ -0,0 +1,115 @@
#!/bin/sh
. `dirname $0`/colors.sh || exit 1
############################################################################
die () {
echo "build-and-test-all-configurations.sh: ${BAD}error${NORMAL}: $*" 1>&2
exit 1
}
############################################################################
if [ -f configure ]
then
configure="./configure"
makeoptions=""
elif [ -f ../configure ]
then
configure="../configure"
makeoptions=" -C .."
else
die "Can not find 'configure'."
fi
if [ "$CXX" = "" ]
then
environment=""
else
environment="CXX=$CXX "
fi
if [ ! "$CXXFLAGS" = "" ]
then
[ "$environment" = "" ] || environment="$environment "
environment="${environment}CXXFLAGS=\"$CXXFLAGS\" "
fi
############################################################################
ok=0
run () {
if [ "$*" = "" ]
then
configureoptions=""
description="<empty>"
else
configureoptions=" $*"
description="$*"
fi
echo "$environment$configure$configureoptions && make$makeoptions test"
$configure$configureoptions $* >/dev/null 2>&1 && \
make$makeoptions test >/dev/null 2>&1
test $? = 0 || die "Configuration \`$description' failed."
make$makeoptions clean >/dev/null 2>&1
test $? = 0 || die "Cleaning up for \`$description' failed."
ok=`expr $ok + 1`
}
############################################################################
# start with these two for fast fail
run # default configuration (depends on 'MAKEFLAGS'!)
run -p # then check default pedantic first
run -q # library users might want to disable messages
run -q -p # also check '--quiet' pedantically
# now start with the five single options
run -a # actually enables all the four next options below
run -c
run -g
run -l
# all five single options pedantically
run -a -p
run -c -p
run -g -p
run -l -p
# all legal pairs of single options
# ('-a' can not be combined with any of the other options)
# ('-g' can not be combined '-c')
run -c -l
run -c -q
run -g -l
run -g -q
# the same pairs but now with pedantic compilation
run -c -l -p
run -c -q -p
run -g -l -p
run -g -q -p
# finally check that these also work to some extend
run -m32 -q
run -m32 -a -p
run --no-unlocked -q
run --no-unlocked -a -p
run --no-contracts -q
run --no-contracts -a -p
run --no-tracing -q
run --no-tracing -a -p
echo "successfully compiled and tested ${GOOD}${ok}${NORMAL} configurations"

View File

@ -0,0 +1,11 @@
#!/bin/sh
sed \
-e '/^[OLQ[A-Z]*(/!d' \
-e 's,^[OLQ[A-Z]*(,,' \
-e 's/,.*//' \
../src/options.hpp | \
while read option
do
grep -q opts.$option ../src/*.hpp ../src/*.cpp && continue
echo "option '$option' not found"
done

29
hCaD_V2/scripts/colors.sh Executable file
View File

@ -0,0 +1,29 @@
# Use colors if '<stdin>' is connected to a terminal.
color_echo_options=""
if [ -t 1 ]
then
BAD="\033[1;31m" # bright red
HILITE="\033[32m" # normal green
GOOD="\033[1;32m" # bright green
HIDE="\033[34m" # cyan
BOLD="\033[1m" # bold color
NORMAL="\033[0m" # normal color
if [ x"`echo -e 2>/dev/null`" = x ]
then
color_echo_options=" -e"
fi
else
BAD=""
HILITE=""
GOOD=""
HIDE=""
BOLD=""
NORMAL=""
fi
cecho () {
echo$color_echo_options $*
}

View File

@ -0,0 +1,75 @@
#!/bin/sh
name=extend-solution.sh
scriptsdir=`dirname $0`
colors=$scriptsdir/colors.sh
if [ -f $colors ]
then
. $colors
else
BAD=""
BOLD=""
NORMAL=""
fi
die () {
echo "${BOLD}$name:${NORMAL} ${BAD}error:${NORMAL} $*" 1>&2
exit 1
}
[ $# -eq 2 ] || \
die "expected exactly two arguments"
[ -f $1 ] || \
die "argument '$1' not a file"
[ -f $2 ] || \
die "argument '$2' not a file"
status="`grep '^s' $1|head -1`"
case x"$status" in
x"s SATISFIABLE") ;;
x"s UNSATISFIABLE") echo "s UNSATISFIABLE"; exit 20;;
*) die "not valid 's ...' line found in '$1'";;
esac
(
grep '^v' $1;
awk '{
printf "c"
for (i = 1; $i; i++)
printf " %d", $i
printf "\nw";
for (i++; $i; i++)
printf " %d", $i
printf "\n"
}' $2
) | \
awk '
function abs (i) { return i < 0 ? -i : i }
/^v/{
for (i = 2; i <= NF; i++) {
lit = $i;
idx = abs(lit);
if (idx) a[idx] = lit;
}
}
/^c/{
satisfied = 0
for (i = 2; i <= NF; i++) {
lit = $i;
idx = abs(lit);
tmp = a[idx];
if (lit < 0) tmp = -tmp;
if (tmp > 0) satisfied = 1;
}
}
/^w/{
if (satisfied) next
for (i = 2; i <= NF; i++) {
lit = $i;
idx = abs(lit);
a[idx] = -a[idx];
}
}
END {
print "s SATISFIABLE"
for (i in a)
print "v", a[i]
print "v 0"
}
'
exit 10

View File

@ -0,0 +1,37 @@
#!/bin/sh
usage () {
cat <<EOF 2>&1
usage: generate-cubes.sh [-h] <input> [ <output> [ <march_cu option> ... ] ]
EOF
exit 0
}
[ "$1" = -h ] && usage
die () {
echo "generate-cubes.sh: error: $*" 1>&2
exit 1
}
[ $# -lt 1 ] && die "expected DIMACS file argument"
output=""
options=""
[ -f "$1" ] || die "first argument is not a DIMACS file"
input="$1"
shift
while [ $# -gt 0 ]
do
case "$1" in
-*) options="$options $1";;
*) exec 1>"$1";;
esac
shift
done
prefix=/tmp/generate-cubes-$$
cleanup () {
rm -f $prefix*
}
trap "cleanup" 2 11
cubes=$prefix.cubes
march_cu $input -o $cubes $options 1>&2 || exit 1
sed -e 's,^p cnf.*,p inccnf,' $input
cat $cubes
cleanup
exit 0

View File

@ -0,0 +1,13 @@
#!/bin/sh
cd `dirname $0`
grep 'OPTION( ' ../src/options.hpp | \
sed -e 's,^OPTION( ,,' \
-e 's/,/ /' \
-e 's/,.*//g' | \
awk '{printf "c --%s=%d\n", $1, $2}' | \
grep -v 'check' | \
grep -v 'clim' | \
grep -v 'dlim' | \
grep -v 'extend' | \
grep -v 'learn' | \
grep -v 'witness'

View File

@ -0,0 +1,18 @@
#!/bin/sh
cd `dirname $0`
egrep '^(OPTION|BCEOPT|SHROPT)\( ' ../src/options.hpp | \
grep -v '\<double,' | \
sed -e 's,^[^(]*(,,' \
-e 's/\<int,//' \
-e 's/\<bool,//' \
-e 's,".*,,' \
-e 's/,[^,]*$//' \
-e 's,/\*[^\*]*\*/,,' \
-e 's/,/ /g' | \
awk '{printf "%s %d %d %d\n", $1, $2, $3, $4}' | \
grep -v 'check' | \
grep -v 'clim' | \
grep -v 'dlim' | \
grep -v 'extend' | \
grep -v 'learn' | \
grep -v 'witness'

3
hCaD_V2/scripts/get-git-id.sh Executable file
View File

@ -0,0 +1,3 @@
#!/bin/sh
# print current SHA1 git id which uniquely identifies the source code
git show 2>/dev/null |awk '{print $2;exit}'

View File

@ -0,0 +1,92 @@
#!/bin/sh
#--------------------------------------------------------------------------#
# Used to generate 'build.hpp', which in turn is used in 'version.cpp' to
# print compile time options, compiler version, and source code version.
# If generating 'build.hpp' fails you can still compile 'version.cpp' by
# using the '-DNBUILD' compile time option.
#--------------------------------------------------------------------------#
die () {
echo "make-build-header.sh: error: $*" 1>&2
exit 1
}
warning () {
echo "make-build-header.sh: warning: $*" 1>&2
}
#--------------------------------------------------------------------------#
[ ! -f VERSION -a ! -f ../VERSION ] && \
die "needs to be called from build sub-directory"
[ -f makefile ] || \
warning "could not find 'makefile'"
#--------------------------------------------------------------------------#
# The version.
#
VERSION="`cat ../VERSION`"
if [ x"$VERSION" = x ]
then
warning "could not determine 'VERSION'"
else
echo "#define VERSION \"$VERSION\""
fi
#--------------------------------------------------------------------------#
# The unique GIT hash.
#
IDENTIFIER="`../scripts/get-git-id.sh`"
if [ x"$IDENTIFIER" = x ]
then
warning "could not determine 'IDENTIFIER' (git id)"
else
echo "#define IDENTIFIER \"$IDENTIFIER\""
fi
#--------------------------------------------------------------------------#
# C++ compiler 'CXX' used in 'makefile'.
#
COMPILER="`sed -e '/^CXX=/!d' -e 's,^CXX=,,' makefile 2>/dev/null`"
case x"$COMPILER" in
x*g++* | x*clang++*)
COMPILER="`$COMPILER --version 2>/dev/null|head -1`";;
*) COMPILER="";;
esac
if [ x"$COMPILER" = x ]
then
warning "could not determine 'COMPILER' ('CXX')"
else
echo "#define COMPILER \"$COMPILER\""
fi
#--------------------------------------------------------------------------#
# C++ compiler flags 'CXXFLAGS' used in 'makefile'.
#
FLAGS="`sed -e '/^CXXFLAGS=/!d' -e 's,^CXXFLAGS=,,' makefile 2>/dev/null`"
if [ x"$FLAGS" = x ]
then
warning "could not determine 'FLAGS' ('CXXFLAGS')"
else
echo "#define FLAGS \"$FLAGS\""
fi
#--------------------------------------------------------------------------#
# Use time of executing this script at build time.
#
LC_TIME="en_US" # Avoid umlaut in 'DATE'.
export LC_TIME
# The time and date we compiled the CaDiCaL library.
DATE="`date 2>/dev/null|sed -e 's, *, ,g'`"
OS="`uname -srmn 2>/dev/null`"
DATE="`echo $DATE $OS|sed -e 's,^ *,,' -e 's, *$,,'`"
if [ x"$DATE" = x" " ]
then
warning "could not determine 'DATE' (build date and time)"
else
echo "#define DATE \"$DATE\""
fi

View File

@ -0,0 +1,36 @@
#!/bin/sh
die () {
echo "make-src-release.sh: error: $*" 1>&2
exit 1
}
cd `dirname $0`/..
[ -d .git ] || \
die "does not seem to be under 'git' control ('.git' not found)"
version="`awk '/define VERSION/{print $4}' src/version.cpp|sed -e 's,",,g'`"
VERSION="`cat VERSION`"
[ "$version" = "$VERSION" ] || \
die "versions '$version' in 'src/version.cpp' and " \
"'$VERSION' in 'VERSION' do not match (run 'scripts/update-version.sh')"
fullgitid="`./scripts/get-git-id.sh`"
gitid="`echo $fullgitid|sed -e 's,^\(.......\).*,\1,'`"
branch=`git branch|grep '^\*'|head -1|awk '{print $2}'`
[ "$branch" = "" ] && die "could not get branch from 'git'"
name=cadical-${version}-${gitid}
[ "$branch" = "master" ] || name="$name-$branch"
dir=/tmp/$name
tar=/tmp/$name.tar.xz
rm -rf $dir
mkdir $dir || exit 1
git archive $branch | tar -x -C $dir
cat >$dir/scripts/get-git-id.sh <<EOF
#!/bin/sh
echo "$fullgitid"
EOF
sed -i -e "s,IDENTIFIER 0$,IDENTIFIER \"$fullgitid\"," $dir/src/version.cpp
chmod 755 $dir/scripts/*.sh
cd /tmp
rm -rf /tmp/$name/.git
tar cJf $tar $name
bytes="`ls --block-size=1 -s $tar 2>/dev/null |awk '{print $1}'`"
echo "generated '$tar' of $bytes bytes"
rm -rf $dir

View File

@ -0,0 +1,8 @@
#!/bin/sh
cd `dirname $0`/../src
for i in *.cpp *.hpp
do
cp $i $i~ || exit 1
expand $i~ |sed -e 's, *$,,'|uniq > $i
rm -f $i~
done

View File

@ -0,0 +1,52 @@
#!/bin/sh
cd `dirname $0`/..
root=`pwd`
tmp=/tmp/prepare-cadical-sc2021-submission.log
VERSION=`cat VERSION`
rm -f $tmp
##########################################################################
cd $root
./scripts/make-src-release.sh >$tmp || exit 1
tar=`awk '{print $2}' $tmp |sed -e "s,',,g"`
##########################################################################
cd $root
base=cadical-${VERSION}-starexec
dir=/tmp/$base
rm -rf $dir
mkdir $dir
mkdir $dir/bin
mkdir $dir/build
mkdir $dir/archives
cp -a $tar $dir/archives
cat <<EOF >$dir/build/build.sh
#!/bin/sh
tar xf ../archives/cadical*
mv cadical* cadical
cd cadical
./configure --competition
make test
install -s build/cadical ../../bin/
EOF
chmod 755 $dir/build/build.sh
cat <<EOF >$dir/starexec_build
#!/bin/sh
cd build
exec ./build.sh
EOF
chmod 755 $dir/starexec_build
cat <<EOF >$dir/bin/starexec_run_default
#!/bin/sh
exec ./cadical \$1 \$2/proof.out
EOF
chmod 755 $dir/bin/starexec_run_default
description=$dir/starexec_description.txt
grep '^CaDiCaL' README.md|head -1 >$description
cat $description
archive=/tmp/$base.zip
rm -f $archive
cd $dir
zip -r $archive .
cd /tmp/
ls -l $archive
rm -f $tmp
rm -rf $dir/

View File

@ -0,0 +1,58 @@
#!/bin/sh
scriptsdir=`dirname $0`
bindir=$scriptsdir/../build
solver=$bindir/cadical
solutionchecker=$bindir/precochk
proofchecker=$bindir/drat-trim
name=run-cadical-and-check-proof.sh
colors=$scriptsdir/colors.sh
if [ -f $colors ]
then
. $colors
else
BAD=""
BOLD=""
HIDE=""
NORMAL=""
fi
die () {
echo "$name: error: $*" 1>&2
exit 1
}
msg () {
echo "${HIDE}c [$name] $*${NORMAL}"
}
[ $# -eq 1 ] || \
die "expected exactly one argument"
msg "input '$1'"
[ -f $solver ] || \
die "can not find '$solver' (build solver first)"
msg "found '$solver'"
[ -f $solutionchecker ] || \
die "can not find '$solutionchecker' (run cnf tests to compile it first)"
msg "found '$solutionchecker'"
[ -f $proofchecker ] || \
die "can not find '$proofchecker' (run cnf tests to compile it first)"
msg "found '$proofchecker'"
prefix=/tmp/run-cadical-and-check-proof-$$
trap "rm -f $prefix*" 2 9 15
msg "$solver $1 $prefix.proof > $prefix.log"
$solver $1 $prefix.proof > $prefix.log
res=$?
msg "solver exit code '$res'"
msg "cat $prefix.log"
cat $prefix.log
case $res in
10)
msg "$solutionchecker $1 $prefix.log"
$solutionchecker $1 $prefix.log || exit 1
;;
20)
msg "$proofchecker $1 $prefix.proof"
$proofchecker $1 $prefix.proof || exit 1
;;
esac
msg "rm -f $prefix*"
rm -f $prefix*
msg "exit $res"
exit $res

View File

@ -0,0 +1,84 @@
#!/bin/sh
scriptsdir=`dirname $0`
bindir=$scriptsdir/../build
solver=$bindir/cadical
colors=$scriptsdir/colors.sh
extend=$scriptsdir/extend-solution.sh
name=run-simplifier-and-extend-solution.sh
if [ -f $colors ]
then
. $colors
else
BAD=""
BOLD=""
HIDE=""
NORMAL=""
fi
die () {
echo "${BOLD}$name:${NORMAL} ${BAD}error:${NORMAL} $*" 1>&2
exit 1
}
msg () {
echo "${HIDE}c [$name] $*${NORMAL}"
}
input=""
options=""
while [ $# -gt 0 ]
do
case $1 in
-t) options="$1 $2"; shift;;
*)
if [ -f $1 ]
then
[ "$input" ] && die "too many inputs"
input="$1"
else
die "invalid option '$1'"
fi
;;
esac
shift
done
msg "input '$input'"
msg "options '$options'"
[ "$input" ] || die "expected at least one input argument"
[ -f $solver ] || \
die "can not find '$solver' (build solver first)"
msg "found '$solver'"
[ -f $extend ] || \
die "can not find '$extend'"
prefix=/tmp/run-simplifier-and-extend-solution-$$
trap "rm -f $prefix*" 2 9 15
out=$prefix.out
ext=$prefix.ext
log=$prefix.log
msg "$solver $options -n -O1 -c 0 -o $out -e $ext $input > $log"
$solver $options -n -O1 -c 0 -o $out -e $ext $input > $log
res=$?
msg "simplifier exit code '$res'"
msg "sed -e 's,^[vs],c,' -e 's,^c,c [simplifier],' $log"
sed -e 's,^[vs],c,' -e 's,^c,c [simplifier],' $log
msg "$solver $options $out > $log"
$solver $options $out > $log
res=$?
msg "solver exit code '$res'"
msg "sed -e 's,^[vs],c,' -e 's,^c,c [solver],' $log"
sed -e 's,^[vs],c,' -e 's,^c,c [solver],' $log
case $res in
0|10|20) ;;
*) die "unexpected solver exit code '$res'";;
esac
if [ $res = 10 ]
then
msg "$extend $log $ext"
$extend $log $ext
res=$?
msg "extender exit code $res"
elif [ $res = 20 ]
then
echo "s UNSATISFIABLE"
fi
msg "rm -f $prefix*"
rm -f $prefix*
msg "exit $res"
exit $res

View File

@ -0,0 +1,28 @@
#!/bin/sh
echo `dirname $0`
cd `dirname $0`
pwd
. ./colors.sh
new=/tmp/cadical.hpp
old=../src/cadical.hpp
example=../test/api/example.cpp
rm -f $new
sed -e '/solver = new CaDiCaL::Solver/,$d' $old >> $new
sed -e '/solver = new CaDiCaL::Solver/,/delete solver/!d' $example | \
expand | sed -e 's,^,// ,' >> $new
sed -e '1,/delete solver/d' $old >> $new
echo "${HILITE}diff $old $new${NORMAL}"
if diff $old $new
then
echo "${GOOD}no need to update '$old'${NORMAL}"
else
echo "${BAD}cp $new $old${NORMAL}"
cp $new $old
fi
rm -f $new

View File

@ -0,0 +1,12 @@
#!/bin/sh
cd `dirname $0`
NEW="`cat ../VERSION`"
OLD="`awk '/define VERSION/{print \$4}' ../src/version.cpp|sed -e 's,\",,g'`"
if [ x"$OLD" = x"$NEW" ]
then
echo "same version '$NEW' in 'VERSION' and '../src/version.cpp'"
else
echo "found new version '$NEW' in '../VERSION'"
echo "updating old version '$OLD' in '../src/version.cpp'"
sed -i -e '/define VERSION/s,".*",'\""$NEW"\", ../src/version.cpp
fi

12
hCaD_V2/src/README.md Normal file
View File

@ -0,0 +1,12 @@
This is the source code of the library `libcadical.a` with header
`cadical.hpp`, the stand-alone solver `cadical` (in `cadical.cpp`) and the
model based tester `mobical` (in `mobical.app`).
The `configure` script and link to the `makefile` in the root directory
can be used from within the `src` sub-directory too and then will just work
as if used from the root directory. For instance
./configure && make test
will configure and build in `../build` the default (optimizing)
configuration and if successful then run the test suite.

830
hCaD_V2/src/analyze.cpp Normal file
View File

@ -0,0 +1,830 @@
#include "internal.hpp"
namespace CaDiCaL {
/*------------------------------------------------------------------------*/
// Code for conflict analysis, i.e., to generate the first UIP clause. The
// main function is 'analyze' below. It further uses 'minimize' to minimize
// the first UIP clause, which is in 'minimize.cpp'. An important side
// effect of conflict analysis is to update the decision queue by bumping
// variables. Similarly analyzed clauses are bumped to mark them as active.
/*------------------------------------------------------------------------*/
void Internal::learn_empty_clause () {
assert (!unsat);
LOG ("learned empty clause");
external->check_learned_empty_clause ();
if (proof) proof->add_derived_empty_clause ();
unsat = true;
}
void Internal::learn_unit_clause (int lit) {
LOG ("learned unit clause %d", lit);
external->check_learned_unit_clause (lit);
if (proof) proof->add_derived_unit_clause (lit);
mark_fixed (lit);
}
/*------------------------------------------------------------------------*/
// Move bumped variables to the front of the (VMTF) decision queue. The
// 'bumped' time stamp is updated accordingly. It is used to determine
// whether the 'queue.assigned' pointer has to be moved in 'unassign'.
void Internal::bump_queue (int lit) {
assert (opts.bump);
const int idx = vidx (lit);
if (!links[idx].next) return;
queue.dequeue (links, idx);
queue.enqueue (links, idx);
assert (stats.bumped != INT64_MAX);
btab[idx] = ++stats.bumped;
LOG ("moved to front variable %d and bumped to %" PRId64 "", idx, btab[idx]);
if (!vals[idx]) update_queue_unassigned (idx);
}
/*------------------------------------------------------------------------*/
// It would be better to use 'isinf' but there are some historical issues
// with this function. On some platforms it is a macro and even for C++ it
// changed the scope (in pre 5.0 gcc) from '::isinf' to 'std::isinf'. I do
// not want to worry about these strange incompatibilities and thus use the
// same trick as in older solvers (since the MiniSAT team invented EVSIDS)
// and simply put a hard limit here. It is less elegant but easy to port.
static inline bool evsids_limit_hit (double score) {
assert (sizeof (score) == 8); // assume IEEE 754 64-bit double
return score > 1e150; // MAX_DOUBLE is around 1.8e308
}
/*------------------------------------------------------------------------*/
// Classical exponential VSIDS as pioneered by MiniSAT.
void Internal::rescale_variable_scores () {
stats.rescored++;
double divider = score_inc;
for (auto idx : vars) {
const double tmp = stab[idx];
if (tmp > divider) divider = tmp;
}
PHASE ("rescore", stats.rescored,
"rescoring %d variable scores by 1/%g", max_var, divider);
assert (divider > 0);
double factor = 1.0 / divider;
for (auto idx : vars)
stab[idx] *= factor;
score_inc *= factor;
PHASE ("rescore", stats.rescored,
"new score increment %g after %" PRId64 " conflicts",
score_inc, stats.conflicts);
}
void Internal::bump_variable_score (int lit) {
assert (opts.bump);
int idx = vidx (lit);
double old_score = score (idx);
assert (!evsids_limit_hit (old_score));
double new_score = old_score + score_inc;
if (evsids_limit_hit (new_score)) {
LOG ("bumping %g score of %d hits EVSIDS score limit", old_score, idx);
rescale_variable_scores ();
old_score = score (idx);
assert (!evsids_limit_hit (old_score));
new_score = old_score + score_inc;
}
assert (!evsids_limit_hit (new_score));
LOG ("new %g score of %d", new_score, idx);
score (idx) = new_score;
if (scores.contains (idx)) scores.update (idx);
}
// Important variables recently used in conflict analysis are 'bumped',
void Internal::bump_variable (int lit) {
if (use_scores ()) bump_variable_score (lit);
else bump_queue (lit);
}
// After every conflict the variable score increment is increased by a
// factor (if we are currently using scores).
void Internal::bump_variable_score_inc () {
assert (use_scores ());
assert (!evsids_limit_hit (score_inc));
double f = 1e3/opts.scorefactor;
double new_score_inc = score_inc * f;
if (evsids_limit_hit (new_score_inc)) {
LOG ("bumping %g increment by %g hits EVSIDS score limit", score_inc, f);
rescale_variable_scores ();
new_score_inc = score_inc * f;
}
assert (!evsids_limit_hit (new_score_inc));
LOG ("bumped score increment from %g to %g with factor %g",
score_inc, new_score_inc, f);
score_inc = new_score_inc;
}
void Internal::pol_dec_act() {
double f = 1e3/opts.scorefactor,
nps_i = pol_sc_inc * f;
if (nps_i > 1e150) {
rescale_pol_scs ();
nps_i = pol_sc_inc * f;
}
pol_sc_inc = nps_i;
}
void Internal::rescale_pol_scs () {
double f, m = pol_sc_inc;
for (auto l : lits) {
const double tmp = phases.act[vlit(l)];
if (tmp > m) m = tmp;
}
f = 1.0 / m;
for (auto l : lits)
phases.act[vlit(l)] *= f;
pol_sc_inc *= f;
}
void Internal::bump_pol_sc (int lt) {
int l = vlit (lt);
double os = phases.act[l],
ns = os + pol_sc_inc;
if (ns > 1e150) {
rescale_pol_scs ();
os = phases.act[l];
ns = os + pol_sc_inc;
}
phases.act[l] = ns;
}
/*------------------------------------------------------------------------*/
struct analyze_bumped_rank {
Internal * internal;
analyze_bumped_rank (Internal * i) : internal (i) { }
typedef uint64_t Type;
Type operator () (const int & a) const {
return internal->bumped (a);
}
};
struct analyze_bumped_smaller {
Internal * internal;
analyze_bumped_smaller (Internal * i) : internal (i) { }
bool operator () (const int & a, const int & b) const {
const auto s = analyze_bumped_rank (internal) (a);
const auto t = analyze_bumped_rank (internal) (b);
return s < t;
}
};
/*------------------------------------------------------------------------*/
void Internal::bump_variables () {
assert (opts.bump);
START (bump);
if (opts.bumpreason) bump_also_all_reason_literals ();
if (!use_scores ()) {
// Variables are bumped in the order they are in the current decision
// queue. This maintains relative order between bumped variables in
// the queue and seems to work best. We also experimented with
// focusing on variables of the last decision level, but results were
// mixed.
MSORT (opts.radixsortlim,
analyzed.begin (), analyzed.end (),
analyze_bumped_rank (this), analyze_bumped_smaller (this));
}
for (const auto & lit : analyzed)
bump_variable (lit);
if (use_scores ()) bump_variable_score_inc ();
STOP (bump);
}
/*------------------------------------------------------------------------*/
// We use the glue time stamp table 'gtab' for fast glue computation.
int Internal::recompute_glue (Clause * c) {
int res = 0;
const int64_t stamp = ++stats.recomputed;
for (const auto & lit : *c) {
int level = var (lit).level;
assert (gtab[level] <= stamp);
if (gtab[level] == stamp) continue;
gtab[level] = stamp;
res++;
}
return res;
}
// Clauses resolved since the last reduction are marked as 'used', their
// glue is recomputed and they are promoted if the glue shrinks. Note that
// promotion from 'tier3' to 'tier2' will set 'used' to '2'.
inline void Internal::bump_clause (Clause * c) {
LOG (c, "bumping");
unsigned used = c->used;
c->used = 1;
if (c->keep) return;
if (c->hyper) return;
if (!c->redundant) return;
int new_glue = recompute_glue (c);
if (new_glue < c->glue) promote_clause (c, new_glue);
else if (used && c->glue <= opts.reducetier2glue) c->used = 2;
}
/*------------------------------------------------------------------------*/
// During conflict analysis literals not seen yet either become part of the
// first unique implication point (UIP) clause (if on lower decision level),
// are dropped (if fixed), or are resolved away (if on the current decision
// level and different from the first UIP). At the same time we update the
// number of seen literals on a decision level. This helps conflict clause
// minimization. The number of seen levels is the glucose level (also
// called 'glue', or 'LBD').
inline void
Internal::analyze_literal (int lit, int & open) {
assert (lit);
Flags & f = flags (lit);
if (f.seen) return;
Var & v = var (lit);
if (!v.level) return;
assert (val (lit) < 0);
assert (v.level <= level);
if (v.level < level) clause.push_back (lit);
Level & l = control[v.level];
if (!l.seen.count++) {
LOG ("found new level %d contributing to conflict", v.level);
levels.push_back (v.level);
}
if (v.trail < l.seen.trail) l.seen.trail = v.trail;
f.seen = true;
analyzed.push_back (lit);
LOG ("analyzed literal %d assigned at level %d", lit, v.level);
if (v.level == level) open++;
}
inline void
Internal::analyze_reason (int lit, Clause * reason, int & open) {
assert (reason);
bump_clause (reason);
for (const auto & other : *reason)
if (other != lit)
analyze_literal (other, open);
}
/*------------------------------------------------------------------------*/
// This is an idea which was implicit in MapleCOMSPS 2016 for 'limit = 1'.
// See also the paragraph on 'bumping reason side literals' in their SAT'16
// paper [LiangGaneshPoupartCzarnecki-SAT'16]. Reason side bumping was
// performed exactly when 'LRB' based decision heuristics was used, which in
// the original version was enabled after 10000 conflicts until a time limit
// of 2500 seconds was reached (half of the competition time limit). The
// Maple / Glucose / MiniSAT evolution winning the SAT race in 2019 made
// the schedule of reason side bumping deterministic, i.e., avoiding a time
// limit, by switching between 'LRB' and 'VSIDS' in an interval of initially
// 30 million propagations, which then is increased geometrically by 10%.
inline bool Internal::bump_also_reason_literal (int lit) {
assert (lit);
assert (val (lit) < 0);
Flags & f = flags (lit);
if (f.seen) return false;
const Var & v = var (lit);
if (!v.level) return false;
f.seen = true;
analyzed.push_back (lit);
LOG ("bumping also reason literal %d assigned at level %d", lit, v.level);
return true;
}
// We experimented with deeper reason bumping without much success though.
inline void Internal::bump_also_reason_literals (int lit, int limit) {
assert (lit);
assert (limit > 0);
const Var & v = var (lit);
assert (val (lit));
if (!v.level) return;
Clause * reason = v.reason;
if (!reason) return;
for (const auto & other : *reason) {
if (other == lit) continue;
if (!bump_also_reason_literal (other)) continue;
if (limit < 2) continue;
bump_also_reason_literals (-other, limit-1);
}
}
inline void Internal::bump_also_all_reason_literals () {
assert (opts.bumpreason);
assert (opts.bumpreasondepth > 0);
LOG ("bumping reasons up to depth %d", opts.bumpreasondepth);
for (const auto & lit : clause)
bump_also_reason_literals (-lit, opts.bumpreasondepth + stable);
}
/*------------------------------------------------------------------------*/
void Internal::clear_analyzed_literals () {
LOG ("clearing %zd analyzed literals", analyzed.size ());
for (const auto & lit : analyzed) {
Flags & f = flags (lit);
assert (f.seen);
f.seen = false;
assert (!f.keep);
assert (!f.poison);
assert (!f.removable);
}
analyzed.clear ();
}
void Internal::clear_analyzed_levels () {
LOG ("clearing %zd analyzed levels", levels.size ());
for (const auto & l : levels)
if (l < (int) control.size ())
control[l].reset ();
levels.clear ();
}
/*------------------------------------------------------------------------*/
// Smaller level and trail. Comparing literals on their level is necessary
// for chronological backtracking, since trail order might in this case not
// respect level order.
struct analyze_trail_negative_rank {
Internal * internal;
analyze_trail_negative_rank (Internal * s) : internal (s) { }
typedef uint64_t Type;
Type operator () (int a) {
Var & v = internal->var (a);
uint64_t res = v.level;
res <<= 32;
res |= v.trail;
return ~res;
}
};
struct analyze_trail_larger {
Internal * internal;
analyze_trail_larger (Internal * s) : internal (s) { }
bool operator () (const int & a, const int & b) const {
return
analyze_trail_negative_rank (internal) (a) <
analyze_trail_negative_rank (internal) (b);
}
};
/*------------------------------------------------------------------------*/
// Generate new driving clause and compute jump level.
Clause * Internal::new_driving_clause (const int glue, int & jump) {
const size_t size = clause.size ();
Clause * res;
if (!size) {
jump = 0;
res = 0;
} else if (size == 1) {
iterating = true;
jump = 0;
res = 0;
} else {
assert (clause.size () > 1);
// We have to get the last assigned literals into the watch position.
// Sorting all literals with respect to reverse assignment order is
// overkill but seems to get slightly faster run-time. For 'minimize'
// we sort the literals too heuristically along the trail order (so in
// the opposite order) with the hope to hit the recursion limit less
// frequently. Thus sorting effort is doubled here.
//
MSORT (opts.radixsortlim,
clause.begin (), clause.end (),
analyze_trail_negative_rank (this), analyze_trail_larger (this));
jump = var (clause[1]).level;
res = new_learned_redundant_clause (glue);
res->used = 1 + (glue <= opts.reducetier2glue);
}
LOG ("jump level %d", jump);
return res;
}
/*------------------------------------------------------------------------*/
// If chronological backtracking is enabled we need to find the actual
// conflict level and then potentially can also reuse the conflict clause
// as driving clause instead of deriving a redundant new driving clause
// (forcing 'forced') if the number 'count' of literals in conflict assigned
// at the conflict level is exactly one.
inline int Internal::find_conflict_level (int & forced) {
assert (conflict);
assert (opts.chrono);
int res = 0, count = 0;
forced = 0;
for (const auto & lit : *conflict) {
const int tmp = var (lit).level;
if (tmp > res) {
res = tmp;
forced = lit;
count = 1;
} else if (tmp == res) {
count++;
if (res == level && count > 1)
break;
}
}
LOG ("%d literals on actual conflict level %d", count, res);
const int size = conflict->size;
int * lits = conflict->literals;
// Move the two highest level literals to the front.
//
for (int i = 0; i < 2; i++) {
const int lit = lits[i];
int highest_position = i;
int highest_literal = lit;
int highest_level = var (highest_literal).level;
for (int j = i + 1; j < size; j++) {
const int other = lits[j];
const int tmp = var (other).level;
if (highest_level >= tmp) continue;
highest_literal = other;
highest_position = j;
highest_level = tmp;
if (highest_level == res) break;
}
// No unwatched higher assignment level literal.
//
if (highest_position == i) continue;
if (highest_position > 1)
{
LOG (conflict, "unwatch %d in", lit);
remove_watch (watches (lit), conflict);
}
lits[highest_position] = lit;
lits[i] = highest_literal;
if (highest_position > 1)
watch_literal (highest_literal, lits[!i], conflict);
}
// Only if the number of highest level literals in the conflict is one
// then we can reuse the conflict clause as driving clause for 'forced'.
//
if (count != 1) forced = 0;
return res;
}
/*------------------------------------------------------------------------*/
inline int Internal::determine_actual_backtrack_level (int jump) {
int res;
assert (level > jump);
if (!opts.chrono) {
res = jump;
LOG ("chronological backtracking disabled using jump level %d", res);
} else if (opts.chronoalways) {
stats.chrono++;
res = level - 1;
LOG ("forced chronological backtracking to level %d", res);
} else if (jump >= level - 1) {
res = jump;
LOG ("jump level identical to chronological backtrack level %d", res);
} else if ((size_t) jump < assumptions.size ()) {
res = jump;
LOG ("using jump level %d since it is lower than assumption level %zd",
res, assumptions.size ());
} else if (level - jump > opts.chronolevelim) {
stats.chrono++;
res = level - 1;
LOG ("back-jumping over %d > %d levels prohibited"
"thus backtracking chronologically to level %d",
level - jump, opts.chronolevelim, res);
} else if (opts.chronoreusetrail) {
int best_idx = 0, best_pos = 0;
if (use_scores ()) {
for (size_t i = control[jump + 1].trail; i < trail.size (); i++) {
const int idx = abs (trail[i]);
if (best_idx && !score_smaller (this) (best_idx, idx)) continue;
best_idx = idx;
best_pos = i;
}
LOG ("best variable score %g", score (best_idx));
} else {
for (size_t i = control[jump + 1].trail; i < trail.size (); i++) {
const int idx = abs (trail[i]);
if (best_idx && bumped (best_idx) >= bumped (idx)) continue;
best_idx = idx;
best_pos = i;
}
LOG ("best variable bumped %" PRId64 "", bumped (best_idx));
}
assert (best_idx);
LOG ("best variable %d at trail position %d", best_idx, best_pos);
// Now find the frame and decision level in the control stack of that
// best variable index. Note that, as in 'reuse_trail', the frame
// 'control[i]' for decision level 'i' contains the trail before that
// decision level, i.e., the decision 'control[i].decision' sits at
// 'control[i].trail' in the trail and we thus have to check the level
// of the control frame one higher than at the result level.
//
res = jump;
while (res < level-1 && control[res+1].trail <= best_pos)
res++;
if (res == jump)
LOG ("default non-chronological back-jumping to level %d", res);
else {
stats.chrono++;
LOG ("chronological backtracking to level %d to reuse trail", res);
}
} else {
res = jump;
LOG ("non-chronological back-jumping to level %d", res);
}
return res;
}
/*------------------------------------------------------------------------*/
void Internal::eagerly_subsume_recently_learned_clauses (Clause * c) {
assert (opts.eagersubsume);
LOG (c, "trying eager subsumption with");
mark (c);
int64_t lim = stats.eagertried + opts.eagersubsumelim;
const auto begin = clauses.begin ();
auto it = clauses.end ();
#ifdef LOGGING
int64_t before = stats.eagersub;
#endif
while (it != begin && stats.eagertried++ <= lim) {
Clause * d = *--it;
if (c == d) continue;
if (d->garbage) continue;
if (!d->redundant) continue;
int needed = c->size;
for (auto & lit : *d) {
if (marked (lit) <= 0) continue;
if (!--needed) break;
}
if (needed) continue;
LOG (d, "eager subsumed");
stats.eagersub++;
stats.subsumed++;
mark_garbage (d);
}
unmark (c);
#ifdef LOGGING
uint64_t subsumed = stats.eagersub - before;
if (subsumed) LOG ("eagerly subsumed %" PRIu64 " clauses", subsumed);
#endif
}
/*------------------------------------------------------------------------*/
// This is the main conflict analysis routine. It assumes that a conflict
// was found. Then we derive the 1st UIP clause, optionally minimize it,
// add it as learned clause, and then uses the clause for conflict directed
// back-jumping and flipping the 1st UIP literal. In combination with
// chronological backtracking (see discussion above) the algorithm becomes
// slightly more involved.
void Internal::analyze () {
START (analyze);
assert (conflict);
// First update moving averages of trail height at conflict.
//
UPDATE_AVERAGE (averages.current.trail.fast, trail.size ());
UPDATE_AVERAGE (averages.current.trail.slow, trail.size ());
/*----------------------------------------------------------------------*/
if (opts.chrono) {
int forced;
const int conflict_level = find_conflict_level (forced);
// In principle we can perform conflict analysis as in non-chronological
// backtracking except if there is only one literal with the maximum
// assignment level in the clause. Then standard conflict analysis is
// unnecessary and we can use the conflict as a driving clause. In the
// pseudo code of the SAT'18 paper on chronological backtracking this
// corresponds to the situation handled in line 4-6 in Alg. 1, except
// that the pseudo code in the paper only backtracks while we eagerly
// assign the single literal on the highest decision level.
if (forced) {
assert (forced);
assert (conflict_level > 0);
LOG ("single highest level literal %d", forced);
// The pseudo code in the SAT'18 paper actually backtracks to the
// 'second highest decision' level, while their code backtracks
// to 'conflict_level-1', which is more in the spirit of chronological
// backtracking anyhow and thus we also do the latter.
//
backtrack (conflict_level - 1);
LOG ("forcing %d", forced);
search_assign_driving (forced, conflict);
conflict = 0;
STOP (analyze);
return;
}
// Backtracking to the conflict level is in the pseudo code in the
// SAT'18 chronological backtracking paper, but not in their actual
// implementation. In principle we do not need to backtrack here.
// However, as a side effect of backtracking to the conflict level we
// set 'level' to the conflict level which then allows us to reuse the
// old 'analyze' code as is. The alternative (which we also tried but
// then abandoned) is to use 'conflict_level' instead of 'level' in the
// analysis, which however requires to pass it to the 'analyze_reason'
// and 'analyze_literal' functions.
//
backtrack (conflict_level);
}
// Actual conflict on root level, thus formula unsatisfiable.
//
if (!level) {
learn_empty_clause ();
if (external->learner) external->export_learned_empty_clause ();
STOP (analyze);
return;
}
/*----------------------------------------------------------------------*/
// First derive the 1st UIP clause by going over literals assigned on the
// current decision level. Literals in the conflict are marked as 'seen'
// as well as all literals in reason clauses of already 'seen' literals on
// the current decision level. Thus the outer loop starts with the
// conflict clause as 'reason' and then uses the 'reason' of the next
// seen literal on the trail assigned on the current decision level.
// During this process maintain the number 'open' of seen literals on the
// current decision level with not yet processed 'reason'. As soon 'open'
// drops to one, we have found the first unique implication point. This
// is sound because the topological order in which literals are processed
// follows the assignment order and a more complex algorithm to find
// articulation points is not necessary.
//
Clause * reason = conflict;
LOG (reason, "analyzing conflict");
assert (clause.empty ());
int i = trail.size (); // Start at end-of-trail.
int open = 0; // Seen but not processed on this level.
int uip = 0; // The first UIP literal.
for (;;) {
analyze_reason (uip, reason, open);
uip = 0;
while (!uip) {
assert (i > 0);
const int lit = trail[--i];
if (!flags (lit).seen) continue;
if (var (lit).level == level) uip = lit;
}
if (!--open) break;
reason = var (uip).reason;
LOG (reason, "analyzing %d reason", uip);
}
LOG ("first UIP %d", uip);
clause.push_back (-uip);
// Update glue and learned (1st UIP literals) statistics.
//
int size = (int) clause.size ();
const int glue = (int) levels.size () - 1;
LOG (clause, "1st UIP size %d and glue %d clause", size, glue);
UPDATE_AVERAGE (averages.current.glue.fast, glue);
UPDATE_AVERAGE (averages.current.glue.slow, glue);
stats.learned.literals += size;
stats.learned.clauses++;
assert (glue < size);
// Minimize the 1st UIP clause as pioneered by Niklas Soerensson in
// MiniSAT and described in our joint SAT'09 paper.
//
if (size > 1) {
if (opts.shrink)
shrink_and_minimize_clause();
else if (opts.minimize)
minimize_clause();
size = (int) clause.size ();
// Update decision heuristics.
//
if (opts.bump)
bump_variables();
if(opts.psids) pol_dec_act();
if (external->learner) external->export_learned_large_clause (clause);
} else if (external->learner)
external->export_learned_unit_clause(-uip);
// Update actual size statistics.
//
stats.units += (size == 1);
stats.binaries += (size == 2);
UPDATE_AVERAGE (averages.current.size, size);
// Determine back-jump level, learn driving clause, backtrack and assign
// flipped 1st UIP literal.
//
int jump;
Clause * driving_clause = new_driving_clause (glue, jump);
UPDATE_AVERAGE (averages.current.jump, jump);
int new_level = determine_actual_backtrack_level (jump);;
UPDATE_AVERAGE (averages.current.level, new_level);
backtrack (new_level);
if (uip) search_assign_driving (-uip, driving_clause);
else learn_empty_clause ();
if (stable) reluctant.tick (); // Reluctant has its own 'conflict' counter.
// Clean up.
//
clear_analyzed_literals ();
clear_analyzed_levels ();
clause.clear ();
conflict = 0;
STOP (analyze);
if (driving_clause && opts.eagersubsume)
eagerly_subsume_recently_learned_clauses (driving_clause);
}
// We wait reporting a learned unit until propagation of that unit is
// completed. Otherwise the 'i' report gives the number of remaining
// variables before propagating the unit (and hides the actual remaining
// variables after propagating it).
void Internal::iterate () { iterating = false; report ('i'); }
}

30
hCaD_V2/src/arena.cpp Normal file
View File

@ -0,0 +1,30 @@
#include "internal.hpp"
namespace CaDiCaL {
Arena::Arena (Internal * i) {
memset (this, 0, sizeof *this);
internal = i;
}
Arena::~Arena () {
delete [] from.start;
delete [] to.start;
}
void Arena::prepare (size_t bytes) {
LOG ("preparing 'to' space of arena with %zd bytes", bytes);
assert (!to.start);
to.top = to.start = new char[bytes];
to.end = to.start + bytes;
}
void Arena::swap () {
delete [] from.start;
LOG ("delete 'from' space of arena with %zd bytes",
(size_t) (from.end - from.start));
from = to;
to.start = to.top = to.end = 0;
}
}

103
hCaD_V2/src/arena.hpp Normal file
View File

@ -0,0 +1,103 @@
#ifndef _arena_hpp_INCLUDED
#define _arena_hpp_INCLUDED
namespace CaDiCaL {
// This memory allocation arena provides fixed size pre-allocated memory for
// the moving garbage collector 'copy_non_garbage_clauses' in 'collect.cpp'
// to hold clauses which should survive garbage collection.
// The advantage of using a pre-allocated arena is that the allocation order
// of the clauses can be adapted in such a way that clauses watched by the
// same literal are allocated consecutively. This improves locality during
// propagation and thus is more cache friendly. A similar technique is
// implemented in MiniSAT and Glucose and gives substantial speed-up in
// propagations per second even though it might even almost double peek
// memory usage. Note that in MiniSAT this arena is actually required for
// MiniSAT to be able to use 32 bit clauses references instead of 64 bit
// pointers. This would restrict the maximum number of clauses and thus is
// a restriction we do not want to use anymore.
// New learned clauses are allocated in CaDiCaL outside of this arena and
// moved to the arena during garbage collection. The additional 'to' space
// required for such a moving garbage collector is only allocated for those
// clauses surviving garbage collection, which usually needs much less
// memory than all clauses. The net effect is that in our implementation
// the moving garbage collector using this arena only needs roughly 50% more
// memory than allocating the clauses directly. Both implementations can be
// compared by varying the 'opts.arenatype' option (which also controls the
// allocation order of clauses during moving them).
// The standard sequence of using the arena is as follows:
//
// Arena arena;
// ...
// arena.prepare (bytes);
// q1 = arena.copy (p1, bytes1);
// ...
// qn = arena.copy (pn, bytesn);
// assert (bytes1 + ... + bytesn <= bytes);
// arena.swap ();
// ...
// if (!arena.contains (q)) delete q;
// ...
// arena.prepare (bytes);
// q1 = arena.copy (p1, bytes1);
// ...
// qn = arena.copy (pn, bytesn);
// assert (bytes1 + ... + bytesn <= bytes);
// arena.swap ();
// ...
//
// One has to be really careful with 'qi' references to arena memory.
struct Internal;
class Arena {
Internal * internal;
struct { char * start, * top, * end; } from, to;
public:
Arena (Internal *);
~Arena ();
// Prepare 'to' space to hold that amount of memory. Precondition is that
// the 'to' space is empty. The following sequence of 'copy' operations
// can use as much memory in sum as pre-allocated here.
//
void prepare (size_t bytes);
// Does the memory pointed to by 'p' belong to this arena? More precisely
// to the 'from' space, since that is the only one remaining after 'swap'.
//
bool contains (void * p) const {
char * c = (char *) p;
return from.start <= c && c < from.top;
}
// Allocate that amount of memory in 'to' space. This assumes the 'to'
// space has been prepared to hold enough memory with 'prepare'. Then
// copy the memory pointed to by 'p' of size 'bytes'. Note that it does
// not matter whether 'p' is in 'from' or allocated outside of the arena.
//
char * copy (const char * p, size_t bytes) {
char * res = to.top;
to.top += bytes;
assert (to.top <= to.end);
memcpy (res, p, bytes);
return res;
}
// Completely delete 'from' space and then replace 'from' by 'to' (by
// pointer swapping). Everything previously allocated (in 'from') and not
// explicitly copied to 'to' with 'copy' becomes invalid.
//
void swap ();
};
}
#endif

178
hCaD_V2/src/assume.cpp Normal file
View File

@ -0,0 +1,178 @@
#include "internal.hpp"
namespace CaDiCaL {
// Failed literal handling as pioneered by MiniSAT. This first function
// adds an assumption literal onto the assumption stack.
void Internal::assume (int lit) {
Flags & f = flags (lit);
const unsigned char bit = bign (lit);
if (f.assumed & bit) {
LOG ("ignoring already assumed %d", lit);
return;
}
LOG ("assume %d", lit);
f.assumed |= bit;
assumptions.push_back (lit);
freeze (lit);
}
// Find all failing assumptions starting from the one on the assumption
// stack with the lowest decision level. This goes back to MiniSAT and is
// called 'analyze_final' there.
void Internal::failing () {
START (analyze);
LOG ("analyzing failing assumptions");
assert (analyzed.empty ());
assert (clause.empty ());
int first = 0;
// Try to find two clashing assumptions.
//
for (auto & lit : assumptions) {
if (!assumed (-lit)) continue;
first = lit;
break;
}
if (first) {
// TODO: if 'first' is on the root-level, then, as below, we probably
// want an empty core.
clause.push_back (first);
clause.push_back (-first);
Flags & f = flags (first);
unsigned bit = bign (first);
assert (!(f.failed & bit));
f.failed |= bit;
bit = bign (-first);
f.failed |= bit;
} else {
// Find an assumption falsified at smallest decision level.
//
for (auto & lit : assumptions) {
const signed char tmp = val (lit);
if (tmp >= 0) continue;
if (!first || var (first).level > var (lit).level)
first = lit;
}
assert (first);
LOG ("starting with assumption %d falsified on decision level %d",
first, var (first).level);
if (!var (first).level) {
// TODO: I think marking this should yield an empty core
// and not mark 'first' as failed (similarly above).
LOG ("failed assumption %d", first);
clause.push_back (-first);
Flags & f = flags (first);
const unsigned bit = bign (first);
assert (!(f.failed & bit));
f.failed |= bit;
} else {
// The 'analyzed' stack serves as working stack for a BFS through the
// implication graph until decisions, which are all assumptions, or units
// are reached. This is simpler than corresponding code in 'analyze'.
{
LOG ("failed assumption %d", first);
Flags & f = flags (first);
const unsigned bit = bign (first);
assert (!(f.failed & bit));
f.failed |= bit;
f.seen = true;
}
analyzed.push_back (first);
clause.push_back (-first);
size_t next = 0;
while (next < analyzed.size ()) {
const int lit = analyzed[next++];
#ifndef NDEBUG
if (first == lit) assert (val (lit) < 0);
else assert (val (lit) > 0);
#endif
Var & v = var (lit);
if (!v.level) continue;
if (v.reason) {
assert (v.level);
LOG (v.reason, "analyze reason");
for (const auto & other : *v.reason) {
Flags & f = flags (other);
if (f.seen) continue;
f.seen = true;
assert (val (other) < 0);
analyzed.push_back (-other);
}
} else {
assert (assumed (lit));
LOG ("failed assumption %d", lit);
clause.push_back (-lit);
Flags & f = flags (lit);
const unsigned bit = bign (lit);
assert (!(f.failed & bit));
f.failed |= bit;
}
}
clear_analyzed_literals ();
// TODO, we can not do clause minimization here, right?
}
}
VERBOSE (1, "found %zd failed assumptions %.0f%%",
clause.size (), percent (clause.size (), assumptions.size ()));
// We do not actually need to learn this clause, since the conflict is
// forced already by some other clauses. There is also no bumping
// of variables nor clauses necessary. But we still want to check
// correctness of the claim that the determined subset of failing
// assumptions are a high-level core or equivalently their negations form
// a unit-implied clause.
//
external->check_learned_clause ();
if (proof) {
proof->add_derived_clause (clause);
proof->delete_clause (clause);
}
clause.clear ();
STOP (analyze);
}
// Add the start of each incremental phase (leaving the state
// 'UNSATISFIABLE' actually) we reset all assumptions.
void Internal::reset_assumptions () {
for (const auto & lit : assumptions) {
Flags & f = flags (lit);
const unsigned char bit = bign (lit);
f.assumed &= ~bit;
f.failed &= ~bit;
melt (lit);
}
LOG ("cleared %zd assumptions", assumptions.size ());
assumptions.clear ();
}
}

30
hCaD_V2/src/averages.cpp Normal file
View File

@ -0,0 +1,30 @@
#include "internal.hpp"
namespace CaDiCaL {
void Internal::init_averages () {
LOG ("initializing averages");
INIT_EMA (averages.current.jump, opts.emajump);
INIT_EMA (averages.current.level, opts.emalevel);
INIT_EMA (averages.current.size, opts.emasize);
INIT_EMA (averages.current.glue.fast, opts.emagluefast);
INIT_EMA (averages.current.glue.slow, opts.emaglueslow);
INIT_EMA (averages.current.trail.fast, opts.ematrailfast);
INIT_EMA (averages.current.trail.slow, opts.ematrailslow);
assert (!averages.swapped);
}
void Internal::swap_averages () {
LOG ("saving current averages");
swap (averages.current, averages.saved);
if (!averages.swapped) init_averages ();
else LOG ("swapping in previously saved averages");
averages.swapped++;
}
}

36
hCaD_V2/src/averages.hpp Normal file
View File

@ -0,0 +1,36 @@
#ifndef _averages_hpp_INCLUDED
#define _averages_hpp_INCLUDED
#include "ema.hpp" // alphabetically after 'averages.hpp'
namespace CaDiCaL {
struct Averages {
int64_t swapped;
struct {
struct {
EMA fast; // average fast (small window) moving glucose level
EMA slow; // average slow (large window) moving glucose level
} glue;
struct {
EMA fast; // average fast (small window) moving trail level
EMA slow; // average slow (large window) moving trail level
} trail;
EMA size; // average learned clause size
EMA jump; // average (potential non-chronological) back-jump level
EMA level; // average back track level after conflict
} current, saved;
Averages () : swapped (0) { }
};
}
#endif

125
hCaD_V2/src/backtrack.cpp Normal file
View File

@ -0,0 +1,125 @@
#include "internal.hpp"
namespace CaDiCaL {
// The global assignment stack can only be (partially) reset through
// 'backtrack' which is the only function using 'unassign' (inlined and thus
// local to this file). It turns out that 'unassign' does not need a
// specialization for 'probe' nor 'vivify' and thus it is shared.
inline void Internal::unassign (int lit) {
assert (val (lit) > 0);
const int idx = vidx (lit);
vals[idx] = 0;
vals[-idx] = 0;
LOG ("unassign %d @ %d", lit, var (idx).level);
// In the standard EVSIDS variable decision heuristic of MiniSAT, we need
// to push variables which become unassigned back to the heap.
//
if (!scores.contains (idx)) scores.push_back (idx);
// For VMTF we need to update the 'queue.unassigned' pointer in case this
// variable sits after the variable to which 'queue.unassigned' currently
// points. See our SAT'15 paper for more details on this aspect.
//
if (queue.bumped < btab[idx]) update_queue_unassigned (idx);
}
/*------------------------------------------------------------------------*/
// Update the current target maximum assignment and also the very best
// assignment. Whether a trail produces a conflict is determined during
// propagation. Thus that all functions in the 'search' loop after
// propagation can assume that 'no_conflict_until' is valid. If a conflict
// is found then the trail before the last decision is used (see the end of
// 'propagate'). During backtracking we can then save this largest
// propagation conflict free assignment. It is saved as both 'target'
// assignment for picking decisions in 'stable' mode and if it is the
// largest ever such assignment also as 'best' assignment. This 'best'
// assignment can then be used in future stable decisions after the next
// 'rephase_best' overwrites saved phases with it.
void Internal::update_target_and_best () {
bool reset = (rephased && stats.conflicts > last.rephase.conflicts);
if (reset) {
target_assigned = 0;
if (rephased == 'B') best_assigned = 0; // update it again
}
if (no_conflict_until > target_assigned) {
copy_phases (phases.target);
target_assigned = no_conflict_until;
LOG ("new target trail level %zu", target_assigned);
}
if (no_conflict_until > best_assigned) {
copy_phases (phases.best);
best_assigned = no_conflict_until;
LOG ("new best trail level %zu", best_assigned);
}
if (reset) {
report (rephased);
rephased = 0;
}
}
/*------------------------------------------------------------------------*/
void Internal::backtrack (int new_level) {
assert (new_level <= level);
if (new_level == level) return;
stats.backtracks++;
update_target_and_best ();
const size_t assigned = control[new_level+1].trail;
LOG ("backtracking to decision level %d with decision %d and trail %zd",
new_level, control[new_level].decision, assigned);
const size_t end_of_trail = trail.size ();
size_t i = assigned, j = i;
int reassigned = 0, unassigned = 0;
while (i < end_of_trail) {
int lit = trail[i++];
Var & v = var (lit);
if (v.level > new_level) {
unassign (lit);
unassigned++;
} else {
// This is the essence of the SAT'18 paper on chronological
// backtracking. It is possible to just keep out-of-order assigned
// literals on the trail without breaking the solver (after some
// modifications to 'analyze' - see 'opts.chrono' guarded code there).
assert (opts.chrono);
#ifdef LOGGING
if (!v.level) LOG ("reassign %d @ 0 unit clause %d", lit, lit);
else LOG (v.reason, "reassign %d @ %d", lit, v.level);
#endif
trail[j] = lit;
v.trail = j++;
reassigned++;
}
}
trail.resize (j);
LOG ("unassigned %d literals %.0f%%",
unassigned, percent (unassigned, unassigned + reassigned));
LOG ("reassigned %d literals %.0f%%",
reassigned, percent (reassigned, unassigned + reassigned));
if (propagated > assigned) propagated = assigned;
if (propagated2 > assigned) propagated2 = assigned;
if (no_conflict_until > assigned) no_conflict_until = assigned;
control.resize (new_level + 1);
level = new_level;
}
}

148
hCaD_V2/src/backward.cpp Normal file
View File

@ -0,0 +1,148 @@
#include "internal.hpp"
namespace CaDiCaL {
/*------------------------------------------------------------------------*/
// Provide eager backward subsumption for resolved clauses.
// The eliminator maintains a queue of clauses that are new and have to be
// checked to subsume or strengthen other (longer or same size) clauses.
void Eliminator::enqueue (Clause * c) {
if (!internal->opts.elimbackward) return;
if (c->enqueued) return;
LOG (c, "backward enqueue");
backward.push (c);
c->enqueued = true;
}
Clause * Eliminator::dequeue () {
if (backward.empty ()) return 0;
Clause * res = backward.front ();
backward.pop ();
assert (res->enqueued);
res->enqueued = false;
LOG (res, "backward dequeue");
return res;
}
Eliminator::~Eliminator () {
while (dequeue ())
;
}
/*------------------------------------------------------------------------*/
void Internal::elim_backward_clause (Eliminator & eliminator, Clause *c) {
assert (opts.elimbackward);
assert (!c->redundant);
if (c->garbage) return;
LOG (c, "attempting backward subsumption and strengthening with");
size_t len = UINT_MAX;
unsigned size = 0;
int best = 0;
bool satisfied = false;
for (const auto & lit : *c) {
const signed char tmp = val (lit);
if (tmp > 0) { satisfied = true; break; }
if (tmp < 0) continue;
size_t l = occs (lit).size ();
LOG ("literal %d occurs %zd times", lit, l);
if (l < len) best = lit, len = l;
mark (lit);
size++;
}
if (satisfied) {
LOG ("clause actually already satisfied");
elim_update_removed_clause (eliminator, c);
mark_garbage (c);
} else if (len > (size_t) opts.elimocclim) {
LOG ("skipping backward subsumption due to too many occurrences");
} else {
assert (len);
LOG ("literal %d has smallest number of occurrences %zd", best, len);
LOG ("marked %d literals in clause of size %d", size, c->size);
for (auto & d : occs (best)) {
if (d == c) continue;
if (d->garbage) continue;
if ((unsigned) d->size < size) continue;
int negated = 0;
unsigned found = 0;
for (const auto & lit : *d) {
signed char tmp = val (lit);
if (tmp > 0) { satisfied = true; break; }
if (tmp < 0) continue;
tmp = marked (lit);
if (!tmp) continue;
if (tmp < 0) {
if (negated) { size = UINT_MAX; break; }
else negated = lit;
}
if (++found == size) break;
}
if (satisfied) {
LOG (d, "found satisfied clause");
elim_update_removed_clause (eliminator, d);
mark_garbage (d);
} else if (found == size) {
if (!negated) {
LOG (d, "found subsumed clause");
elim_update_removed_clause (eliminator, d);
mark_garbage (d);
stats.subsumed++;
stats.elimbwsub++;
} else {
int unit = 0;
for (const auto & lit : * d) {
const signed char tmp = val (lit);
if (tmp < 0) continue;
if (tmp > 0) { satisfied = true; break; }
if (lit == negated) continue;
if (unit) { unit = INT_MIN; break; }
else unit = lit;
}
assert (unit);
if (satisfied) {
mark_garbage (d);
elim_update_removed_clause (eliminator, d);
} else if (unit && unit != INT_MIN) {
assert (unit);
LOG (d, "unit %d through hyper unary resolution with", unit);
assign_unit (unit);
elim_propagate (eliminator, unit);
break;
} else if (occs (negated).size () <= (size_t) opts.elimocclim) {
strengthen_clause (d, negated);
remove_occs (occs (negated), d);
elim_update_removed_lit (eliminator, negated);
stats.elimbwstr++;
assert (negated != best);
eliminator.enqueue (d);
}
}
}
}
}
unmark (c);
}
/*------------------------------------------------------------------------*/
void Internal::elim_backward_clauses (Eliminator & eliminator) {
if (!opts.elimbackward) {
assert (eliminator.backward.empty ());
return;
}
START (backward);
LOG ("attempting backward subsumption and strengthening with %zd clauses",
eliminator.backward.size ());
Clause * c;
while (!unsat && (c = eliminator.dequeue ()))
elim_backward_clause (eliminator, c);
STOP (backward);
}
/*------------------------------------------------------------------------*/
}

22
hCaD_V2/src/bins.cpp Normal file
View File

@ -0,0 +1,22 @@
#include "internal.hpp"
namespace CaDiCaL {
/*------------------------------------------------------------------------*/
// Binary implication graph lists.
void Internal::init_bins () {
assert (big.empty ());
while (big.size () < 2*vsize)
big.push_back (Bins ());
LOG ("initialized binary implication graph");
}
void Internal::reset_bins () {
assert (!big.empty ());
erase_vector (big);
LOG ("reset binary implication graph");
}
}

17
hCaD_V2/src/bins.hpp Normal file
View File

@ -0,0 +1,17 @@
#ifndef _bins_hpp_INCLUDED
#define _bins_hpp_INCLUDED
#include "util.hpp" // Alphabetically after 'bins'.
namespace CaDiCaL {
using namespace std;
typedef vector<int> Bins;
inline void shrink_bins (Bins & bs) { shrink_vector (bs); }
inline void erase_bins (Bins & bs) { erase_vector (bs); }
}
#endif

751
hCaD_V2/src/block.cpp Normal file
View File

@ -0,0 +1,751 @@
#include "internal.hpp"
namespace CaDiCaL {
/*------------------------------------------------------------------------*/
// This implements an inprocessing version of blocked clause elimination and
// is assumed to be triggered just before bounded variable elimination. It
// has a separate 'block' flag while variable elimination uses 'elim'.
// Thus it only tries to block clauses on a literal which was removed in an
// irredundant clause in negated form before and has not been tried to use
// as blocking literal since then.
/*------------------------------------------------------------------------*/
inline bool block_more_occs_size::operator () (unsigned a, unsigned b) {
size_t s = internal->noccs (-internal->u2i (a));
size_t t = internal->noccs (-internal->u2i (b));
if (s > t) return true;
if (s < t) return false;
s = internal->noccs (internal->u2i (a));
t = internal->noccs (internal->u2i (b));
if (s > t) return true;
if (s < t) return false;
return a > b;
}
/*------------------------------------------------------------------------*/
// Determine whether 'c' is blocked on 'lit', by first marking all its
// literals and then checking all resolvents with negative clauses (with
// '-lit') are tautological. We use a move-to-front scheme for both the
// occurrence list of negative clauses (with '-lit') and then for literals
// within each such clause. The clause move-to-front scheme has the goal to
// find non-tautological clauses faster in the future, while the literal
// move-to-front scheme has the goal to faster find the matching literal,
// which makes the resolvent tautological (again in the future).
bool Internal::is_blocked_clause (Clause * c, int lit) {
LOG (c, "trying to block on %d", lit);
assert (c->size >= opts.blockminclslim);
assert (c->size <= opts.blockmaxclslim);
assert (active (lit));
assert (!val (lit));
assert (!c->garbage);
assert (!c->redundant);
assert (!level);
mark (c); // First mark all literals in 'c'.
Occs & os = occs (-lit);
LOG ("resolving against at most %zd clauses with %d", os.size (), -lit);
bool res = true; // Result is true if all resolvents tautological.
// Can not use 'auto' here since we update 'os' during traversal.
//
const auto end_of_os = os.end ();
auto i = os.begin ();
Clause * prev_d = 0; // Previous non-tautological clause.
for (; i != end_of_os; i++)
{
// Move the first clause with non-tautological resolvent to the front of
// the occurrence list to improve finding it faster later.
//
Clause * d = *i;
assert (!d->garbage);
assert (!d->redundant);
assert (d->size <= opts.blockmaxclslim);
*i = prev_d; // Move previous non-tautological clause
prev_d = d; // backwards but remember clause at this position.
LOG (d, "resolving on %d against", lit);
stats.blockres++;
int prev_other = 0; // Previous non-tautological literal.
// No 'auto' since we update literals of 'd' during traversal.
//
const const_literal_iterator end_of_d = d->end ();
literal_iterator l;
for (l = d->begin (); l != end_of_d; l++)
{
// Same move-to-front mechanism for literals within a clause. It
// moves the first negatively marked literal to the front to find it
// faster in the future.
//
const int other = *l;
*l = prev_other;
prev_other = other;
if (other == -lit) continue;
assert (other != lit);
assert (active (other));
assert (!val (other));
if (marked (other) < 0) {
LOG ("found tautological literal %d", other);
d->literals[0] = other; // Move to front of 'd'.
break;
}
}
if (l == end_of_d)
{
LOG ("no tautological literal found");
//
// Since we did not find a tautological literal we restore the old
// order of literals in the clause.
//
const const_literal_iterator begin_of_d = d->begin ();
while (l-- != begin_of_d) {
const int other = *l;
*l = prev_other;
prev_other = other;
}
res = false; // Now 'd' is a witness that 'c' is not blocked.
os[0] = d; // Move it to the front of the occurrence list.
break;
}
}
unmark (c); // ... all literals of the candidate clause.
// If all resolvents are tautological and thus the clause is blocked we
// restore the old order of clauses in the occurrence list of '-lit'.
//
if (res) {
assert (i == end_of_os);
const auto boc = os.begin ();
while (i != boc) {
Clause * d = *--i;
*i = prev_d;
prev_d = d;
}
}
return res;
}
/*------------------------------------------------------------------------*/
void Internal::block_schedule (Blocker & blocker)
{
// Set skip flags for all literals in too large clauses.
//
for (const auto & c : clauses) {
if (c->garbage) continue;
if (c->redundant) continue;
if (c->size <= opts.blockmaxclslim) continue;
for (const auto & lit : *c)
mark_skip (-lit);
}
// Connect all literal occurrences in irredundant clauses.
//
for (const auto & c : clauses) {
if (c->garbage) continue;
if (c->redundant) continue;
for (const auto & lit : *c) {
assert (active (lit));
assert (!val (lit));
occs (lit).push_back (c);
}
}
// We establish the invariant that 'noccs' gives the number of actual
// occurrences of 'lit' in non-garbage clauses, while 'occs' might still
// refer to garbage clauses, thus 'noccs (lit) <= occs (lit).size ()'. It
// is expensive to remove references to garbage clauses from 'occs' during
// blocked clause elimination, but decrementing 'noccs' is cheap.
for (auto lit : lits) {
if (!active (lit)) continue;
assert (!val (lit));
Occs & os = occs (lit);
noccs (lit) = os.size ();
}
// Now we fill the schedule (priority queue) of candidate literals to be
// tried as blocking literals. It is probably slightly faster to do this
// in one go after all occurrences have been determined, instead of
// filling the priority queue during pushing occurrences. Filling the
// schedule can not be fused with the previous loop (easily) since we
// first have to initialize 'noccs' for both 'lit' and '-lit'.
int skipped = 0;
for (auto idx : vars) {
if (!active (idx)) continue;
if (frozen (idx)) { skipped += 2; continue; }
assert (!val (idx));
for (int sign = -1; sign <= 1; sign += 2) {
const int lit = sign * idx;
if (marked_skip (lit)) { skipped++; continue; }
if (!marked_block (lit)) continue;
unmark_block (lit);
LOG ("scheduling %d with %" PRId64 " positive and %" PRId64 " negative occurrences",
lit, noccs (lit), noccs (-lit));
blocker.schedule.push_back (vlit (lit));
}
}
PHASE ("block", stats.blockings,
"scheduled %zd candidate literals %.2f%% (%d skipped %.2f%%)",
blocker.schedule.size (),
percent (blocker.schedule.size (), 2.0*active ()),
skipped, percent (skipped, 2.0*active ()));
}
/*------------------------------------------------------------------------*/
// A literal is pure if it only occurs positive. Then all clauses in which
// it occurs are blocked on it. This special case can be implemented faster
// than trying to block literals with at least one negative occurrence and
// is thus handled separately. It also allows to avoid pushing blocked
// clauses onto the extension stack.
void Internal::block_pure_literal (Blocker & blocker, int lit)
{
if (frozen (lit)) return;
assert (active (lit));
Occs & pos = occs (lit);
Occs & nos = occs (-lit);
assert (!noccs (-lit));
#ifndef NDEBUG
for (const auto & c : nos) assert (c->garbage);
#endif
stats.blockpurelits++;
LOG ("found pure literal %d", lit);
int64_t pured = 0;
for (const auto & c : pos) {
if (c->garbage) continue;
assert (!c->redundant);
LOG (c, "pure literal %d in", lit);
blocker.reschedule.push_back (c);
external->push_clause_on_extension_stack (c, lit);
stats.blockpured++;
mark_garbage (c);
pured++;
}
erase_vector (pos);
erase_vector (nos);
mark_pure (lit);
stats.blockpured++;
LOG ("blocking %" PRId64 " clauses on pure literal %d", pured, lit);
}
/*------------------------------------------------------------------------*/
// If there is only one negative clause with '-lit' it is faster to mark it
// instead of marking all the positive clauses with 'lit' one after the
// other and then resolving against the negative clause.
void
Internal::block_literal_with_one_negative_occ (Blocker & blocker, int lit)
{
assert (active (lit));
assert (!frozen (lit));
assert (noccs (lit) > 0);
assert (noccs (-lit) == 1);
Occs & nos = occs (-lit);
assert (nos.size () >= 1);
Clause * d = 0;
for (const auto & c : nos) {
if (c->garbage) continue;
assert (!d);
d = c;
#ifndef NDEBUG
break;
#endif
}
assert (d);
nos.resize (1);
nos[0] = d;
if (d && d->size > opts.blockmaxclslim) {
LOG (d, "skipped common antecedent");
return;
}
assert (!d->garbage);
assert (!d->redundant);
assert (d->size <= opts.blockmaxclslim);
LOG (d, "common %d antecedent", lit);
mark (d);
int64_t blocked = 0, skipped = 0;
Occs & pos = occs (lit);
// Again no 'auto' since 'pos' is update during traversal.
//
const auto eop = pos.end ();
auto j = pos.begin (), i = j;
for (; i != eop; i++)
{
Clause * c = *j++ = *i;
if (c->garbage) { j--; continue; }
if (c->size > opts.blockmaxclslim) { skipped++; continue; }
if (c->size < opts.blockminclslim) { skipped++; continue; }
LOG (c, "trying to block on %d", lit);
// We use the same literal move-to-front strategy as in
// 'is_blocked_clause'. See there for more explanations.
int prev_other = 0; // Previous non-tautological literal.
// No 'auto' since literals of 'c' are updated during traversal.
//
const const_literal_iterator end_of_c = c->end ();
literal_iterator l;
for (l = c->begin (); l != end_of_c; l++)
{
const int other = *l;
*l = prev_other;
prev_other = other;
if (other == lit) continue;
assert (other != -lit);
assert (active (other));
assert (!val (other));
if (marked (other) < 0) {
LOG ("found tautological literal %d", other);
c->literals[0] = other; // Move to front of 'c'.
break;
}
}
if (l == end_of_c) {
LOG ("no tautological literal found");
// Restore old literal order in the clause because.
const const_literal_iterator begin_of_c = c->begin ();
while (l-- != begin_of_c) {
const int other = *l;
*l = prev_other;
prev_other = other;
}
continue; // ... with next candidate 'c' in 'pos'.
}
blocked++;
LOG (c, "blocked");
external->push_clause_on_extension_stack (c, lit);
blocker.reschedule.push_back (c);
mark_garbage (c);
j--;
}
if (j == pos.begin ()) erase_vector (pos);
else pos.resize (j - pos.begin ());
stats.blocked += blocked;
LOG ("blocked %" PRId64 " clauses on %d (skipped %" PRId64 ")", blocked, lit, skipped);
unmark (d);
}
/*------------------------------------------------------------------------*/
// Determine the set of candidate clauses with 'lit', which are checked to
// be blocked by 'lit'. Filter out too large and small clauses and which do
// not have any negated other literal in any of the clauses with '-lit'.
size_t Internal::block_candidates (Blocker & blocker, int lit) {
assert (blocker.candidates.empty ());
Occs & pos = occs (lit); // Positive occurrences of 'lit'.
Occs & nos = occs (-lit);
assert ((size_t) noccs (lit) <= pos.size ());
assert ((size_t) noccs (-lit) == nos.size ()); // Already flushed.
// Mark all literals in clauses with '-lit'. Note that 'mark2' uses
// separate bits for 'lit' and '-lit'.
//
for (const auto & c : nos) mark2 (c);
const auto eop = pos.end ();
auto j = pos.begin (), i = j;
for (; i != eop; i++)
{
Clause * c = *j++ = *i;
if (c->garbage) { j--; continue; }
assert (!c->redundant);
if (c->size > opts.blockmaxclslim) continue;
if (c->size < opts.blockminclslim) continue;
const const_literal_iterator eoc = c->end ();
const_literal_iterator l;
for (l = c->begin (); l != eoc; l++) {
const int other = *l;
if (other == lit) continue;
assert (other != -lit);
assert (active (other));
assert (!val (other));
if (marked2 (-other)) break;
}
if (l != eoc) blocker.candidates.push_back (c);
}
if (j == pos.begin ()) erase_vector (pos);
else pos.resize (j - pos.begin ());
assert (pos.size () == (size_t) noccs (lit)); // Now also flushed.
for (const auto & c : nos) unmark (c);
return blocker.candidates.size ();
}
/*------------------------------------------------------------------------*/
// Try to find a clause with '-lit' which does not have any literal in
// clauses with 'lit'. If such a clause exists no candidate clause can be
// blocked on 'lit' since all candidates would produce a non-tautological
// resolvent with that clause.
Clause * Internal::block_impossible (Blocker & blocker, int lit)
{
assert (noccs (-lit) > 1);
assert (blocker.candidates.size () > 1);
for (const auto & c : blocker.candidates) mark2 (c);
Occs & nos = occs (-lit);
Clause * res = 0;
for (const auto & c : nos) {
assert (!c->garbage);
assert (!c->redundant);
assert (c->size <= opts.blockmaxclslim);
const const_literal_iterator eoc = c->end ();
const_literal_iterator l;
for (l = c->begin (); l != eoc; l++) {
const int other = *l;
if (other == -lit) continue;
assert (other != lit);
assert (active (other));
assert (!val (other));
if (marked2 (-other)) break;
}
if (l == eoc) res = c;
}
for (const auto & c : blocker.candidates) unmark (c);
if (res) {
LOG (res, "common non-tautological resolvent producing");
blocker.candidates.clear ();
}
return res;
}
/*------------------------------------------------------------------------*/
// In the general case we have at least two negative occurrences.
void Internal::block_literal_with_at_least_two_negative_occs (
Blocker & blocker,
int lit)
{
assert (active (lit));
assert (!frozen (lit));
assert (noccs (lit) > 0);
assert (noccs (-lit) > 1);
Occs & nos = occs (-lit);
assert ((size_t) noccs (-lit) <= nos.size ());
int max_size = 0;
// Flush all garbage clauses in occurrence list 'nos' of '-lit' and
// determine the maximum size of negative clauses (with '-lit').
//
const auto eon = nos.end ();
auto j = nos.begin (), i = j;
for (; i != eon; i++)
{
Clause * c = *j++ = *i;
if (c->garbage) j--;
else if (c->size > max_size) max_size = c->size;
}
if (j == nos.begin ()) erase_vector (nos);
else nos.resize (j - nos.begin ());
assert (nos.size () == (size_t) noccs (-lit));
assert (nos.size () > 1);
// If the maximum size of a negative clause (with '-lit') exceeds the
// maximum clause size limit ignore this candidate literal.
//
if (max_size > opts.blockmaxclslim) {
LOG ("maximum size %d of clauses with %d exceeds clause size limit %d",
max_size, -lit, opts.blockmaxclslim);
return;
}
LOG ("maximum size %d of clauses with %d", max_size, -lit);
// We filter candidate clauses with positive occurrence of 'lit' in
// 'blocker.candidates' and return if no candidate clause remains.
// Candidates should be small enough and should have at least one literal
// which occurs negated in one of the clauses with '-lit'.
//
size_t candidates = block_candidates (blocker, lit);
if (!candidates) {
LOG ("no candidate clauses found");
return;
}
LOG ("found %zd candidate clauses", candidates);
// We further search for a clause with '-lit' that has no literal
// negated in any of the candidate clauses (except 'lit'). If such a
// clause exists, we know that none of the candidates is blocked.
//
if (candidates > 1 && block_impossible (blocker, lit)) {
LOG ("impossible to block any candidate clause on %d", lit);
assert (blocker.candidates.empty ());
return;
}
LOG ("trying to block %zd clauses out of %" PRId64 " with literal %d",
candidates, noccs (lit), lit);
int64_t blocked = 0;
// Go over all remaining candidates and try to block them on 'lit'.
//
for (const auto & c : blocker.candidates) {
assert (!c->garbage);
assert (!c->redundant);
if (!is_blocked_clause (c, lit)) continue;
blocked++;
LOG (c, "blocked");
external->push_clause_on_extension_stack (c, lit);
blocker.reschedule.push_back (c);
mark_garbage (c);
}
LOG ("blocked %" PRId64 " clauses on %d out of %zd candidates in %zd occurrences",
blocked, lit, blocker.candidates.size (), occs (lit).size ());
blocker.candidates.clear ();
stats.blocked += blocked;
if (blocked) flush_occs (lit);
}
/*------------------------------------------------------------------------*/
// Reschedule literals in a clause (except 'lit') which was blocked.
void
Internal::block_reschedule_clause (Blocker & blocker, int lit, Clause * c)
{
#ifdef NDEBUG
(void) lit;
#endif
assert (c->garbage);
for (const auto & other : *c) {
int64_t & n = noccs (other);
assert (n > 0);
n--;
LOG ("updating %d with %" PRId64 " positive and %" PRId64 " negative occurrences",
other, noccs (other), noccs (-other));
if (blocker.schedule.contains (vlit (-other)))
blocker.schedule.update (vlit (-other));
else if (active (other) &&
!frozen (other) &&
!marked_skip (-other)) {
LOG ("rescheduling to block clauses on %d", -other);
blocker.schedule.push_back (vlit (-other));
}
if (blocker.schedule.contains (vlit (other))) {
assert (other != lit);
blocker.schedule.update (vlit (other));
}
}
}
// Reschedule all literals in clauses blocked by 'lit' (except 'lit').
void Internal::block_reschedule (Blocker & blocker, int lit)
{
while (!blocker.reschedule.empty ()) {
Clause * c = blocker.reschedule.back ();
blocker.reschedule.pop_back ();
block_reschedule_clause (blocker, lit, c);
}
}
/*------------------------------------------------------------------------*/
void Internal::block_literal (Blocker & blocker, int lit)
{
assert (!marked_skip (lit));
if (!active (lit)) return; // Pure literal '-lit'.
if (frozen (lit)) return;
assert (!val (lit));
// If the maximum number of a negative clauses (with '-lit') exceeds the
// occurrence limit ignore this candidate literal.
//
if (noccs (-lit) > opts.blockocclim) return;
LOG ("blocking literal candidate %d "
"with %" PRId64 " positive and %" PRId64 " negative occurrences",
lit, noccs (lit), noccs (-lit));
stats.blockcands++;
assert (blocker.reschedule.empty ());
assert (blocker.candidates.empty ());
if (!noccs (-lit)) block_pure_literal (blocker, lit);
else if (!noccs (lit)) {
// Rare situation, where the clause length limit was hit for 'lit' and
// '-lit' is skipped and then it becomes pure. Can be ignored. We also
// so it once happing for a 'elimboundmin=-1' and zero positive and one
// negative occurrence.
} else if (noccs (-lit) == 1)
block_literal_with_one_negative_occ (blocker, lit);
else
block_literal_with_at_least_two_negative_occs (blocker, lit);
// Done with blocked clause elimination on this literal and we do not
// have to try blocked clause elimination on it again until irredundant
// clauses with its negation are removed.
//
assert (!frozen (lit)); // just to be sure ...
unmark_block (lit);
}
/*------------------------------------------------------------------------*/
bool Internal::block () {
if (!opts.block) return false;
if (unsat) return false;
if (!stats.current.irredundant) return false;
if (terminated_asynchronously ()) return false;
if (propagated < trail.size ()) {
LOG ("need to propagate %zd units first", trail.size () - propagated);
init_watches ();
connect_watches ();
if (!propagate ()) {
LOG ("propagating units results in empty clause");
learn_empty_clause ();
assert (unsat);
}
clear_watches ();
reset_watches ();
if (unsat) return false;
}
START_SIMPLIFIER (block, BLOCK);
stats.blockings++;
LOG ("block-%" PRId64 "", stats.blockings);
assert (!level);
assert (!watching ());
assert (!occurring ());
mark_satisfied_clauses_as_garbage ();
init_occs (); // Occurrence lists for all literals.
init_noccs (); // Number of occurrences to avoid flushing garbage clauses.
Blocker blocker (this);
block_schedule (blocker);
int64_t blocked = stats.blocked;
int64_t resolutions = stats.blockres;
int64_t purelits = stats.blockpurelits;
int64_t pured = stats.blockpured;
while (!terminated_asynchronously () &&
!blocker.schedule.empty ()) {
int lit = u2i (blocker.schedule.front ());
blocker.schedule.pop_front ();
block_literal (blocker, lit);
block_reschedule (blocker, lit);
}
blocker.erase ();
reset_noccs ();
reset_occs ();
resolutions = stats.blockres - resolutions;
blocked = stats.blocked - blocked;
PHASE ("block", stats.blockings,
"blocked %" PRId64 " clauses in %" PRId64 " resolutions",
blocked, resolutions);
pured = stats.blockpured - pured;
purelits = stats.blockpurelits - purelits;
if (pured)
mark_redundant_clauses_with_eliminated_variables_as_garbage ();
if (purelits)
PHASE ("block", stats.blockings,
"found %" PRId64 " pure literals in %" PRId64 " clauses",
purelits, pured);
else
PHASE ("block", stats.blockings,
"no pure literals found");
report ('b', !opts.reportall && !blocked);
STOP_SIMPLIFIER (block, BLOCK);
return blocked;
}
}

37
hCaD_V2/src/block.hpp Normal file
View File

@ -0,0 +1,37 @@
#ifndef _block_hpp_INCLUDED
#define _block_hpp_INCLUDED
#include "heap.hpp" // Alphabetically after 'block.hpp'.
namespace CaDiCaL {
struct Internal;
struct block_more_occs_size {
Internal * internal;
block_more_occs_size (Internal * i) : internal (i) { }
bool operator () (unsigned a, unsigned b);
};
typedef heap<block_more_occs_size> BlockSchedule;
class Blocker {
friend struct Internal;
vector<struct Clause*> candidates;
vector<struct Clause*> reschedule;
BlockSchedule schedule;
Blocker (Internal * i) : schedule (block_more_occs_size (i)) { }
void erase () {
erase_vector (candidates);
erase_vector (reschedule);
schedule.erase ();
}
};
}
#endif

933
hCaD_V2/src/cadical.cpp Normal file
View File

@ -0,0 +1,933 @@
/*------------------------------------------------------------------------*/
// Do include 'internal.hpp' but try to minimize internal dependencies.
#include "internal.hpp"
#include "signal.hpp" // Separate, only need for apps.
/*------------------------------------------------------------------------*/
namespace CaDiCaL {
// A wrapper app which makes up the CaDiCaL stand alone solver. It in
// essence only consists of the 'App::main' function. So this class
// contains code, which is not required if only the library interface in
// the class 'Solver' is used (defined in 'cadical.hpp'). It further uses
// static data structures in order to have a signal handler catch signals.
//
// It is thus neither thread-safe nor reentrant. If you want to use
// multiple instances of the solver use the 'Solver' interface directly
// which is thread-safe and reentrant among different solver instances.
/*------------------------------------------------------------------------*/
class App : public Handler, public Terminator {
Solver * solver; // Global solver.
#ifndef __WIN32
// Command line options.
//
int time_limit; // '-t <sec>'
#endif
// Strictness of (DIMACS) parsing:
//
// 0 = force parsing and completely ignore header
// 1 = relaxed header handling (default)
// 2 = strict header handling
//
// To use '0' use '-f' of '--force'.
// To use '2' use '--strict'.
//
int force_strict_parsing;
bool force_writing;
static bool most_likely_existing_cnf_file (const char * path);
// Internal variables.
//
int max_var; // Set after parsing.
volatile bool timesup; // Asynchronous termination.
// Printing.
//
void print_usage (bool all = false);
void print_witness (FILE *);
#ifndef QUIET
void signal_message (const char * msg, int sig);
#endif
// Option handling.
//
bool set (const char*);
bool set (const char*, int);
int get (const char*);
bool verbose () { return get ("verbose") && !get ("quiet"); }
/*----------------------------------------------------------------------*/
// The actual initialization.
//
void init ();
// Terminator interface.
//
bool terminate () { return timesup; }
// Handler interface.
//
void catch_signal (int sig);
void catch_alarm ();
public:
App ();
~App ();
// Parse the arguments and run the solver.
//
int main (int arg, char ** argv);
};
/*------------------------------------------------------------------------*/
void App::print_usage (bool all) {
printf (
"usage: cadical [ <option> ... ] [ <input> [ <proof> ] ]\n"
"\n"
"where '<option>' is one of the following common options:\n"
"\n");
if (!all) { // Print only a short list of common options.
printf (
" -h print this short list of common options\n"
" --help print complete list of all options\n"
" --version print version\n"
"\n"
" -n do not print witness\n"
#ifndef QUIET
" -v increase verbosity\n"
" -q be quiet\n"
#endif
"\n"
#ifndef __WIN32
" -t <sec> set wall clock time limit\n"
#endif
);
} else { // Print complete list of all options.
printf (
" -h print alternatively only a list of common options\n"
" --help print this complete list of all options\n"
" --version print version\n"
"\n"
" -n do not print witness (same as '--no-witness')\n"
#ifndef QUIET
" -v increase verbosity (see also '--verbose' below)\n"
" -q be quiet (same as '--quiet')\n"
#endif
#ifndef __WIN32
" -t <sec> set wall clock time limit\n"
#endif
"\n"
"Or '<option>' is one of the less common options\n"
"\n"
" -L<rounds> run local search initially (default '0' rounds)\n"
" -O<level> increase limits by '2^<level>' or '10^<level>'\n"
" -P<rounds> initial preprocessing (default '0' rounds)\n"
"\n"
"Note there is no separating space for the options above while the\n"
"following options require a space after the option name:\n"
"\n"
" -c <limit> limit the number of conflicts (default unlimited)\n"
" -d <limit> limit the number of decisions (default unlimited)\n"
"\n"
" -o <output> write simplified CNF in DIMACS format to file\n"
" -e <extend> write reconstruction/extension stack to file\n"
#ifdef LOGGING
" -l enable logging messages (same as '--log')\n"
#endif
"\n"
" --force | -f parsing broken DIMACS header and writing proofs\n"
" --strict strict parsing (no white space in header)\n"
"\n"
" -r <sol> read solution in competition output format\n"
" to check consistency of learned clauses\n"
" during testing and debugging\n"
"\n"
" -w <sol> write result including a potential witness\n"
" solution in competition format to the given file\n"
"\n"
" --colors force colored output\n"
" --no-colors disable colored output to terminal\n"
" --no-witness do not print witness (see also '-n' above)\n"
"\n"
" --build print build configuration\n"
" --copyright print copyright information\n"
);
printf (
"\n"
"There are pre-defined configurations of advanced internal options:\n"
"\n");
solver->configurations ();
printf (
"\n"
"Or '<option>' is one of the following advanced internal options:\n"
"\n");
solver->usage ();
fputs (
"\n"
"The internal options have their default value printed in brackets\n"
"after their description. They can also be used in the form\n"
"'--<name>' which is equivalent to '--<name>=1' and in the form\n"
"'--no-<name>' which is equivalent to '--<name>=0'. One can also\n"
"use 'true' instead of '1', 'false' instead of '0', as well as\n"
"numbers with positive exponent such as '1e3' instead of '1000'.\n"
"\n"
"Alternatively option values can also be specified in the header\n"
"of the DIMACS file, e.g., 'c --elim=false', or through environment\n"
"variables, such as 'CADICAL_ELIM=false'. The embedded options in\n"
"the DIMACS file have highest priority, followed by command line\n"
"options and then values specified through environment variables.\n",
stdout);
}
//------------------------------------------------------------------------
// Common to both complete and common option usage.
fputs (
"\n"
"The input is read from '<input>' assumed to be in DIMACS format.\n"
"Incremental 'p inccnf' files are supported too with cubes at the end.\n"
"If '<proof>' is given then a DRAT proof is written to that file.\n",
stdout);
//------------------------------------------------------------------------
// More explanations for complete option usage.
if (all) {
fputs (
"\n"
"If '<input>' is missing then the solver reads from '<stdin>',\n"
"also if '-' is used as input path name '<input>'. Similarly,\n"
"\n"
"For incremental files each cube is solved in turn. The solver\n"
"stops at the first satisfied cube if there is one and uses that\n"
"one for the witness to print. Conflict and decision limits are\n"
"applied to each individual cube solving call while '-P', '-L'"
#ifdef __WIN32
"\n"
#else
" and\n"
"'-t' "
#endif
"remain global. Only if all cubes were unsatisfiable the solver\n"
"prints the standard unsatisfiable solution line ('s UNSATISFIABLE').\n"
"\n"
"By default the proof is stored in the binary DRAT format unless\n"
"the option '--no-binary' is specified or the proof is written\n"
"to '<stdout>' and '<stdout>' is connected to a terminal.\n"
"\n"
"The input is assumed to be compressed if it is given explicitly\n"
"and has a '.gz', '.bz2', '.xz' or '.7z' suffix. The same applies\n"
"to the output file. In order to use compression and decompression\n"
"the corresponding utilities 'gzip', 'bzip', 'xz', and '7z' (depending\n"
"on the format) are required and need to be installed on the system.\n"
"The solver checks file type signatures though and falls back to\n"
"non-compressed file reading if the signature does not match.\n",
stdout);
}
}
/*------------------------------------------------------------------------*/
// Pretty print competition format witness with 'v' lines.
void App::print_witness (FILE * file) {
int c = 0, i = 0, tmp;
do {
if (!c) fputc ('v', file), c = 1;
if (i++ == max_var) tmp = 0;
else tmp = solver->val (i) < 0 ? -i : i;
char str[20];
sprintf (str, " %d", tmp);
int l = strlen (str);
if (c + l > 78) fputs ("\nv", file), c = 1;
fputs (str, file);
c += l;
} while (tmp);
if (c) fputc ('\n', file);
}
/*------------------------------------------------------------------------*/
// Wrapper around option setting.
int App::get (const char * o) { return solver->get (o); }
bool App::set (const char * o, int v) { return solver->set (o, v); }
bool App::set (const char * arg) { return solver->set_long_option (arg); }
/*------------------------------------------------------------------------*/
bool App::most_likely_existing_cnf_file (const char * path)
{
if (!File::exists (path)) return false;
if (has_suffix (path, ".dimacs")) return true;
if (has_suffix (path, ".dimacs.gz")) return true;
if (has_suffix (path, ".dimacs.xz")) return true;
if (has_suffix (path, ".dimacs.bz2")) return true;
if (has_suffix (path, ".dimacs.7z")) return true;
if (has_suffix (path, ".dimacs.lzma")) return true;
if (has_suffix (path, ".cnf")) return true;
if (has_suffix (path, ".cnf.gz")) return true;
if (has_suffix (path, ".cnf.xz")) return true;
if (has_suffix (path, ".cnf.bz2")) return true;
if (has_suffix (path, ".cnf.7z")) return true;
if (has_suffix (path, ".cnf.lzma")) return true;
return false;
}
/*------------------------------------------------------------------------*/
// Short-cut for errors to avoid a hard 'exit'.
#define APPERR(...) \
do { solver->error (__VA_ARGS__); } while (0)
/*------------------------------------------------------------------------*/
int App::main (int argc, char ** argv) {
// Handle options which lead to immediate exit first.
if (argc == 2) {
const char * arg = argv[1];
if (!strcmp (arg, "-h")) {
print_usage ();
return 0;
} else if (!strcmp (arg, "--help")) {
print_usage (true);
return 0;
} else if (!strcmp (arg, "--version")) {
printf ("%s\n", CaDiCaL::version ());
return 0;
} else if (!strcmp (arg, "--build")) {
tout.disable ();
Solver::build (stdout, "");
return 0;
} else if (!strcmp (arg, "--copyright")) {
printf ("%s\n", copyright ());
return 0;
}
}
// Now initialize solver.
init ();
// Set all argument option values to not used yet.
const char * preprocessing_specified = 0, * optimization_specified = 0;
const char * read_solution_path = 0, * write_result_path = 0;
const char * dimacs_path = 0, * proof_path = 0;
bool proof_specified = false, dimacs_specified = false;
int optimize = 0, preprocessing = 0, localsearch = 0;
const char * output_path = 0, * extension_path = 0;
int conflict_limit = -1, decision_limit = -1;
const char * conflict_limit_specified = 0;
const char * decision_limit_specified = 0;
const char * localsearch_specified = 0;
const char * time_limit_specified = 0;
bool witness = true, less = false;
const char * dimacs_name, * err;
for (int i = 1; i < argc; i++) {
if (!strcmp (argv[i], "-h") ||
!strcmp (argv[i], "--help") ||
!strcmp (argv[i], "--build") ||
!strcmp (argv[i], "--version") ||
!strcmp (argv[i], "--copyright")) {
APPERR ("can only use '%s' as single first option", argv[i]);
} else if (!strcmp (argv[i], "-")) {
if (proof_specified) APPERR ("too many arguments");
else if (!dimacs_specified) dimacs_specified = true;
else proof_specified = true;
} else if (!strcmp (argv[i], "-r")) {
if (++i == argc) APPERR ("argument to '-r' missing");
else if (read_solution_path)
APPERR ("multiple read solution file options '-r %s' and '-r %s'",
read_solution_path, argv[i]);
else read_solution_path = argv[i];
} else if (!strcmp (argv[i], "-w")) {
if (++i == argc) APPERR ("argument to '-w' missing");
else if (write_result_path)
APPERR ("multiple solution file options '-w %s' and '-w %s'",
write_result_path, argv[i]);
else write_result_path = argv[i];
} else if (!strcmp (argv[i], "-o")) {
if (++i == argc) APPERR ("argument to '-o' missing");
else if (output_path)
APPERR ("multiple output file options '-o %s' and '-o %s'",
output_path, argv[i]);
else if (!force_writing &&
most_likely_existing_cnf_file (argv[i]))
APPERR ("output file '%s' most likely existing CNF (use '-f')",
argv[i]);
else if (!File::writable (argv[i]))
APPERR ("output file '%s' not writable", argv[i]);
else output_path = argv[i];
} else if (!strcmp (argv[i], "-e")) {
if (++i == argc) APPERR ("argument to '-e' missing");
else if (extension_path)
APPERR ("multiple extension file options '-e %s' and '-e %s'",
extension_path, argv[i]);
else if (!force_writing &&
most_likely_existing_cnf_file (argv[i]))
APPERR ("extension file '%s' most likely existing CNF (use '-f')",
argv[i]);
else if (!File::writable (argv[i]))
APPERR ("extension file '%s' not writable", argv[i]);
else extension_path = argv[i];
} else if (is_color_option (argv[i])) {
tout.force_colors ();
terr.force_colors ();
} else if (is_no_color_option (argv[i])) {
tout.force_no_colors ();
terr.force_no_colors ();
} else if (!strcmp (argv[i], "--witness") ||
!strcmp (argv[i], "--witness=true") ||
!strcmp (argv[i], "--witness=1"))
witness = true;
else if (!strcmp (argv[i], "-n") ||
!strcmp (argv[i], "--no-witness") ||
!strcmp (argv[i], "--witness=false") ||
!strcmp (argv[i], "--witness=0"))
witness = false;
else if (!strcmp (argv[i], "--less")) { // EXPERIMENTAL!
if (less) APPERR ("multiple '--less' options");
else if (!isatty (1))
APPERR ("'--less' without '<stdout>' connected to terminal");
else less = true;
} else if (!strcmp (argv[i], "-c")) {
if (++i == argc) APPERR ("argument to '-c' missing");
else if (conflict_limit_specified)
APPERR ("multiple conflict limits '-c %s' and '-c %s'",
conflict_limit_specified, argv[i]);
else if (!parse_int_str (argv[i], conflict_limit))
APPERR ("invalid argument in '-c %s'", argv[i]);
else if (conflict_limit < 0)
APPERR ("invalid conflict limit");
else conflict_limit_specified = argv[i];
} else if (!strcmp (argv[i], "-d")) {
if (++i == argc) APPERR ("argument to '-d' missing");
else if (decision_limit_specified)
APPERR ("multiple decision limits '-d %s' and '-d %s'",
decision_limit_specified, argv[i]);
else if (!parse_int_str (argv[i], decision_limit))
APPERR ("invalid argument in '-d %s'", argv[i]);
else if (decision_limit < 0)
APPERR ("invalid decision limit");
else decision_limit_specified = argv[i];
}
#ifndef __WIN32
else if (!strcmp (argv[i], "-t")) {
if (++i == argc) APPERR ("argument to '-t' missing");
else if (time_limit_specified)
APPERR ("multiple time limit '-t %s' and '-t %s'",
time_limit_specified, argv[i]);
else if (!parse_int_str (argv[i], time_limit))
APPERR ("invalid argument in '-d %s'", argv[i]);
else if (time_limit < 0)
APPERR ("invalid time limit");
else time_limit_specified = argv[i];
}
#endif
#ifndef QUIET
else if (!strcmp (argv[i], "-q")) set ("--quiet");
else if (!strcmp (argv[i], "-v"))
set ("verbose", get ("verbose") + 1);
#endif
#ifdef LOGGING
else if (!strcmp (argv[i], "-l")) set ("--log");
#endif
else if (!strcmp (argv[i], "-f") ||
!strcmp (argv[i], "--force") ||
!strcmp (argv[i], "--force=1") ||
!strcmp (argv[i], "--force=true"))
force_strict_parsing = 0,
force_writing = true;
else if (!strcmp (argv[i], "--strict") ||
!strcmp (argv[i], "--strict=1") ||
!strcmp (argv[i], "--strict=true"))
force_strict_parsing = 2;
else if (has_prefix (argv[i], "-O")) {
if (optimization_specified)
APPERR ("multiple optimization options '%s' and '%s'",
optimization_specified, argv[i]);
optimization_specified = argv[i];
if (!parse_int_str (argv[i] + 2, optimize))
APPERR ("invalid optimization option '%s'", argv[i]);
if (optimize < 0 || optimize > 31)
APPERR ("invalid argument in '%s' (expected '0..31')", argv[i]);
} else if (has_prefix (argv[i], "-P")) {
if (preprocessing_specified)
APPERR ("multiple preprocessing options '%s' and '%s'",
preprocessing_specified, argv[i]);
preprocessing_specified = argv[i];
if (!parse_int_str (argv[i] + 2, preprocessing))
APPERR ("invalid preprocessing option '%s'", argv[i]);
if (preprocessing < 0)
APPERR ("invalid argument in '%s' (expected non-negative number)",
argv[i]);
} else if (has_prefix (argv[i], "-L")) {
if (localsearch_specified)
APPERR ("multiple local search options '%s' and '%s'",
localsearch_specified, argv[i]);
localsearch_specified = argv[i];
if (!parse_int_str (argv[i] + 2, localsearch))
APPERR ("invalid local search option '%s'", argv[i]);
if (localsearch < 0)
APPERR ("invalid argument in '%s' (expected non-negative number)",
argv[i]);
} else if (has_prefix (argv[i], "--") &&
solver->is_valid_configuration (argv[i] + 2)) {
solver->configure (argv[i] + 2);
} else if (set (argv[i])) {
/* nothing do be done */
} else if (argv[i][0] == '-') APPERR ("invalid option '%s'", argv[i]);
else if (proof_specified) APPERR ("too many arguments");
else if (dimacs_specified) {
proof_path = argv[i];
proof_specified = true;
if (!force_writing &&
most_likely_existing_cnf_file (proof_path))
APPERR ("DRAT proof file '%s' most likely existing CNF (use '-f')",
proof_path);
else if (!File::writable (proof_path))
APPERR ("DRAT proof file '%s' not writable", proof_path);
} else dimacs_specified = true, dimacs_path = argv[i];
}
/*----------------------------------------------------------------------*/
if (dimacs_specified && dimacs_path && !File::exists (dimacs_path))
APPERR ("DIMACS input file '%s' does not exist", dimacs_path);
if (read_solution_path && !File::exists (read_solution_path))
APPERR ("solution file '%s' does not exist", read_solution_path);
if (dimacs_specified && dimacs_path &&
proof_specified && proof_path &&
!strcmp (dimacs_path, proof_path) && strcmp (dimacs_path, "-"))
APPERR ("DIMACS input file '%s' also specified as DRAT proof file",
dimacs_path);
/*----------------------------------------------------------------------*/
// The '--less' option is not fully functional yet (it is also not
// mentioned in the 'usage' message yet). It only works as expected if
// you let the solver run until it exits. The goal is to have a similar
// experience as with 'git diff' if the terminal is too small for the
// printed messages, but this needs substantial hacking.
//
// TODO: add proper forking, waiting, signal catching & propagating ...
//
FILE * less_pipe;
if (less) {
assert (isatty (1));
less_pipe = popen ("less -r", "w");
if (!less_pipe)
APPERR ("could not execute and open pipe to 'less -r' command");
dup2 (fileno (less_pipe), 1);
} else less_pipe = 0;
/*----------------------------------------------------------------------*/
if (read_solution_path && !get ("check")) set ("--check");
#ifndef QUIET
if (!get ("quiet")) {
solver->section ("banner");
solver->message ("%sCaDiCaL Radically Simplified CDCL SAT Solver%s",
tout.bright_magenta_code (), tout.normal_code ());
solver->message ("%s%s%s",
tout.bright_magenta_code (), copyright (), tout.normal_code ());
solver->message ();
CaDiCaL::Solver::build (stdout, "c ");
}
#endif
if (preprocessing > 0 || localsearch > 0 ||
#ifndef __WIN32
time_limit >= 0 ||
#endif
conflict_limit >= 0 || decision_limit >= 0) {
solver->section ("limit");
if (preprocessing > 0) {
solver->message (
"enabling %d initial rounds of preprocessing (due to '%s')",
preprocessing, preprocessing_specified);
solver->limit ("preprocessing", preprocessing);
}
if (localsearch > 0) {
solver->message (
"enabling %d initial rounds of local search (due to '%s')",
localsearch, localsearch_specified);
solver->limit ("localsearch", localsearch);
}
#ifndef __WIN32
if (time_limit >= 0) {
solver->message (
"setting time limit to %d seconds real time (due to '-t %s')",
time_limit, time_limit_specified);
Signal::alarm (time_limit);
solver->connect_terminator (this);
}
#endif
if (conflict_limit >= 0) {
solver->message (
"setting conflict limit to %d conflicts (due to '%s')",
conflict_limit, conflict_limit_specified);
bool succeeded = solver->limit ("conflicts", conflict_limit);
assert (succeeded), (void) succeeded;
}
if (decision_limit >= 0) {
solver->message (
"setting decision limit to %d decisions (due to '%s')",
decision_limit, decision_limit_specified);
bool succeeded = solver->limit ("decisions", decision_limit);
assert (succeeded), (void) succeeded;
}
}
if (verbose () || proof_specified) solver->section ("proof tracing");
if (proof_specified) {
if (!proof_path) {
const bool force_binary = (isatty (1) && get ("binary"));
if (force_binary) set ("--no-binary");
solver->message ("writing %s proof trace to %s'<stdout>'%s",
(get ("binary") ? "binary" : "non-binary"),
tout.green_code (), tout.normal_code ());
if (force_binary)
solver->message (
"connected to terminal thus non-binary proof forced");
solver->trace_proof (stdout, "<stdout>");
} else if (!solver->trace_proof (proof_path))
APPERR ("can not open and write DRAT proof to '%s'", proof_path);
else
solver->message (
"writing %s proof trace to %s'%s'%s",
(get ("binary") ? "binary" : "non-binary"),
tout.green_code (), proof_path, tout.normal_code ());
} else solver->verbose (1, "will not generate nor write DRAT proof");
solver->section ("parsing input");
dimacs_name = dimacs_path ? dimacs_path : "<stdin>";
string help;
if (!dimacs_path) {
help += " ";
help += tout.magenta_code ();
help += "(use '-h' for a list of common options)";
help += tout.normal_code ();
}
solver->message ("reading DIMACS file from %s'%s'%s%s",
tout.green_code (), dimacs_name, tout.normal_code (), help.c_str ());
bool incremental;
vector<int> cube_literals;
if (dimacs_path)
err = solver->read_dimacs(dimacs_path, max_var, force_strict_parsing,
incremental, cube_literals);
else
err = solver->read_dimacs(stdin, dimacs_name, max_var, force_strict_parsing,
incremental, cube_literals);
if (err) APPERR ("%s", err);
if (read_solution_path) {
solver->section ("parsing solution");
solver->message ("reading solution file from '%s'", read_solution_path);
if ((err = solver->read_solution (read_solution_path)))
APPERR ("%s", err);
}
solver->section ("options");
if (optimize > 0) {
solver->optimize (optimize);
solver->message ();
}
solver->options ();
int res = 0;
if (incremental) {
bool reporting = get ("report") > 1 || get ("verbose") > 0;
if (!reporting) set ("report", 0);
if (!reporting) solver->section ("incremental solving");
size_t cubes = 0, solved = 0;
size_t satisfiable = 0, unsatisfiable = 0, inconclusive = 0;
#ifndef QUIET
bool quiet = get ("quiet");
struct { double start, delta, sum; } time = { 0, 0, 0 };
#endif
for (auto lit : cube_literals)
if (!lit)
cubes++;
if (!reporting) {
if (cubes)
solver->message ("starting to solve %zu cubes", cubes),
solver->message ();
else
solver->message ("no cube to solve");
}
vector<int> cube, failed;
for (auto lit : cube_literals) {
if (lit) cube.push_back (lit);
else {
reverse (cube.begin (), cube.end ());
for (auto other : cube)
solver->assume (other);
if (solved++) {
if (conflict_limit >= 0)
(void) solver->limit ("conflicts", conflict_limit);
if (decision_limit >= 0)
(void) solver->limit ("decisions", decision_limit);
}
#ifndef QUIET
char buffer[160];
if (!quiet) {
if (reporting) {
sprintf (buffer, "solving cube %zu / %zu %.0f%%",
solved, cubes, percent (solved, cubes));
solver->section (buffer);
}
time.start = absolute_process_time ();
}
#endif
res = solver->solve ();
#ifndef QUIET
if (!quiet) {
time.delta = absolute_process_time () - time.start;
time.sum += time.delta;
sprintf (buffer,
"%s"
"in %.3f sec "
"(%.0f%% after %.2f sec at %.0f ms/cube)"
"%s",
tout.magenta_code (),
time.delta,
percent (solved, cubes),
time.sum,
relative (1e3*time.sum, solved),
tout.normal_code ());
if (reporting)
solver->message ();
const char * cube_str, * status_str, * color_code;
if (res == 10) {
cube_str = "CUBE";
color_code = tout.green_code ();
status_str = "SATISFIABLE";
} else if (res == 20) {
cube_str = "CUBE";
color_code = tout.cyan_code ();
status_str = "UNSATISFIABLE";
} else {
cube_str = "cube";
color_code = tout.magenta_code ();
status_str = "inconclusive";
}
const char * fmt;
if (reporting) fmt = "%s%s %zu %s%s %s";
else fmt = "%s%s %zu %-13s%s %s";
solver->message (fmt,
color_code, cube_str, solved, status_str,
tout.normal_code (), buffer);
}
#endif
if (res == 10) {
satisfiable++;
break;
} else if (res == 20) {
unsatisfiable++;
for (auto other : cube)
if (solver->failed (other))
failed.push_back (other);
for (auto other : failed)
solver->add (-other);
solver->add (0);
failed.clear ();
} else {
assert (!res);
inconclusive++;
if (timesup)
break;
}
cube.clear ();
}
}
solver->section ("incremental summary");
solver->message ("%zu cubes solved %.0f%%",
solved, percent (solved, cubes));
solver->message ("%zu cubes inconclusive %.0f%%",
inconclusive, percent (inconclusive, solved));
solver->message ("%zu cubes unsatisfiable %.0f%%",
unsatisfiable, percent (unsatisfiable, solved));
solver->message ("%zu cubes satisfiable %.0f%%",
satisfiable, percent (satisfiable, solved));
if (inconclusive && res == 20)
res = 0;
} else {
solver->section ("solving");
res = solver->solve ();
}
if (proof_specified) {
solver->section ("closing proof");
solver->flush_proof_trace ();
solver->close_proof_trace ();
}
if (output_path) {
solver->section ("writing output");
solver->message ("writing simplified CNF to DIMACS file %s'%s'%s",
tout.green_code (), output_path, tout.normal_code ());
err = solver->write_dimacs (output_path, max_var);
if (err) APPERR ("%s", err);
}
if (extension_path) {
solver->section ("writing extension");
solver->message ("writing extension stack to %s'%s'%s",
tout.green_code (), extension_path, tout.normal_code ());
err = solver->write_extension (extension_path);
if (err) APPERR ("%s", err);
}
solver->section ("result");
FILE * write_result_file = stdout;
if (write_result_path)
{
write_result_file = fopen (write_result_path, "w");
if (!write_result_file)
APPERR ("could not write solution to '%s'", write_result_path);
solver->message ("writing result to '%s'", write_result_path);
}
if (res == 10) {
fputs ("s SATISFIABLE\n", write_result_file);
if (witness)
print_witness (write_result_file);
} else if (res == 20) fputs ("s UNSATISFIABLE\n", write_result_file);
else fputs ("c UNKNOWN\n", write_result_file);
fflush (write_result_file);
if (write_result_path)
fclose (write_result_file);
solver->statistics ();
solver->resources ();
solver->section ("shutting down");
solver->message ("exit %d", res);
if (less_pipe) {
close (1);
pclose (less_pipe);
}
#ifndef __WIN32
if (time_limit > 0)
alarm (0);
#endif
return res;
}
/*------------------------------------------------------------------------*/
// The real initialization is delayed.
void App::init () {
assert (!solver);
#ifndef __WIN32
time_limit = -1;
#endif
force_strict_parsing = 1;
force_writing = false;
max_var = 0;
timesup = false;
// Call 'new Solver' only after setting 'reportdefault' and do not
// add this call to the member initialization above. This is because for
// stand-alone usage the default report default value is 'true' while for
// library usage it should remain 'false'. See the explanation in
// 'options.hpp' related to 'reportdefault' for details.
CaDiCaL::Options::reportdefault = 1;
solver = new Solver ();
Signal::set (this);
}
/*------------------------------------------------------------------------*/
App::App () : solver (0) { } // Only partially initialize the app.
App::~App () {
if (!solver) return; // Only partially initialized.
Signal::reset ();
delete solver;
}
/*------------------------------------------------------------------------*/
#ifndef QUIET
void App::signal_message (const char * msg, int sig) {
solver->message (
"%s%s %ssignal %d%s (%s)%s",
tout.red_code (), msg,
tout.bright_red_code (), sig,
tout.red_code (), Signal::name (sig),
tout.normal_code ());
}
#endif
void App::catch_signal (int sig) {
#ifndef QUIET
if (!get ("quiet")) {
solver->message ();
signal_message ("caught", sig);
solver->section ("result");
solver->message ("UNKNOWN");
solver->statistics ();
solver->resources ();
solver->message ();
signal_message ("raising", sig);
}
#else
(void) sig;
#endif
}
void App::catch_alarm () {
// Both approaches work. We keep them here for illustration purposes.
#if 0 // THIS IS AN ALTERNATIVE WE WANT TO KEEP AROUND.
solver->terminate (); // Immediate asynchronous call into solver.
#else
timesup = true; // Wait for solver to call 'App::terminate ()'.
#endif
}
} // end of 'namespace CaDiCaL'
/*------------------------------------------------------------------------*/
// The actual app is allocated on the stack and then its 'main' function is
// called. This looks like that there are no static variables, which is not
// completely true, since both the signal handler connected to the app as
// well as the terminal have statically allocated components as well as the
// options table 'Options::table'. All are shared among solvers.
int main (int argc, char ** argv) {
CaDiCaL::App app;
int res = app.main (argc, argv);
return res;
}

891
hCaD_V2/src/cadical.hpp Normal file
View File

@ -0,0 +1,891 @@
#ifndef _cadical_hpp_INCLUDED
#define _cadical_hpp_INCLUDED
#include <cstdio>
#include <cstdint>
#include <vector>
namespace CaDiCaL {
/*========================================================================*/
// This provides the actual API of the CaDiCaL solver, which is implemented
// in the class 'Solver' below. Beside its constructor and destructor most
// important is the IPASIR part which you can find between 'BEGIN IPASIR'
// and 'END IPASIR' comments below. The following '[Example]' below might
// also be a good starting point to understand the API.
/*========================================================================*/
// [Example]
//
// The internal solver state follows the IPASIR API model used in the
// incremental track of the SAT competition. State transitions are
// triggered by member function calls, declared and described below.
//
// Consider the following code (from 'test/api/example.cpp') of API usage:
//
// CaDiCaL::Solver * solver = new CaDiCaL::Solver;
//
// // ------------------------------------------------------------------
// // Encode Problem and check without assumptions.
//
// enum { TIE = 1, SHIRT = 2 };
//
// solver->add (-TIE), solver->add (SHIRT), solver->add (0);
// solver->add (TIE), solver->add (SHIRT), solver->add (0);
// solver->add (-TIE), solver->add (-SHIRT), solver->add (0);
//
// int res = solver->solve (); // Solve instance.
// assert (res == 10); // Check it is 'SATISFIABLE'.
//
// res = solver->val (TIE); // Obtain assignment of 'TIE'.
// assert (res < 0); // Check 'TIE' assigned to 'false'.
//
// res = solver->val (SHIRT); // Obtain assignment of 'SHIRT'.
// assert (res > 0); // Check 'SHIRT' assigned to 'true'.
//
// // ------------------------------------------------------------------
// // Incrementally solve again under one assumption.
//
// solver->assume (TIE); // Now force 'TIE' to true.
//
// res = solver->solve (); // Solve again incrementally.
// assert (res == 20); // Check it is 'UNSATISFIABLE'.
//
// res = solver->failed (TIE); // Check 'TIE' responsible.
// assert (res); // Yes, 'TIE' in core.
//
// res = solver->failed (SHIRT); // Check 'SHIRT' responsible.
// assert (!res); // No, 'SHIRT' not in core.
//
// // ------------------------------------------------------------------
// // Incrementally solve once more under another assumption.
//
// solver->assume (-SHIRT); // Now force 'SHIRT' to false.
//
// res = solver->solve (); // Solve again incrementally.
// assert (res == 20); // Check it is 'UNSATISFIABLE'.
//
// res = solver->failed (TIE); // Check 'TIE' responsible.
// assert (!res); // No, 'TIE' not in core.
//
// res = solver->failed (-SHIRT); // Check '!SHIRT' responsible.
// assert (res); // Yes, '!SHIRT' in core.
//
// // ------------------------------------------------------------------
//
// delete solver;
/*========================================================================*/
// [States and Transitions]
//
// Compared to IPASIR we also use an 'ADDING' state in which the solver
// stays while adding non-zero literals until the clause is completed
// through adding a zero literal. The additional 'INITIALIZING',
// 'CONFIGURING' and 'DELETING' states are also not part of IPASIR but also
// useful for testing and debugging.
//
// We have the following transitions which are all synchronous except for
// the reentrant 'terminate' call:
//
// new
// INITIALIZING --------------------------> CONFIGURING
//
// set / trace
// CONFIGURING --------------------------> CONFIGURING
//
// add (non zero literal)
// VALID --------------------------> ADDING
//
// add (zero literal)
// VALID --------------------------> UNKNOWN
//
// assume (non zero literal)
// READY --------------------------> UNKNOWN
//
// solve
// READY --------------------------> SOLVING
//
// (internal)
// SOLVING --------------------------> READY
//
// val (non zero literal)
// SATISFIED --------------------------> SATISFIED
//
// failed (non zero literal )
// UNSATISFIED --------------------------> UNSATISFIED
//
// delete
// VALID --------------------------> DELETING
//
// where
//
// READY = CONFIGURING | UNKNOWN | SATISFIED | UNSATISFIED
// VALID = READY | ADDING
// INVALID = INITIALIZING | DELETING
//
// The 'SOLVING' state is only visible in different contexts, i.e., from
// another thread or from a signal handler. It is used to implement
// 'terminate'. Here is the only asynchronous transition:
//
// terminate (asynchronously)
// SOLVING -------------------------> UNKNOWN
//
// The important behaviour to remember is that adding or assuming a literal
// (immediately) destroys the satisfying assignment in the 'SATISFIED' state
// and vice versa resets all assumptions in the 'UNSATISFIED' state. This
// is exactly the behaviour required by the IPASIR interface.
//
// Furthermore, the model can only be queried through 'val' in the
// 'SATISFIED' state, while extracting failed assumptions with 'val' only in
// the 'UNSATISFIED' state. Solving can only be started in the 'UNKNOWN' or
// 'CONFIGURING' state or after the previous call to 'solve' yielded an
// 'UNKNOWN, 'SATISFIED' or 'UNSATISFIED' state.
//
// All literals have to be valid literals too, i.e., 32-bit integers
// different from 'INT_MIN'. If any of these requirements is violated the
// solver aborts with an 'API contract violation' message.
//
// HINT: If you do not understand why a contract is violated you can run
// 'mobical' on the failing API call trace. Point the environment variable
// 'CADICAL_API_TRACE' to the file where you want to save the trace during
// execution of your program linking against the library. You probably need
// for 'mobical' to use the option '--do-not-enforce-contracts' though to
// force running into the same contract violation.
//
// Additional API calls (like 'freeze' and 'melt') do not change the state
// of the solver and are all described below.
/*========================================================================*/
// States are represented by a bit-set in order to combine them.
enum State
{
INITIALIZING = 1, // during initialization (invalid)
CONFIGURING = 2, // configure options (with 'set')
UNKNOWN = 4, // ready to call 'solve'
ADDING = 8, // adding clause literals (zero missing)
SOLVING = 16, // while solving (within 'solve')
SATISFIED = 32, // satisfiable allows 'val'
UNSATISFIED = 64, // unsatisfiable allows 'failed'
DELETING = 128, // during and after deletion (invalid)
// These combined states are used to check contracts.
READY = CONFIGURING | UNKNOWN | SATISFIED | UNSATISFIED,
VALID = READY | ADDING,
INVALID = INITIALIZING | DELETING
};
/*------------------------------------------------------------------------*/
// Opaque classes needed in the API and declared in the same namespace.
class File;
struct Internal;
struct External;
/*------------------------------------------------------------------------*/
// Forward declaration of call-back classes. See bottom of this file.
class Learner;
class Terminator;
class ClauseIterator;
class WitnessIterator;
/*------------------------------------------------------------------------*/
class Solver {
public:
// ====== BEGIN IPASIR ===================================================
// This section implements the corresponding IPASIR functionality.
Solver ();
~Solver ();
static const char * signature (); // name of this library
// Core functionality as in the IPASIR incremental SAT solver interface.
// (recall 'READY = CONFIGURING | UNKNOWN | SATISFIED | UNSATISFIED').
// Further note that 'lit' is required to be different from 'INT_MIN' and
// different from '0' except for 'add'.
// Add valid literal to clause or zero to terminate clause.
//
// require (VALID) // recall 'VALID = READY | ADDING'
// if (lit) ensure (ADDING) // and thus VALID but not READY
// if (!lit) ensure (UNKNOWN) // and thus READY
//
void add (int lit);
// Assume valid non zero literal for next call to 'solve'. These
// assumptions are reset after the call to 'solve' as well as after
// returning from 'simplify' and 'lookahead.
//
// require (READY)
// ensure (UNKNOWN)
//
void assume (int lit);
// Try to solve the current formula. Returns
//
// 0 = UNSOLVED (limit reached or interrupted through 'terminate')
// 10 = SATISFIABLE
// 20 = UNSATISFIABLE
//
// require (READY)
// ensure (UNKNOWN | SATISFIED | UNSATISFIED)
//
// Note, that while in this call the solver actually transitions to state
// 'SOLVING', which however is only visible from a different context,
// i.e., from a different thread or from a signal handler. Only right
// before returning from this call it goes into a 'READY' state.
//
int solve ();
// Get value (-lit=false, lit=true) of valid non-zero literal.
//
// require (SATISFIED)
// ensure (SATISFIED)
//
int val (int lit);
// Determine whether the valid non-zero literal is in the core.
// Returns 'true' if the literal is in the core and 'false' otherwise.
// Note that the core does not have to be minimal.
//
// require (UNSATISFIED)
// ensure (UNSATISFIED)
//
bool failed (int lit);
// Add call-back which is checked regularly for termination. There can
// only be one terminator connected. If a second (non-zero) one is added
// the first one is implicitly disconnected.
//
// require (VALID)
// ensure (VALID)
//
void connect_terminator (Terminator * terminator);
void disconnect_terminator ();
// Add call-back which allows to export learned clauses.
//
// require (VALID)
// ensure (VALID)
//
void connect_learner (Learner * learner);
void disconnect_learner ();
// ====== END IPASIR =====================================================
//------------------------------------------------------------------------
// This function determines a good splitting literal. The result can be
// zero if the formula is proven to be satisfiable or unsatisfiable. This
// can then be checked by 'state ()'. If the formula is empty and
// the function is not able to determine satisfiability also zero is
// returned but the state remains unknown.
//
// require (READY)
// ensure (UNKNOWN|SATISFIED|UNSATISFIED)
//
int lookahead(void);
struct CubesWithStatus {
int status;
std::vector<std::vector<int>> cubes;
};
CubesWithStatus generate_cubes(int, int min_depth = 0);
void reset_assumptions();
// Return the current state of the solver as defined above.
//
const State & state () const { return _state; }
// Similar to 'state ()' but using the staddard competition exit codes of
// '10' for 'SATISFIABLE', '20' for 'UNSATISFIABLE' and '0' otherwise.
//
int status () const {
if (_state == SATISFIED) return 10;
else if (_state == UNSATISFIED) return 20;
else return 0;
}
/*----------------------------------------------------------------------*/
static const char * version (); // return version string
/*----------------------------------------------------------------------*/
// Copy 'this' into a fresh 'other'. The copy procedure is not a deep
// clone, but only copies irredundant clauses and units. It also makes
// sure that witness reconstruction works with the copy as with the
// original formula such that both solvers have the same models.
// Assumptions are not copied. Options however are copied as well as
// flags which remember the current state of variables in preprocessing.
//
// require (READY) // for 'this'
// ensure (READY) // for 'this'
//
// other.require (CONFIGURING)
// other.ensure (CONFIGURING | UNKNOWN)
//
void copy (Solver & other) const;
/*----------------------------------------------------------------------*/
// Variables are usually added and initialized implicitly whenever a
// literal is used as an argument except for the functions 'val', 'fixed',
// 'failed' and 'frozen'. However, the library internally keeps a maximum
// variable index, which can be queried.
//
// require (VALID | SOLVING)
// ensure (VALID | SOLVING)
//
int vars ();
// Increase the maximum variable index explicitly. This function makes
// sure that at least 'min_max_var' variables are initialized. Since it
// might need to reallocate tables, it destroys a satisfying assignment
// and has the same state transition and conditions as 'assume' etc.
//
// require (READY)
// ensure (UNKNOWN)
//
void reserve (int min_max_var);
#ifndef NTRACING
//------------------------------------------------------------------------
// This function can be used to write API calls to a file. The same
// format is used which 'mobical' can read, execute and also shrink
// through delta debugging.
//
// Tracing API calls can also be achieved by using the environment
// variable 'CADICAL_API_TRACE'. That alternative is useful if you do not
// want to change the source code using the solver, e.g., if you only have
// a binary with the solver linked in. However, that method only allows
// to trace one solver instance, while with the following function API
// tracing can be enabled for different solver instances individually.
//
// The solver will flush the file after every trace API call but does not
// close it during deletion. It remains owned by the user of the library.
//
// require (VALID)
// ensure (VALID)
//
void trace_api_calls (FILE * file);
#endif
//------------------------------------------------------------------------
// Option handling.
// Determine whether 'name' is a valid option name.
//
static bool is_valid_option (const char * name);
// Determine whether 'name' enables a specific preprocessing technique.
//
static bool is_preprocessing_option (const char * name);
// Determine whether 'arg' is a valid long option of the form '--<name>',
// '--<name>=<val>' or '--no-<name>' similar to 'set_long_option' below.
// Legal values are 'true', 'false', or '[-]<mantissa>[e<exponent>]'.
static bool is_valid_long_option (const char * arg);
// Get the current value of the option 'name'. If 'name' is invalid then
// zero is returned. Here '--...' arguments as invalid options.
//
int get (const char * name);
// Set the default verbose message prefix (default "c ").
//
void prefix (const char * verbose_message_prefix);
// Explicit version of setting an option. If the option '<name>' exists
// and '<val>' can be parsed then 'true' is returned. If the option value
// is out of range the actual value is computed as the closest (minimum or
// maximum) value possible, but still 'true' is returned.
//
// require (CONFIGURING)
// ensure (CONFIGURING)
//
// Thus options can only bet set right after initialization.
//
bool set (const char * name, int val);
// This function accepts options in command line syntax:
//
// '--<name>=<val>', '--<name>', or '--no-<name>'
//
// It actually calls the previous 'set' function after parsing 'arg'. The
// same values are expected as for 'is_valid_long_option' above and as
// with 'set' any value outside of the range of legal values for a
// particular option are set to either the minimum or maximum depending on
// which side of the valid interval they lie.
//
// require (CONFIGURING)
// ensure (CONFIGURING)
//
bool set_long_option (const char * arg);
// Determine whether 'name' is a valid configuration.
//
static bool is_valid_configuration (const char *);
// Overwrite (some) options with the forced values of the configuration.
// The result is 'true' iff the 'name' is a valid configuration.
//
// require (CONFIGURING)
// ensure (CONFIGURING)
//
bool configure (const char *);
// Increase preprocessing and inprocessing limits by '10^<val>'. Values
// below '0' are ignored and values above '9' are reduced to '9'.
//
// require (READY)
// ensure (READY)
//
void optimize (int val);
// Specify search limits, where currently 'name' can be "conflicts",
// "decisions", "preprocessing", or "localsearch". The first two limits
// are unbounded by default. Thus using a negative limit for conflicts or
// decisions switches back to the default of unlimited search (for that
// particular limit). The preprocessing limit determines the number of
// preprocessing rounds, which is zero by default. Similarly, the local
// search limit determines the number of local search rounds (also zero by
// default). As with 'set', the return value denotes whether the limit
// 'name' is valid. These limits are only valid for the next 'solve' or
// 'simplify' call and reset to their default after 'solve' returns (as
// well as overwritten and reset during calls to 'simplify' and
// 'lookahead'). We actually also have an internal "terminate" limit
// which however should only be used for testing and debugging.
//
// require (READY)
// ensure (READY)
//
bool limit (const char * arg, int val);
bool is_valid_limit (const char * arg);
// The number of currently active variables and clauses can be queried by
// these functions. Variables become active if a clause is added with it.
// They become inactive if they are eliminated or fixed at the root level
// Clauses become inactive if they are satisfied, subsumed, eliminated.
// Redundant clauses are reduced regularly and thus the 'redundant'
// function is less useful.
//
// require (VALID)
// ensure (VALID)
//
int active () const; // Number of active variables.
int64_t redundant () const; // Number of active redundant clauses.
int64_t irredundant () const; // Number of active irredundant clauses.
//------------------------------------------------------------------------
// This function executes the given number of preprocessing rounds. It is
// similar to 'solve' with 'limits ("preprocessing", rounds)' except that
// no CDCL nor local search, nor lucky phases are executed. The result
// values are also the same: 0=unknown, 10=satisfiable, 20=unsatisfiable.
// As 'solve' it resets current assumptions and limits before returning.
// The numbers of rounds should not be negative. If the number of rounds
// is zero only clauses are restored (if necessary) and top level unit
// propagation is performed, which both take some time.
//
// require (READY)
// ensure (UNKNOWN | SATISFIED | UNSATISFIED)
//
int simplify (int rounds = 3);
//------------------------------------------------------------------------
// Force termination of 'solve' asynchronously.
//
// require (SOLVING | READY)
// ensure (UNKNOWN) // actually not immediately (synchronously)
//
void terminate ();
//------------------------------------------------------------------------
// We have the following common reference counting functions, which avoid
// to restore clauses but require substantial user guidance. This was the
// only way to use inprocessing in incremental SAT solving in Lingeling
// (and before in MiniSAT's 'freeze' / 'thaw') and which did not use
// automatic clause restoring. In general this is slower than
// restoring clauses and should not be used.
//
// In essence the user freezes variables which potentially are still
// needed in clauses added or assumptions used after the next 'solve'
// call. As in Lingeling you can freeze a variable multiple times, but
// then have to melt it the same number of times again in order to enable
// variable eliminating on it etc. The arguments can be literals
// (negative indices) but conceptually variables are frozen.
//
// In the old way of doing things without restore you should not use a
// variable incrementally (in 'add' or 'assume'), which was used before
// and potentially could have been eliminated in a previous 'solve' call.
// This can lead to spurious satisfying assignment. In order to check
// this API contract one can use the 'checkfrozen' option. This has the
// drawback that restoring clauses implicitly would fail with a fatal
// error message even if in principle the solver could just restore
// clauses. Thus this option is disabled by default.
//
// See our SAT'19 paper [FazekasBiereScholl-SAT'19] for more details.
//
// require (VALID)
// ensure (VALID)
//
bool frozen (int lit) const;
void freeze (int lit);
void melt (int lit); // Also needs 'require (frozen (lit))'.
//------------------------------------------------------------------------
// Root level assigned variables can be queried with this function.
// It returns '1' if the literal is implied by the formula, '-1' if its
// negation is implied, or '0' if this is unclear at this point.
//
// require (VALID)
// ensure (VALID)
//
int fixed (int lit) const;
//------------------------------------------------------------------------
// Force the default decision phase of a variable to a certain value.
//
void phase (int lit);
void unphase (int lit);
//------------------------------------------------------------------------
// Enables clausal proof tracing in DRAT format and returns 'true' if
// successfully opened for writing. Writing proofs has to be enabled
// before calling 'solve', 'add' and 'dimacs', that is in state
// 'CONFIGURING'. Otherwise only partial proofs would be written.
//
// require (CONFIGURING)
// ensure (CONFIGURING)
//
bool trace_proof (FILE * file, const char * name); // Write DRAT proof.
bool trace_proof (const char * path); // Open & write proof.
// Flush proof trace file.
//
// require (VALID)
// ensure (VALID)
//
void flush_proof_trace ();
// Close proof trace early.
//
// require (VALID)
// ensure (VALID)
//
void close_proof_trace ();
//------------------------------------------------------------------------
static void usage (); // print usage information for long options
static void configurations (); // print configuration usage options
// require (!DELETING)
// ensure (!DELETING)
//
void statistics (); // print statistics
void resources (); // print resource usage (time and memory)
// require (VALID)
// ensure (VALID)
//
void options (); // print current option and value list
//------------------------------------------------------------------------
// Traverse irredundant clauses or the extension stack in reverse order.
//
// The return value is false if traversal is aborted early due to one of
// the visitor functions returning false. See description of the
// iterators below for more details on how to use these functions.
//
// require (VALID)
// ensure (VALID)
//
bool traverse_clauses (ClauseIterator &) const;
bool traverse_witnesses_backward (WitnessIterator &) const;
bool traverse_witnesses_forward (WitnessIterator &) const;
//------------------------------------------------------------------------
// Files with explicit path argument support compressed input and output
// if appropriate helper functions 'gzip' etc. are available. They are
// called through opening a pipe to an external command.
//
// If the 'strict' argument is zero then the number of variables and
// clauses specified in the DIMACS headers are ignored, i.e., the header
// 'p cnf 0 0' is always legal. If the 'strict' argument is larger '1'
// strict formatting of the header is required, i.e., single spaces
// everywhere and no trailing white space.
//
// Returns zero if successful and otherwise an error message.
//
// require (VALID)
// ensure (VALID)
//
const char * read_dimacs (FILE * file,
const char * name, int & vars, int strict = 1);
const char * read_dimacs (const char * path, int & vars, int strict = 1);
// The following routines work the same way but parse both DIMACS and
// INCCNF files (with 'p inccnf' header and 'a <cube>' lines). If the
// parser finds and 'p inccnf' header or cubes then '*incremental' is set
// to true and the cubes are stored in the given vector (each cube
// terminated by a zero).
const char * read_dimacs (FILE * file,
const char * name, int & vars, int strict,
bool & incremental, std::vector<int> & cubes);
const char * read_dimacs (const char * path, int & vars, int strict,
bool & incremental, std::vector<int> & cubes);
//------------------------------------------------------------------------
// Write current irredundant clauses and all derived unit clauses
// to a file in DIMACS format. Clauses on the extension stack are
// not included, nor any redundant clauses.
//
// The 'min_max_var' parameter gives a lower bound on the number '<vars>'
// of variables used in the DIMACS 'p cnf <vars> ...' header.
//
// Returns zero if successful and otherwise an error message.
//
// require (VALID)
// ensure (VALID)
//
const char * write_dimacs (const char * path, int min_max_var = 0);
// The extension stack for reconstruction a solution can be written too.
//
const char * write_extension (const char * path);
// Print build configuration to a file with prefix 'c '. If the file
// is '<stdout>' or '<stderr>' then terminal color codes might be used.
//
static void build (FILE * file, const char * prefix = "c ");
private:
//==== start of state ====================================================
State _state; // API states as discussed above.
/*----------------------------------------------------------------------*/
// The 'Solver' class is a 'facade' object for 'External'. It exposes the
// public API of 'External' but hides everything else (except for the some
// private functions). It is supposed to make it easier to understand the
// API and use the solver through the API.
// This approach has the benefit of decoupling this header file from all
// internal data structures, which is particularly useful if the rest of
// the source is not available. For instance if only a CaDiCaL library is
// installed in a system, then only this header file has to be installed
// too, and still allows to compile and link against the library.
/*----------------------------------------------------------------------*/
// More precisely the CaDiCaL code is split into three layers:
//
// Solver: facade object providing the actual API of the solver
// External: communication layer between 'Solver' and 'Internal'
// Internal: the actual solver code
//
// The 'External' and 'Internal' layers are declared and implemented in
// the corresponding '{external,internal}.{hpp,cpp}' files (as expected),
// while the 'Solver' facade class is defined in 'cadical.hpp' (here) but
// implemented in 'solver.cpp'. The reason for this naming mismatch is,
// that we want to use 'cadical.hpp' for the library header (this header
// file) and call the binary of the stand alone SAT also 'cadical', which
// is more naturally implemented in 'cadical.cpp'.
//
// Separating 'External' from 'Internal' also allows us to map external
// literals to internal literals, which is useful with many fixed or
// eliminated variables (during 'compact' the internal variable range is
// reduced and external variables are remapped). Such an approach is also
// necessary, if we want to use extended resolution in the future (such as
// bounded variable addition).
//
Internal * internal; // Hidden internal solver.
External * external; // Hidden API to internal solver mapping.
#ifndef NTRACING
// The API calls to the solver can be traced by setting the environment
// variable 'CADICAL_API_TRACE' to point to the path of a file to which
// API calls are written. The same format is used which 'mobical' can
// read, execute and also shrink through delta debugging.
//
// The environment variable is read in the constructor and the trace is
// opened for writing and then closed again in the destructor.
//
// Alternatively one case use 'trace_api_calls'. Both
//
bool close_trace_api_file; // Close file if owned by solver it.
FILE * trace_api_file; // Also acts as flag that we are tracing.
static bool tracing_api_through_environment;
//===== end of state ====================================================
void trace_api_call (const char *) const;
void trace_api_call (const char *, int) const;
void trace_api_call (const char *, const char *, int) const;
#endif
void transition_to_unknown_state ();
//------------------------------------------------------------------------
// Used in the stand alone solver application 'App' and the model based
// tester 'Mobical'. So only these two classes need direct access to the
// otherwise more application specific functions listed here together with
// the internal DIMACS parser.
friend class App;
friend class Mobical;
friend class Parser;
// Read solution in competition format for debugging and testing.
//
// require (VALID)
// ensure (VALID)
//
const char * read_solution (const char * path);
// This gives warning messages for wrong 'printf' style format string usage.
// Apparently (on 'gcc 9' at least) the first argument is 'this' here.
// TODO: support for other compilers (beside 'gcc' and 'clang').
//
# define CADICAL_ATTRIBUTE_FORMAT(FORMAT_POSITION,VARIADIC_ARGUMENT_POSITION) \
__attribute__ ((format (printf, FORMAT_POSITION, VARIADIC_ARGUMENT_POSITION)))
// Messages in a common style.
//
// require (VALID | DELETING)
// ensure (VALID | DELETING)
//
void section (const char *); // print section header
void message (const char *, ...) // ordinary message
CADICAL_ATTRIBUTE_FORMAT (2, 3);
void message (); // empty line - only prefix
void error (const char *, ...) // produce error message
CADICAL_ATTRIBUTE_FORMAT (2, 3);
// Explicit verbose level ('section' and 'message' use '0').
//
// require (VALID | DELETING)
// ensure (VALID | DELETING)
//
void verbose (int level, const char *, ...)
CADICAL_ATTRIBUTE_FORMAT (3, 4);
// Factoring out common code to both 'read_dimacs' functions above.
//
// require (VALID)
// ensure (VALID)
//
const char * read_dimacs (File *, int &, int strict,
bool * incremental = 0,
std::vector<int> * = 0);
// Factored out common code for 'solve', 'simplify' and 'lookahead'.
//
int call_external_solve_and_check_results (bool preprocess_only);
//------------------------------------------------------------------------
// Print DIMACS file to '<stdout>' for debugging and testing purposes,
// including derived units and assumptions. Since it will print in terms
// of internal literals it is otherwise not really useful. To write a
// DIMACS formula in terms of external variables use 'write_dimacs'.
//
// require (!INITIALIZING)
// ensure (!INITIALIZING)
//
void dump_cnf ();
friend struct DumpCall; // Mobical calls 'dump_cnf' in 'DumpCall::execute'
};
/*========================================================================*/
// Connected terminators are checked for termination regularly. If the
// 'terminate' function of the terminator returns true the solver is
// terminated synchronously as soon it calls this function.
class Terminator {
public:
virtual ~Terminator () { }
virtual bool terminate () = 0;
};
// Connected learners which can be used to export learned clauses.
// The 'learning' can check the size of the learn clause and only if it
// returns true then the individual literals of the learned clause are given
// to the learn through 'learn' one by one terminated by a zero literal.
class Learner {
public:
virtual ~Learner () { }
virtual bool learning (int size) = 0;
virtual void learn (int lit) = 0;
};
/*------------------------------------------------------------------------*/
// Allows to traverse all remaining irredundant clauses. Satisfied and
// eliminated clauses are not included, nor any derived units unless such
// a unit literal is frozen. Falsified literals are skipped. If the solver
// is inconsistent only the empty clause is traversed.
//
// If 'clause' returns false traversal aborts early.
class ClauseIterator {
public:
virtual ~ClauseIterator () { }
virtual bool clause (const std::vector<int> &) = 0;
};
/*------------------------------------------------------------------------*/
// Allows to traverse all clauses on the extension stack together with their
// witness cubes. If the solver is inconsistent, i.e., an empty clause is
// found and the formula is unsatisfiable, then nothing is traversed.
//
// The clauses traversed in 'traverse_clauses' together with the clauses on
// the extension stack are logically equivalent to the original clauses.
// See our SAT'19 paper for more details.
//
// The witness literals can be used to extend and fix an assignment on the
// remaining clauses to satisfy the clauses on the extension stack too.
//
// All derived units of non-frozen variables are included too.
//
// If 'witness' returns false traversal aborts early.
class WitnessIterator {
public:
virtual ~WitnessIterator () { }
virtual bool witness (const std::vector<int> & clause,
const std::vector<int> & witness) = 0;
};
/*------------------------------------------------------------------------*/
}
#endif

172
hCaD_V2/src/ccadical.cpp Normal file
View File

@ -0,0 +1,172 @@
#include "cadical.hpp"
#include <cstdlib>
#include <cstring>
namespace CaDiCaL {
struct Wrapper : Learner, Terminator {
Solver * solver;
struct {
void * state;
int (*function) (void *);
} terminator;
struct {
void * state;
int max_length;
int * begin_clause, * end_clause, * capacity_clause;
void (*function) (void *, int *);
} learner;
bool terminate () {
if (!terminator.function)
return false;
return terminator.function (terminator.state);
}
bool learning (int size) {
if (!learner.function)
return false;
return size <= learner.max_length;
}
void learn (int lit) {
if (learner.end_clause == learner.capacity_clause) {
size_t count = learner.end_clause - learner.begin_clause;
size_t size = count ? 2*count : 1;
learner.begin_clause = (int*)
realloc (learner.begin_clause, size * sizeof (int));
learner.end_clause = learner.begin_clause + count;
learner.capacity_clause = learner.begin_clause + size;
}
*learner.end_clause++ = lit;
if (lit)
return;
learner.function (learner.state, learner.begin_clause);
learner.end_clause = learner.begin_clause;
}
Wrapper () : solver (new Solver ()) {
memset (&terminator, 0, sizeof terminator);
memset (&learner, 0, sizeof learner);
}
~Wrapper () {
terminator.function = 0;
if (learner.begin_clause) free (learner.begin_clause);
delete solver; }
};
}
using namespace CaDiCaL;
extern "C" {
#include "ccadical.h"
const char * ccadical_signature (void) {
return Solver::signature ();
}
CCaDiCaL * ccadical_init (void) {
return (CCaDiCaL*) new Wrapper ();
}
void ccadical_release (CCaDiCaL * wrapper) {
delete (Wrapper*) wrapper;
}
void ccadical_set_option (CCaDiCaL * wrapper,
const char * name, int val) {
((Wrapper*) wrapper)->solver->set (name, val);
}
void ccadical_limit (CCaDiCaL * wrapper,
const char * name, int val) {
((Wrapper*) wrapper)->solver->limit (name, val);
}
int ccadical_get_option (CCaDiCaL * wrapper, const char * name) {
return ((Wrapper*) wrapper)->solver->get (name);
}
void ccadical_add (CCaDiCaL * wrapper, int lit) {
((Wrapper*) wrapper)->solver->add (lit);
}
void ccadical_assume (CCaDiCaL * wrapper, int lit) {
((Wrapper*) wrapper)->solver->assume (lit);
}
int ccadical_solve (CCaDiCaL * wrapper) {
return ((Wrapper*) wrapper)->solver->solve ();
}
int ccadical_simplify (CCaDiCaL * wrapper) {
return ((Wrapper*) wrapper)->solver->simplify ();
}
int ccadical_val (CCaDiCaL * wrapper, int lit) {
return ((Wrapper*) wrapper)->solver->val (lit);
}
int ccadical_failed (CCaDiCaL * wrapper, int lit) {
return ((Wrapper*) wrapper)->solver->failed (lit);
}
void ccadical_print_statistics (CCaDiCaL * wrapper) {
((Wrapper*) wrapper)->solver->statistics ();
}
void ccadical_terminate (CCaDiCaL * wrapper) {
((Wrapper*) wrapper)->solver->terminate ();
}
int64_t ccadical_active (CCaDiCaL * wrapper) {
return ((Wrapper*) wrapper)->solver->active ();
}
int64_t ccadical_irredundant (CCaDiCaL * wrapper) {
return ((Wrapper*) wrapper)->solver->irredundant ();
}
int ccadical_fixed (CCaDiCaL * wrapper, int lit) {
return ((Wrapper*) wrapper)->solver->fixed (lit);
}
void ccadical_set_terminate (CCaDiCaL * ptr,
void * state, int (*terminate)(void *)) {
Wrapper * wrapper = (Wrapper *) ptr;
wrapper->terminator.state = state;
wrapper->terminator.function = terminate;
if (terminate) wrapper->solver->connect_terminator (wrapper);
else wrapper->solver->disconnect_terminator ();
}
void ccadical_set_learn (CCaDiCaL * ptr,
void * state, int max_length,
void (*learn)(void * state, int * clause)) {
Wrapper * wrapper = (Wrapper *) ptr;
wrapper->learner.state = state;
wrapper->learner.max_length = max_length;
wrapper->learner.function = learn;
if (learn) wrapper->solver->connect_learner (wrapper);
else wrapper->solver->disconnect_learner ();
}
void ccadical_freeze (CCaDiCaL * ptr, int lit) {
((Wrapper*) ptr)->solver->freeze (lit);
}
void ccadical_melt (CCaDiCaL * ptr, int lit) {
((Wrapper*) ptr)->solver->melt (lit);
}
int ccadical_frozen (CCaDiCaL * ptr, int lit) {
return ((Wrapper*) ptr)->solver->frozen (lit);
}
}

63
hCaD_V2/src/ccadical.h Normal file
View File

@ -0,0 +1,63 @@
#ifndef _ccadical_h_INCLUDED
#define _ccadical_h_INCLUDED
/*------------------------------------------------------------------------*/
#ifdef __cplusplus
extern "C" {
#endif
/*------------------------------------------------------------------------*/
#include <stdint.h>
// C wrapper for CaDiCaL's C++ API following IPASIR.
typedef struct CCaDiCaL CCaDiCaL;
const char * ccadical_signature (void);
CCaDiCaL * ccadical_init (void);
void ccadical_release (CCaDiCaL *);
void ccadical_add (CCaDiCaL *, int lit);
void ccadical_assume (CCaDiCaL *, int lit);
int ccadical_solve (CCaDiCaL *);
int ccadical_val (CCaDiCaL *, int lit);
int ccadical_failed (CCaDiCaL *, int lit);
void ccadical_set_terminate (CCaDiCaL *,
void * state, int (*terminate)(void * state));
void ccadical_set_learn (CCaDiCaL *,
void * state, int max_length, void (*learn)(void * state, int * clause));
/*------------------------------------------------------------------------*/
// Non-IPASIR conformant 'C' functions.
void ccadical_set_option (CCaDiCaL *, const char * name, int val);
void ccadical_limit (CCaDiCaL *, const char * name, int limit);
int ccadical_get_option (CCaDiCaL *, const char * name);
void ccadical_print_statistics (CCaDiCaL *);
int64_t ccadical_active (CCaDiCaL *);
int64_t ccadical_irredundant (CCaDiCaL *);
int ccadical_fixed (CCaDiCaL *, int lit);
void ccadical_terminate (CCaDiCaL *);
void ccadical_freeze (CCaDiCaL *, int lit);
int ccadical_frozen (CCaDiCaL *, int lit);
void ccadical_melt (CCaDiCaL *, int lit);
int ccadical_simplify (CCaDiCaL *);
/*------------------------------------------------------------------------*/
// Support legacy names used before moving to more IPASIR conforming names.
#define ccadical_reset ccadical_release
#define ccadical_sat ccadical_solve
#define ccadical_deref ccadical_val
/*------------------------------------------------------------------------*/
#ifdef __cplusplus
}
#endif
/*------------------------------------------------------------------------*/
#endif

555
hCaD_V2/src/checker.cpp Normal file
View File

@ -0,0 +1,555 @@
#include "internal.hpp"
extern "C" {
#include <inttypes.h>
}
namespace CaDiCaL {
/*------------------------------------------------------------------------*/
inline unsigned Checker::l2u (int lit) {
assert (lit);
assert (lit != INT_MIN);
unsigned res = 2*(abs (lit) - 1);
if (lit < 0) res++;
return res;
}
inline signed char Checker::val (int lit) {
assert (lit);
assert (lit != INT_MIN);
assert (abs (lit) < size_vars);
assert (vals[lit] == -vals[-lit]);
return vals[lit];
}
signed char & Checker::mark (int lit) {
const unsigned u = l2u (lit);
assert (u < marks.size ());
return marks[u];
}
inline CheckerWatcher & Checker::watcher (int lit) {
const unsigned u = l2u (lit);
assert (u < watchers.size ());
return watchers[u];
}
/*------------------------------------------------------------------------*/
CheckerClause * Checker::new_clause () {
const size_t size = simplified.size ();
assert (size > 1), assert (size <= UINT_MAX);
const size_t bytes = sizeof (CheckerClause) + (size - 2) * sizeof (int);
CheckerClause * res = (CheckerClause *) new char [bytes];
res->next = 0;
res->hash = last_hash;
res->size = size;
int * literals = res->literals, * p = literals;
for (const auto & lit : simplified)
*p++ = lit;
num_clauses++;
// First two literals are used as watches and should not be false.
//
for (unsigned i = 0; i < 2; i++) {
int lit = literals[i];
if (!val (lit)) continue;
for (unsigned j = i + 1; j < size; j++) {
int other = literals[j];
if (val (other)) continue;
swap (literals[i], literals[j]);
break;
}
}
assert (!val (literals [0]));
assert (!val (literals [1]));
watcher (literals[0]).push_back (CheckerWatch (literals[1], res));
watcher (literals[1]).push_back (CheckerWatch (literals[0], res));
return res;
}
void Checker::delete_clause (CheckerClause * c) {
if (c->size) {
assert (c->size > 1);
assert (num_clauses);
num_clauses--;
} else {
assert (num_garbage);
num_garbage--;
}
delete [] (char*) c;
}
void Checker::enlarge_clauses () {
assert (num_clauses == size_clauses);
const uint64_t new_size_clauses = size_clauses ? 2*size_clauses : 1;
LOG ("CHECKER enlarging clauses of checker from %" PRIu64 " to %" PRIu64,
(uint64_t) size_clauses, (uint64_t) new_size_clauses);
CheckerClause ** new_clauses;
new_clauses = new CheckerClause * [ new_size_clauses ];
clear_n (new_clauses, new_size_clauses);
for (uint64_t i = 0; i < size_clauses; i++) {
for (CheckerClause * c = clauses[i], * next; c; c = next) {
next = c->next;
const uint64_t h = reduce_hash (c->hash, new_size_clauses);
c->next = new_clauses[h];
new_clauses[h] = c;
}
}
delete [] clauses;
clauses = new_clauses;
size_clauses = new_size_clauses;
}
bool Checker::clause_satisfied (CheckerClause * c) {
for (unsigned i = 0; i < c->size; i++)
if (val (c->literals[i]) > 0)
return true;
return false;
}
// The main reason why we have an explicit garbage collection phase is that
// removing clauses from watcher lists eagerly might lead to an accumulated
// quadratic algorithm. Thus we delay removing garbage clauses from watcher
// lists until garbage collection (even though we remove garbage clauses on
// the fly during propagation too). We also remove satisfied clauses.
//
void Checker::collect_garbage_clauses () {
stats.collections++;
for (size_t i = 0; i < size_clauses; i++) {
CheckerClause ** p = clauses + i, * c;
while ((c = *p)) {
if (clause_satisfied (c)) {
c->size = 0; // mark as garbage
*p = c->next;
c->next = garbage;
garbage = c;
num_garbage++;
assert (num_clauses);
num_clauses--;
} else p = &c->next;
}
}
LOG ("CHECKER collecting %" PRIu64 " garbage clauses %.0f%%",
num_garbage, percent (num_garbage, num_clauses));
for (int lit = -size_vars + 1; lit < size_vars; lit++) {
if (!lit) continue;
CheckerWatcher & ws = watcher (lit);
const auto end = ws.end ();
auto j = ws.begin (), i = j;
for (;i != end; i++) {
CheckerWatch & w = *i;
if (w.clause->size) *j++ = w;
}
if (j == ws.end ()) continue;
if (j == ws.begin ()) erase_vector (ws);
else ws.resize (j - ws.begin ());
}
for (CheckerClause * c = garbage, * next; c; c = next)
next = c->next, delete_clause (c);
assert (!num_garbage);
garbage = 0;
}
/*------------------------------------------------------------------------*/
Checker::Checker (Internal * i)
:
internal (i),
size_vars (0), vals (0),
inconsistent (false), num_clauses (0), num_garbage (0),
size_clauses (0), clauses (0), garbage (0),
next_to_propagate (0), last_hash (0)
{
LOG ("CHECKER new");
// Initialize random number table for hash function.
//
Random random (42);
for (unsigned n = 0; n < num_nonces; n++) {
uint64_t nonce = random.next ();
if (!(nonce & 1)) nonce++;
assert (nonce), assert (nonce & 1);
nonces[n] = nonce;
}
memset (&stats, 0, sizeof (stats)); // Initialize statistics.
}
Checker::~Checker () {
LOG ("CHECKER delete");
vals -= size_vars;
delete [] vals;
for (size_t i = 0; i < size_clauses; i++)
for (CheckerClause * c = clauses[i], * next; c; c = next)
next = c->next, delete_clause (c);
for (CheckerClause * c = garbage, * next; c; c = next)
next = c->next, delete_clause (c);
delete [] clauses;
}
/*------------------------------------------------------------------------*/
// The simplicity for accessing 'vals' and 'watchers' directly through a
// signed integer literal, comes with the price of slightly more complex
// code in deleting and enlarging the checker data structures.
void Checker::enlarge_vars (int64_t idx) {
assert (0 < idx), assert (idx <= INT_MAX);
int64_t new_size_vars = size_vars ? 2*size_vars : 2;
while (idx >= new_size_vars) new_size_vars *= 2;
LOG ("CHECKER enlarging variables of checker from %" PRId64 " to %" PRId64 "",
size_vars, new_size_vars);
signed char * new_vals;
new_vals = new signed char [ 2*new_size_vars ];
clear_n (new_vals, 2*new_size_vars);
new_vals += new_size_vars;
if (size_vars) // To make sanitizer happy (without '-O').
memcpy ((void*) (new_vals - size_vars),
(void*) (vals - size_vars), 2*size_vars);
vals -= size_vars;
delete [] vals;
vals = new_vals;
watchers.resize (2*new_size_vars);
marks.resize (2*new_size_vars);
assert (idx < new_size_vars);
size_vars = new_size_vars;
}
inline void Checker::import_literal (int lit) {
assert (lit);
assert (lit != INT_MIN);
int idx = abs (lit);
if (idx >= size_vars) enlarge_vars (idx);
simplified.push_back (lit);
unsimplified.push_back (lit);
}
void Checker::import_clause (const vector<int> & c) {
for (const auto & lit : c)
import_literal (lit);
}
struct lit_smaller {
bool operator () (int a, int b) const {
int c = abs (a), d = abs (b);
if (c < d) return true;
if (c > d) return false;
return a < b;
}
};
bool Checker::tautological () {
sort (simplified.begin (), simplified.end (), lit_smaller ());
const auto end = simplified.end ();
auto j = simplified.begin ();
int prev = 0;
for (auto i = j; i != end; i++) {
int lit = *i;
if (lit == prev) continue; // duplicated literal
if (lit == -prev) return true; // tautological clause
const signed char tmp = val (lit);
if (tmp > 0) return true; // satisfied literal and clause
*j++ = prev = lit;
}
simplified.resize (j - simplified.begin ());
return false;
}
/*------------------------------------------------------------------------*/
uint64_t Checker::reduce_hash (uint64_t hash, uint64_t size) {
assert (size > 0);
unsigned shift = 32;
uint64_t res = hash;
while ((((uint64_t)1) << shift) > size) {
res ^= res >> shift;
shift >>= 1;
}
res &= size - 1;
assert (res < size);
return res;
}
uint64_t Checker::compute_hash () {
unsigned i = 0, j = 0;
uint64_t tmp = 0;
for (i = 0; i < simplified.size (); i++) {
int lit = simplified[i];
tmp += nonces[j++] * (uint64_t) lit;
if (j == num_nonces) j = 0;
}
return last_hash = tmp;
}
CheckerClause ** Checker::find () {
stats.searches++;
CheckerClause ** res, * c;
const uint64_t hash = compute_hash ();
const unsigned size = simplified.size ();
const uint64_t h = reduce_hash (hash, size_clauses);
for (const auto & lit : simplified) mark (lit) = true;
for (res = clauses + h; (c = *res); res = &c->next) {
if (c->hash == hash && c->size == size) {
bool found = true;
const int * literals = c->literals;
for (unsigned i = 0; found && i != size; i++)
found = mark (literals[i]);
if (found) break;
}
stats.collisions++;
}
for (const auto & lit : simplified) mark (lit) = false;
return res;
}
void Checker::insert () {
stats.insertions++;
if (num_clauses == size_clauses) enlarge_clauses ();
const uint64_t h = reduce_hash (compute_hash (), size_clauses);
CheckerClause * c = new_clause ();
c->next = clauses[h];
clauses[h] = c;
}
/*------------------------------------------------------------------------*/
inline void Checker::assign (int lit) {
assert (!val (lit));
vals[lit] = 1;
vals[-lit] = -1;
trail.push_back (lit);
}
inline void Checker::assume (int lit) {
signed char tmp = val (lit);
if (tmp > 0) return;
assert (!tmp);
stats.assumptions++;
assign (lit);
}
void Checker::backtrack (unsigned previously_propagated) {
assert (previously_propagated <= trail.size ());
while (trail.size () > previously_propagated) {
int lit = trail.back ();
assert (val (lit) > 0);
assert (val (-lit) < 0);
vals[lit] = vals[-lit] = 0;
trail.pop_back ();
}
trail.resize (previously_propagated);
next_to_propagate = previously_propagated;
assert (trail.size () == next_to_propagate);
}
/*------------------------------------------------------------------------*/
// This is a standard propagation routine without using blocking literals
// nor without saving the last replacement position.
bool Checker::propagate () {
bool res = true;
while (res && next_to_propagate < trail.size ()) {
int lit = trail[next_to_propagate++];
stats.propagations++;
assert (val (lit) > 0);
assert (abs (lit) < size_vars);
CheckerWatcher & ws = watcher (-lit);
const auto end = ws.end ();
auto j = ws.begin (), i = j;
for (; res && i != end; i++) {
CheckerWatch & w = *j++ = *i;
const int blit = w.blit;
assert (blit != -lit);
const signed char blit_val = val (blit);
if (blit_val > 0) continue;
const unsigned size = w.size;
if (size == 2) { // not precise since
if (blit_val < 0) res = false; // clause might be garbage
else assign (w.blit); // but still sound
} else {
assert (size > 2);
CheckerClause * c = w.clause;
if (!c->size) { j--; continue; } // skip garbage clauses
assert (size == c->size);
int * lits = c->literals;
int other = lits[0]^lits[1]^(-lit);
assert (other != -lit);
signed char other_val = val (other);
if (other_val > 0) { j[-1].blit = other; continue; }
lits[0] = other, lits[1] = -lit;
unsigned k;
int replacement = 0;
signed char replacement_val = -1;
for (k = 2; k < size; k++)
if ((replacement_val = val (replacement = lits[k])) >= 0)
break;
if (replacement_val >= 0) {
watcher (replacement).push_back (CheckerWatch (-lit, c));
swap (lits[1], lits[k]);
j--;
} else if (!other_val) assign (other);
else res = false;
}
}
while (i != end) *j++ = *i++;
ws.resize (j - ws.begin ());
}
return res;
}
bool Checker::check () {
stats.checks++;
if (inconsistent) return true;
unsigned previously_propagated = next_to_propagate;
for (const auto & lit : simplified)
assume (-lit);
bool res = !propagate ();
backtrack (previously_propagated);
return res;
}
/*------------------------------------------------------------------------*/
void Checker::add_clause (const char * type) {
#ifndef LOGGING
(void) type;
#endif
int unit = 0;
for (const auto & lit : simplified) {
const signed char tmp = val (lit);
if (tmp < 0) continue;
assert (!tmp);
if (unit) { unit = INT_MIN; break; }
unit = lit;
}
if (simplified.empty ()) {
LOG ("CHECKER added empty %s clause", type);
inconsistent = true;
} if (!unit) {
LOG ("CHECKER added and checked falsified %s clause", type);
inconsistent = true;
} else if (unit != INT_MIN) {
LOG ("CHECKER added and checked %s unit clause %d", type, unit);
assign (unit);
stats.units++;
if (!propagate ()) {
LOG ("CHECKER inconsistent after propagating %s unit", type);
inconsistent = true;
}
} else insert ();
}
void Checker::add_original_clause (const vector<int> & c) {
if (inconsistent) return;
START (checking);
LOG (c, "CHECKER addition of original clause");
stats.added++;
stats.original++;
import_clause (c);
if (tautological ())
LOG ("CHECKER ignoring satisfied original clause");
else add_clause ("original");
simplified.clear ();
unsimplified.clear ();
STOP (checking);
}
void Checker::add_derived_clause (const vector<int> & c) {
if (inconsistent) return;
START (checking);
LOG (c, "CHECKER addition of derived clause");
stats.added++;
stats.derived++;
import_clause (c);
if (tautological ())
LOG ("CHECKER ignoring satisfied derived clause");
else if (!check ()) {
fatal_message_start ();
fputs ("failed to check derived clause:\n", stderr);
for (const auto & lit : unsimplified)
fprintf (stderr, "%d ", lit);
fputc ('0', stderr);
fatal_message_end ();
} else add_clause ("derived");
simplified.clear ();
unsimplified.clear ();
STOP (checking);
}
/*------------------------------------------------------------------------*/
void Checker::delete_clause (const vector<int> & c) {
if (inconsistent) return;
START (checking);
LOG (c, "CHECKER checking deletion of clause");
stats.deleted++;
import_clause (c);
if (!tautological ()) {
CheckerClause ** p = find (), * d = *p;
if (d) {
assert (d->size > 1);
// Remove from hash table, mark as garbage, connect to garbage list.
num_garbage++;
assert (num_clauses);
num_clauses--;
*p = d->next;
d->next = garbage;
garbage = d;
d->size = 0;
// If there are enough garbage clauses collect them.
if (num_garbage > 0.5 * max ((size_t) size_clauses, (size_t) size_vars))
collect_garbage_clauses ();
} else {
fatal_message_start ();
fputs ("deleted clause not in proof:\n", stderr);
for (const auto & lit : unsimplified)
fprintf (stderr, "%d ", lit);
fputc ('0', stderr);
fatal_message_end ();
}
}
simplified.clear ();
unsimplified.clear ();
STOP (checking);
}
/*------------------------------------------------------------------------*/
void Checker::dump () {
int max_var = 0;
for (uint64_t i = 0; i < size_clauses; i++)
for (CheckerClause * c = clauses[i]; c; c = c->next)
for (unsigned i = 0; i < c->size; i++)
if (abs (c->literals[i]) > max_var)
max_var = abs (c->literals[i]);
printf ("p cnf %d %" PRIu64 "\n", max_var, num_clauses);
for (uint64_t i = 0; i < size_clauses; i++)
for (CheckerClause * c = clauses[i]; c; c = c->next) {
for (unsigned i = 0; i < c->size; i++)
printf ("%d ", c->literals[i]);
printf ("0\n");
}
}
}

165
hCaD_V2/src/checker.hpp Normal file
View File

@ -0,0 +1,165 @@
#ifndef _checker_hpp_INCLUDED
#define _checker_hpp_INCLUDED
#include "observer.hpp" // Alphabetically after 'checker'.
/*------------------------------------------------------------------------*/
namespace CaDiCaL {
/*------------------------------------------------------------------------*/
// This checker implements an online forward DRUP proof checker enabled by
// 'opts.checkproof' (requires 'opts.check' also to be enabled). This is
// useful for model basted testing (and delta-debugging), where we can not
// rely on an external proof checker such as 'drat-trim'. We also do not
// have yet a flow for offline incremental proof checking, while this
// checker here can also be used in an incremental setting.
//
// In essence the checker implements is a simple propagation online SAT
// solver with an additional hash table to find clauses fast for
// 'delete_clause'. It requires its own data structure for clauses
// ('CheckerClause') and watches ('CheckerWatch').
//
// In our experiments the checker slows down overall SAT solving time by a
// factor of 3, which we contribute to its slightly less efficient
// implementation.
/*------------------------------------------------------------------------*/
struct CheckerClause {
CheckerClause * next; // collision chain link for hash table
uint64_t hash; // previously computed full 64-bit hash
unsigned size; // zero if this is a garbage clause
int literals[2]; // otherwise 'literals' of length 'size'
};
struct CheckerWatch {
int blit;
unsigned size;
CheckerClause * clause;
CheckerWatch () { }
CheckerWatch (int b, CheckerClause * c) :
blit (b), size (c->size), clause (c)
{ }
};
typedef vector<CheckerWatch> CheckerWatcher;
/*------------------------------------------------------------------------*/
class Checker : public Observer {
Internal * internal;
// Capacity of variable values.
//
int64_t size_vars;
// For the assignment we want to have an as fast access as possible and
// thus we use an array which can also be indexed by negative literals and
// is actually valid in the range [-size_vars+1, ..., size_vars-1].
//
signed char * vals;
// The 'watchers' and 'marks' data structures are not that time critical
// and thus we access them by first mapping a literal to 'unsigned'.
//
static unsigned l2u (int lit);
vector<CheckerWatcher> watchers; // watchers of literals
vector<signed char> marks; // mark bits of literals
signed char & mark (int lit);
CheckerWatcher & watcher (int lit);
bool inconsistent; // found or added empty clause
uint64_t num_clauses; // number of clauses in hash table
uint64_t num_garbage; // number of garbage clauses
uint64_t size_clauses; // size of clause hash table
CheckerClause ** clauses; // hash table of clauses
CheckerClause * garbage; // linked list of garbage clauses
vector<int> unsimplified; // original clause for reporting
vector<int> simplified; // clause for sorting
vector<int> trail; // for propagation
unsigned next_to_propagate; // next to propagate on trail
void enlarge_vars (int64_t idx);
void import_literal (int lit);
void import_clause (const vector<int> &);
bool tautological ();
static const unsigned num_nonces = 4;
uint64_t nonces[num_nonces]; // random numbers for hashing
uint64_t last_hash; // last computed hash value of clause
uint64_t compute_hash (); // compute and save hash value of clause
// Reduce hash value to the actual size.
//
static uint64_t reduce_hash (uint64_t hash, uint64_t size);
void enlarge_clauses (); // enlarge hash table for clauses
void insert (); // insert clause in hash table
CheckerClause ** find (); // find clause position in hash table
void add_clause (const char * type);
void collect_garbage_clauses ();
CheckerClause * new_clause ();
void delete_clause (CheckerClause *);
signed char val (int lit); // returns '-1', '0' or '1'
bool clause_satisfied (CheckerClause*);
void assign (int lit); // assign a literal to true
void assume (int lit); // assume a literal
bool propagate (); // propagate and check for conflicts
void backtrack (unsigned); // prepare for next clause
bool check (); // check simplified clause is implied
struct {
int64_t added; // number of added clauses
int64_t original; // number of added original clauses
int64_t derived; // number of added derived clauses
int64_t deleted; // number of deleted clauses
int64_t assumptions; // number of assumed literals
int64_t propagations; // number of propagated literals
int64_t insertions; // number of clauses added to hash table
int64_t collisions; // number of hash collisions in 'find'
int64_t searches; // number of searched clauses in 'find'
int64_t checks; // number of implication checks
int64_t collections; // garbage collections
int64_t units;
} stats;
public:
Checker (Internal *);
~Checker ();
// The following three implement the 'Observer' interface.
//
void add_original_clause (const vector<int> &);
void add_derived_clause (const vector<int> &);
void delete_clause (const vector<int> &);
void print_stats ();
void dump (); // for debugging purposes only
};
}
#endif

442
hCaD_V2/src/clause.cpp Normal file
View File

@ -0,0 +1,442 @@
#include "internal.hpp"
namespace CaDiCaL {
/*------------------------------------------------------------------------*/
// Signed marking or unmarking of a clause or the global 'clause'.
void Internal::mark (Clause * c) {
for (const auto & lit : *c) mark (lit);
}
void Internal::mark2 (Clause * c) {
for (const auto & lit : *c) mark2 (lit);
}
void Internal::unmark (Clause * c) {
for (const auto & lit : *c) unmark (lit);
}
void Internal::mark_clause () {
for (const auto & lit : clause) mark (lit);
}
void Internal::unmark_clause () {
for (const auto & lit : clause) unmark (lit);
}
/*------------------------------------------------------------------------*/
// Mark the variables of an irredundant clause to 'have been removed', which
// will trigger these variables to be considered again in the next bounded
// variable elimination phase. This is called from 'mark_garbage' below.
// Note that 'mark_removed (int lit)' will also mark the blocking flag of
// '-lit' to trigger reconsidering blocking clauses on '-lit'.
void Internal::mark_removed (Clause * c, int except) {
LOG (c, "marking removed");
assert (!c->redundant);
for (const auto & lit : *c)
if (lit != except)
mark_removed (lit);
}
// Mark the variables of a (redundant or irredundant) clause to 'have been
// added', which triggers clauses with such a variables, to be considered
// both as a subsumed or subsuming clause in the next subsumption phase.
// This function is called from 'new_clause' below as well as in situations
// where a clause is shrunken (and thus needs to be at least considered
// again to subsume a larger clause). We also use this to tell
// 'ternary' preprocessing reconsider clauses on an added literal as well as
// trying to block clauses on it.
inline void Internal::mark_added (int lit, int size, bool redundant) {
mark_subsume (lit);
if (size == 3)
mark_ternary (lit);
if (!redundant)
mark_block (lit);
}
void Internal::mark_added (Clause * c) {
LOG (c, "marking added");
assert (likely_to_be_kept_clause (c));
for (const auto & lit : *c)
mark_added (lit, c->size, c->redundant);
}
/*------------------------------------------------------------------------*/
Clause * Internal::new_clause (bool red, int glue) {
assert (clause.size () <= (size_t) INT_MAX);
const int size = (int) clause.size ();
assert (size >= 2);
if (glue > size) glue = size;
// Determine whether this clauses should be kept all the time.
//
bool keep;
if (!red) keep = true;
else if (glue <= opts.reducetier1glue) keep = true;
else keep = false;
size_t bytes = Clause::bytes (size);
Clause * c = (Clause *) new char[bytes];
stats.added.total++;
#ifdef LOGGING
c->id = stats.added.total;
#endif
c->conditioned = false;
c->covered = false;
c->enqueued = false;
c->frozen = false;
c->garbage = false;
c->gate = false;
c->hyper = false;
c->instantiated = false;
c->keep = keep;
c->moved = false;
c->reason = false;
c->redundant = red;
c->transred = false;
c->subsume = false;
c->vivified = false;
c->vivify = false;
c->used = 0;
c->glue = glue;
c->size = size;
c->pos = 2;
for (int i = 0; i < size; i++) c->literals[i] = clause[i];
// Just checking that we did not mess up our sophisticated memory layout.
// This might be compiler dependent though. Crucial for correctness.
//
assert (c->bytes () == bytes);
stats.current.total++;
stats.added.total++;
if (red) {
stats.current.redundant++;
stats.added.redundant++;
} else {
stats.irrbytes += bytes;
stats.current.irredundant++;
stats.added.irredundant++;
}
clauses.push_back (c);
LOG (c, "new pointer %p", (void*) c);
if (likely_to_be_kept_clause (c)) mark_added (c);
return c;
}
/*------------------------------------------------------------------------*/
void Internal::promote_clause (Clause * c, int new_glue) {
assert (c->redundant);
if (c->keep) return;
if (c->hyper) return;
int old_glue = c->glue;
if (new_glue >= old_glue) return;
if (!c->keep && new_glue <= opts.reducetier1glue) {
LOG (c, "promoting with new glue %d to tier1", new_glue);
stats.promoted1++;
c->keep = true;
} else if (old_glue > opts.reducetier2glue &&
new_glue <= opts.reducetier2glue) {
LOG (c, "promoting with new glue %d to tier2", new_glue);
stats.promoted2++;
c->used = 2;
} else if (c->keep)
LOG (c, "keeping with new glue %d in tier1", new_glue);
else if (old_glue <= opts.reducetier2glue)
LOG (c, "keeping with new glue %d in tier2", new_glue);
else
LOG (c, "keeping with new glue %d in tier3", new_glue);
stats.improvedglue++;
c->glue = new_glue;
}
/*------------------------------------------------------------------------*/
// Shrinking a clause, e.g., removing one or more literals, requires to fix
// the 'pos' field, if it exists and points after the new last literal, has
// to adjust the global statistics counter of allocated bytes for
// irredundant clauses, and also adjust the glue value of redundant clauses
// if the size becomes smaller than the glue. Also mark the literals in the
// resulting clause as 'added'. The result is the number of (aligned)
// removed bytes, resulting from shrinking the clause.
//
size_t Internal::shrink_clause (Clause * c, int new_size) {
assert (new_size >= 2);
assert (new_size < c->size);
#ifndef NDEBUG
for (int i = c->size; i < new_size; i++)
c->literals[i] = 0;
#endif
if (c->pos >= new_size) c->pos = 2;
size_t old_bytes = c->bytes ();
c->size = new_size;
size_t new_bytes = c->bytes ();
size_t res = old_bytes - new_bytes;
if (c->redundant) promote_clause (c, min (c->size-1, c->glue));
else if (old_bytes > new_bytes) {
assert (stats.irrbytes >= (int64_t) res);
stats.irrbytes -= res;
}
if (likely_to_be_kept_clause (c)) mark_added (c);
return res;
}
// This is the 'raw' deallocation of a clause. If the clause is in the
// arena nothing happens. If the clause is not in the arena its memory is
// reclaimed immediately.
void Internal::deallocate_clause (Clause * c) {
char * p = (char*) c;
if (arena.contains (p)) return;
LOG (c, "deallocate pointer %p", (void*) c);
delete [] p;
}
void Internal::delete_clause (Clause * c) {
LOG (c, "delete pointer %p", (void*) c);
size_t bytes = c->bytes ();
stats.collected += bytes;
if (c->garbage) {
assert (stats.garbage >= (int64_t) bytes);
stats.garbage -= bytes;
// See the discussion in 'propagate' on avoiding to eagerly trace binary
// clauses as deleted (produce 'd ...' lines) as soon they are marked
// garbage. We avoid this and only trace them as deleted when they are
// actually deleted here. This allows the solver to propagate binary
// garbage clauses without producing incorrect 'd' lines. The effect
// from the proof perspective is that the deletion of these binary
// clauses occurs later in the proof file.
//
if (proof && c->size == 2)
proof->delete_clause (c);
}
deallocate_clause (c);
}
// We want to eagerly update statistics as soon clauses are marked garbage.
// Otherwise 'report' for instance gives wrong numbers after 'subsume'
// before the next 'reduce'. Thus we factored out marking and accounting
// for garbage clauses.
//
// Eagerly deleting clauses instead is problematic, since references to these
// clauses need to be flushed, which is too costly to do eagerly.
//
// We also update garbage statistics at this point. This helps to
// determine whether the garbage collector should be called during for
// instance bounded variable elimination, which usually generates lots of
// garbage clauses.
//
// In order not to miss any update to these clause statistics we call
// 'check_clause_stats' after garbage collection in debugging mode.
//
void Internal::mark_garbage (Clause * c) {
assert (!c->garbage);
// Delay tracing deletion of binary clauses. See the discussion above in
// 'delete_clause' and also in 'propagate'.
//
if (proof && c->size != 2)
proof->delete_clause (c);
assert (stats.current.total > 0);
stats.current.total--;
size_t bytes = c->bytes ();
if (c->redundant) {
assert (stats.current.redundant > 0);
stats.current.redundant--;
} else {
assert (stats.current.irredundant > 0);
stats.current.irredundant--;
assert (stats.irrbytes >= (int64_t) bytes);
stats.irrbytes -= bytes;
mark_removed (c);
}
stats.garbage += bytes;
c->garbage = true;
c->used = 0;
LOG (c, "marked garbage pointer %p", (void*) c);
}
/*------------------------------------------------------------------------*/
// Almost the same function as 'search_assign' except that we do not pretend
// to learn a new unit clause (which was confusing in log files).
void Internal::assign_original_unit (int lit) {
assert (!level);
const int idx = vidx (lit);
assert (!vals[idx]);
assert (!flags (idx).eliminated ());
Var & v = var (idx);
v.level = level;
v.trail = (int) trail.size ();
v.reason = 0;
const signed char tmp = sign (lit);
vals[idx] = tmp;
vals[-idx] = -tmp;
assert (val (lit) > 0);
assert (val (-lit) < 0);
trail.push_back (lit);
LOG ("original unit assign %d", lit);
mark_fixed (lit);
if (propagate ()) return;
LOG ("propagation of original unit results in conflict");
learn_empty_clause ();
}
// New clause added through the API, e.g., while parsing a DIMACS file.
//
void Internal::add_new_original_clause () {
if (level) backtrack ();
LOG (original, "original clause");
bool skip = false;
if (unsat) {
LOG ("skipping clause since formula already inconsistent");
skip = true;
} else {
assert (clause.empty ());
for (const auto & lit : original) {
int tmp = marked (lit);
if (tmp > 0) {
LOG ("removing duplicated literal %d", lit);
} else if (tmp < 0) {
LOG ("tautological since both %d and %d occur", -lit, lit);
skip = true;
} else {
mark (lit);
tmp = val (lit);
if (tmp < 0) {
LOG ("removing falsified literal %d", lit);
} else if (tmp > 0) {
LOG ("satisfied since literal %d true", lit);
skip = true;
} else {
clause.push_back (lit);
assert (flags (lit).status != Flags::UNUSED);
}
}
}
for (const auto & lit : original)
unmark (lit);
}
if (skip) {
if (proof) proof->delete_clause (original);
} else {
size_t size = clause.size ();
if (!size) {
if (!unsat) {
if (!original.size ()) VERBOSE (1, "found empty original clause");
else MSG ("found falsified original clause");
unsat = true;
}
} else if (size == 1) {
assign_original_unit (clause[0]);
} else {
Clause * c = new_clause (false);
watch_clause (c);
}
if (original.size () > size) {
external->check_learned_clause ();
if (proof) {
proof->add_derived_clause (clause);
proof->delete_clause (original);
}
}
}
clause.clear ();
}
// Add learned new clause during conflict analysis and watch it. Requires
// that the clause is at least of size 2, and the first two literals
// are assigned at the highest decision level.
//
Clause * Internal::new_learned_redundant_clause (int glue) {
assert (clause.size () > 1);
#ifndef NDEBUG
for (size_t i = 2; i < clause.size (); i++)
assert (var (clause[0]).level >= var (clause[i]).level),
assert (var (clause[1]).level >= var (clause[i]).level);
#endif
external->check_learned_clause ();
Clause * res = new_clause (true, glue);
if (proof) proof->add_derived_clause (res);
assert (watching ());
watch_clause (res);
return res;
}
// Add hyper binary resolved clause during 'probing'.
//
Clause * Internal::new_hyper_binary_resolved_clause (bool red, int glue) {
external->check_learned_clause ();
Clause * res = new_clause (red, glue);
if (proof) proof->add_derived_clause (res);
assert (watching ());
watch_clause (res);
return res;
}
// Add hyper ternary resolved clause during 'ternary'.
//
Clause * Internal::new_hyper_ternary_resolved_clause (bool red) {
external->check_learned_clause ();
size_t size = clause.size ();
Clause * res = new_clause (red, size);
if (proof) proof->add_derived_clause (res);
assert (!watching ());
return res;
}
// Add a new clause with same glue and redundancy as 'orig' but literals are
// assumed to be in 'clause' in 'decompose' and 'vivify'.
//
Clause * Internal::new_clause_as (const Clause * orig) {
external->check_learned_clause ();
const int new_glue = orig->glue;
Clause * res = new_clause (orig->redundant, new_glue);
assert (!orig->redundant || !orig->keep || res->keep);
if (proof) proof->add_derived_clause (res);
assert (watching ());
watch_clause (res);
return res;
}
// Add resolved clause during resolution, e.g., bounded variable
// elimination, but do not connect its occurrences here.
//
Clause * Internal::new_resolved_irredundant_clause () {
external->check_learned_clause ();
Clause * res = new_clause (false);
if (proof) proof->add_derived_clause (res);
assert (!watching ());
return res;
}
}

151
hCaD_V2/src/clause.hpp Normal file
View File

@ -0,0 +1,151 @@
#ifndef _clause_hpp_INCLUDED
#define _clause_hpp_INCLUDED
namespace CaDiCaL {
/*------------------------------------------------------------------------*/
typedef int * literal_iterator;
typedef const int * const_literal_iterator;
/*------------------------------------------------------------------------*/
// The 'Clause' data structure is very important. There are usually many
// clauses and accessing them is a hot-spot. Thus we use common
// optimizations to reduce memory and improve cache usage, even though this
// induces some complexity in understanding the code.
//
// The most important optimization is to 'embed' the actual literals in the
// clause. This requires a variadic size structure and thus strictly is not
// 'C' conform, but supported by all compilers we used. The alternative is
// to store the actual literals somewhere else, which not only needs more
// memory but more importantly also requires another memory access and thus
// is very costly.
struct Clause {
#ifdef LOGGING
int64_t id; // Only useful for debugging.
#endif
bool conditioned:1; // Tried for globally blocked clause elimination.
bool covered:1; // Already considered for covered clause elimination.
bool enqueued:1; // Enqueued on backward queue.
bool frozen:1; // Temporarily frozen (in covered clause elimination).
bool garbage:1; // can be garbage collected unless it is a 'reason'
bool gate:1 ; // Clause part of a gate (function definition).
bool hyper:1; // redundant hyper binary or ternary resolved
bool instantiated:1;// tried to instantiate
bool keep:1; // always keep this clause (if redundant)
bool moved:1; // moved during garbage collector ('copy' valid)
bool reason:1; // reason / antecedent clause can not be collected
bool redundant:1; // aka 'learned' so not 'irredundant' (original)
bool transred:1; // already checked for transitive reduction
bool subsume:1; // not checked in last subsumption round
unsigned used:2; // resolved in conflict analysis since last 'reduce'
bool vivified:1; // clause already vivified
bool vivify:1; // clause scheduled to be vivified
// The glucose level ('LBD' or short 'glue') is a heuristic value for the
// expected usefulness of a learned clause, where smaller glue is consider
// more useful. During learning the 'glue' is determined as the number of
// decisions in the learned clause. Thus the glue of a clause is a strict
// upper limit on the smallest number of decisions needed to make it
// propagate. For instance a binary clause will propagate if one of its
// literals is set to false. Similarly a learned clause with glue 1 can
// propagate after one decision, one with glue 2 after 2 decisions etc.
// In some sense the glue is an abstraction of the size of the clause.
//
// See the IJCAI'09 paper by Audemard & Simon for more details. We
// switched back and forth between keeping the glue stored in a clause and
// using it only initially to determine whether it is kept, that is
// survives clause reduction. The latter strategy is not bad but also
// does not allow to use glue values for instance in 'reduce'.
//
// More recently we also update the glue and promote clauses to lower
// level tiers during conflict analysis. The idea of using three tiers is
// also due to Chanseok Oh and thus used in all recent 'Maple...' solvers.
// Tier one are the always kept clauses with low glue at most
// 'opts.reducetier1glue' (default '2'). The second tier contains all
// clauses with glue larger than 'opts.reducetier1glue' but smaller or
// equal than 'opts.reducetier2glue' (default '6'). The third tier
// consists of clauses with glue larger than 'opts.reducetier2glue'.
//
// Clauses in tier one are not deleted in 'reduce'. Clauses in tier
// two require to be unused in two consecutive 'reduce' intervals before
// being collected while for clauses in tier three not being used since
// the last 'reduce' call makes them deletion candidates. Clauses derived
// by hyper binary or ternary resolution (even though small and thus with
// low glue) are always removed if they remain unused during one interval.
// See 'mark_useless_redundant_clauses_as_garbage' in 'reduce.cpp' and
// 'bump_clause' in 'analyze.cpp'.
//
int glue;
int size; // Actual size of 'literals' (at least 2).
int pos; // Position of last watch replacement [Gent'13].
union {
int literals[2]; // Of variadic 'size' (shrunken if strengthened).
Clause * copy; // Only valid if 'moved', then that's where to.
//
// The 'copy' field is only valid for 'moved' clauses in the moving
// garbage collector 'copy_non_garbage_clauses' for keeping clauses
// compactly in a contiguous memory arena. Otherwise, most of
// the time, 'literals' is valid. See 'collect.cpp' for details.
};
literal_iterator begin () { return literals; }
literal_iterator end () { return literals + size; }
const_literal_iterator begin () const { return literals; }
const_literal_iterator end () const { return literals + size; }
static size_t bytes (int size) {
// Memory sanitizer insists that clauses put into consecutive memory in
// the arena are still 8 byte aligned. We could also allocate 8 byte
// aligned memory there. However, assuming the real memory foot print
// of a clause is 8 bytes anyhow, we just allocate 8 byte aligned memory
// all the time (even if allocated outside of the arena).
//
assert (size > 1);
return align ((size - 2) * sizeof (int) + sizeof (Clause), 8);
}
size_t bytes () const { return bytes (size); }
// Check whether this clause is ready to be collected and deleted. The
// 'reason' flag is only there to protect reason clauses in 'reduce',
// which does not backtrack to the root level. If garbage collection is
// triggered from a preprocessor, which backtracks to the root level, then
// 'reason' is false for sure. We want to use the same garbage collection
// code though for both situations and thus hide here this variance.
//
bool collect () const { return !reason && garbage; }
};
struct clause_smaller_size {
bool operator () (const Clause * a, const Clause * b) {
return a->size < b->size;
}
};
/*------------------------------------------------------------------------*/
// Place literals over the same variable close to each other. This would
// allow eager removal of identical literals and detection of tautological
// clauses but is only currently used for better logging (see also
// 'opts.logsort' in 'logging.cpp').
struct clause_lit_less_than {
bool operator () (int a, int b) const {
int s = abs (a), t = abs (b);
return s < t || (s == t && a < b);
}
};
}
#endif

429
hCaD_V2/src/collect.cpp Normal file
View File

@ -0,0 +1,429 @@
#include "internal.hpp"
namespace CaDiCaL {
/*------------------------------------------------------------------------*/
// Returns the positive number '1' ( > 0) if the given clause is root level
// satisfied or the negative number '-1' ( < 0) if it is not root level
// satisfied but contains a root level falsified literal. Otherwise, if it
// contains neither a satisfied nor falsified literal, then '0' is returned.
int Internal::clause_contains_fixed_literal (Clause * c) {
int satisfied = 0, falsified = 0;
for (const auto & lit : *c) {
const int tmp = fixed (lit);
if (tmp > 0) {
LOG (c, "root level satisfied literal %d in", lit);
satisfied++;
}
if (tmp < 0) {
LOG (c, "root level falsified literal %d in", lit);
falsified++;
}
}
if (satisfied) return 1;
else if (falsified) return -1;
else return 0;
}
// Assume that the clause is not root level satisfied but contains a literal
// set to false (root level falsified literal), so it can be shrunken. The
// clause data is not actually reallocated at this point to avoid dealing
// with issues of special policies for watching binary clauses or whether a
// clause is extended or not. Only its size field is adjusted accordingly
// after flushing out root level falsified literals.
void Internal::remove_falsified_literals (Clause * c) {
const const_literal_iterator end = c->end ();
const_literal_iterator i;
int num_non_false = 0;
for (i = c->begin (); num_non_false < 2 && i != end; i++)
if (fixed (*i) >= 0) num_non_false++;
if (num_non_false < 2) return;
if (proof) proof->flush_clause (c);
literal_iterator j = c->begin ();
for (i = j; i != end; i++) {
const int lit = *j++ = *i, tmp = fixed (lit);
assert (tmp <= 0);
if (tmp >= 0) continue;
LOG ("flushing %d", lit);
j--;
}
stats.collected += shrink_clause (c, j - c->begin ());
}
// If there are new units (fixed variables) since the last garbage
// collection we go over all clauses, mark satisfied ones as garbage and
// flush falsified literals. Otherwise if no new units have been generated
// since the last garbage collection just skip this step.
void Internal::mark_satisfied_clauses_as_garbage () {
if (last.collect.fixed >= stats.all.fixed) return;
last.collect.fixed = stats.all.fixed;
LOG ("marking satisfied clauses and removing falsified literals");
for (const auto & c : clauses) {
if (c->garbage) continue;
const int tmp = clause_contains_fixed_literal (c);
if (tmp > 0) mark_garbage (c);
else if (tmp < 0) remove_falsified_literals (c);
}
}
/*------------------------------------------------------------------------*/
// Reason clauses can not be collected.
//
// We protect reasons before and release protection after garbage collection
// (actually within garbage collection).
//
// For 'reduce' we still need to make sure that all clauses which should not
// be removed are marked as such and thus we need to call it before marking
// clauses to be flushed.
void Internal::protect_reasons () {
LOG ("protecting reason clauses of all assigned variables on trail");
assert (!protected_reasons);
size_t count = 0;
for (const auto & lit : trail) {
if (!active (lit)) continue;
assert (val (lit));
Var & v = var (lit);
assert (v.level > 0);
Clause * reason = v.reason;
if (!reason) continue;
LOG (reason, "protecting assigned %d reason %p", lit, (void*) reason);
assert (!reason->reason);
reason->reason = true;
count++;
}
LOG ("protected %zd reason clauses referenced on trail", count);
protected_reasons = true;
}
/*------------------------------------------------------------------------*/
// After garbage collection we reset the 'reason' flag of the reasons
// of assigned literals on the trail.
void Internal::unprotect_reasons () {
LOG ("unprotecting reasons clauses of all assigned variables on trail");
assert (protected_reasons);
size_t count = 0;
for (const auto & lit : trail) {
if (!active (lit)) continue;
assert (val (lit));
Var & v = var (lit);
assert (v.level > 0);
Clause * reason = v.reason;
if (!reason) continue;
LOG (reason, "unprotecting assigned %d reason %p", lit, (void*) reason);
assert (reason->reason);
reason->reason = false;
count++;
}
LOG ("unprotected %zd reason clauses referenced on trail", count);
protected_reasons = false;
}
/*------------------------------------------------------------------------*/
// Update occurrence lists before deleting garbage clauses in the context of
// preprocessing, e.g., during bounded variable elimination 'elim'. The
// result is the number of remaining clauses, which in this context means
// the number of non-garbage clauses.
size_t Internal::flush_occs (int lit) {
Occs & os = occs (lit);
const const_occs_iterator end = os.end ();
occs_iterator j = os.begin ();
const_occs_iterator i;
size_t res = 0;
Clause * c;
for (i = j; i != end; i++) {
c = *i;
if (c->collect ()) continue;
*j++ = c->moved ? c->copy : c;
assert (!c->redundant);
res++;
}
os.resize (j - os.begin ());
shrink_occs (os);
return res;
}
// Update watch lists before deleting garbage clauses in the context of
// 'reduce' where we watch and no occurrence lists. We have to protect
// reason clauses not be collected and thus we have this additional check
// hidden in 'Clause.collect', which for the root level context of
// preprocessing is actually redundant.
inline void Internal::flush_watches (int lit, Watches & saved) {
assert (saved.empty ());
Watches & ws = watches (lit);
const const_watch_iterator end = ws.end ();
watch_iterator j = ws.begin ();
const_watch_iterator i;
for (i = j; i != end; i++) {
Watch w = *i;
Clause * c = w.clause;
if (c->collect ()) continue;
if (c->moved) c = w.clause = c->copy;
w.size = c->size;
const int new_blit_pos = (c->literals[0] == lit);
assert (c->literals[!new_blit_pos] == lit); /*FW1*/
w.blit = c->literals[new_blit_pos];
if (w.binary ()) *j++ = w;
else saved.push_back (w);
}
ws.resize (j - ws.begin ());
for (const auto & w : saved) ws.push_back (w);
saved.clear ();
shrink_vector (ws);
}
void Internal::flush_all_occs_and_watches () {
if (occurring ())
for (auto idx : vars)
flush_occs (idx), flush_occs (-idx);
if (watching ()) {
Watches tmp;
for (auto idx : vars)
flush_watches (idx, tmp), flush_watches (-idx, tmp);
}
}
/*------------------------------------------------------------------------*/
void Internal::update_reason_references () {
LOG ("update assigned reason references");
size_t count = 0;
for (auto & lit : trail) {
if (!active (lit)) continue;
Var & v = var (lit);
Clause * c = v.reason;
if (!c) continue;
LOG (c, "updating assigned %d reason", lit);
assert (c->reason);
assert (c->moved);
Clause * d = c->copy;
v.reason = d;
count++;
}
LOG ("updated %zd assigned reason references", count);
}
/*------------------------------------------------------------------------*/
// This is a simple garbage collector which does not move clauses. It needs
// less space than the arena based clause allocator, but is not as cache
// efficient, since the copying garbage collector can put clauses together
// which are likely accessed after each other.
void Internal::delete_garbage_clauses () {
flush_all_occs_and_watches ();
LOG ("deleting garbage clauses");
int64_t collected_bytes = 0, collected_clauses = 0;
const auto end = clauses.end ();
auto j = clauses.begin (), i = j;
while (i != end) {
Clause * c = *j++ = *i++;
if (!c->collect ()) continue;
collected_bytes += c->bytes ();
collected_clauses++;
delete_clause (c);
j--;
}
clauses.resize (j - clauses.begin ());
shrink_vector (clauses);
PHASE ("collect", stats.collections,
"collected %" PRId64 " bytes of %" PRId64 " garbage clauses",
collected_bytes, collected_clauses);
}
/*------------------------------------------------------------------------*/
// This is the start of the copying garbage collector using the arena. At
// the core is the following function, which copies a clause to the 'to'
// space of the arena. Be careful if this clause is a reason of an
// assignment. In that case update the reason reference.
//
void Internal::copy_clause (Clause * c) {
LOG (c, "moving");
assert (!c->moved);
char * p = (char*) c;
char * q = arena.copy (p, c->bytes ());
c->copy = (Clause *) q;
c->moved = true;
LOG ("copied clause[%" PRId64 "] from %p to %p",
c->id, (void*) c, (void*) c->copy);
}
// This is the moving garbage collector.
void Internal::copy_non_garbage_clauses () {
size_t collected_clauses = 0, collected_bytes = 0;
size_t moved_clauses = 0, moved_bytes = 0;
// First determine 'moved_bytes' and 'collected_bytes'.
//
for (const auto & c : clauses)
if (!c->collect ()) moved_bytes += c->bytes (), moved_clauses++;
else collected_bytes += c->bytes (), collected_clauses++;
PHASE ("collect", stats.collections,
"moving %zd bytes %.0f%% of %zd non garbage clauses",
moved_bytes,
percent (moved_bytes, collected_bytes + moved_bytes),
moved_clauses);
// Prepare 'to' space of size 'moved_bytes'.
//
arena.prepare (moved_bytes);
// Keep clauses in arena in the same order.
//
if (opts.arenacompact)
for (const auto & c : clauses)
if (!c->collect () && arena.contains (c))
copy_clause (c);
if (opts.arenatype == 1 || !watching ()) {
// Localize according to current clause order.
// If the option 'opts.arenatype == 1' is set, then this means the
// solver uses the original order of clauses. If there are no watches,
// we can not use the watched based copying policies below. This
// happens if garbage collection is triggered during bounded variable
// elimination.
// Copy clauses according to the order of calling 'copy_clause', which
// in essence just gives a compacting garbage collector, since their
// relative order is kept, and actually already gives the largest
// benefit due to better cache locality.
for (const auto & c : clauses)
if (!c->moved && !c->collect ())
copy_clause (c);
} else if (opts.arenatype == 2) {
// Localize according to (original) variable order.
// This is almost the version used by MiniSAT and descendants.
// Our version uses saved phases too.
for (int sign = -1; sign <= 1; sign += 2)
for (auto idx : vars)
for (const auto & w : watches (sign * likely_phase (idx)))
if (!w.clause->moved && !w.clause->collect ())
copy_clause (w.clause);
} else {
// Localize according to decision queue order.
// This is the default for search. It allocates clauses in the order of
// the decision queue and also uses saved phases. It seems faster than
// the MiniSAT version and thus we keep 'opts.arenatype == 3'.
assert (opts.arenatype == 3);
for (int sign = -1; sign <= 1; sign += 2)
for (int idx = queue.last; idx; idx = link (idx).prev)
for (const auto & w : watches (sign * likely_phase (idx)))
if (!w.clause->moved && !w.clause->collect ())
copy_clause (w.clause);
}
// Do not forget to move clauses which are not watched, which happened in
// a rare situation, and now is only left as defensive code.
//
for (const auto & c : clauses)
if (!c->collect () && !c->moved)
copy_clause (c);
flush_all_occs_and_watches ();
update_reason_references ();
// Replace and flush clause references in 'clauses'.
//
const auto end = clauses.end ();
auto j = clauses.begin (), i = j;
for (; i != end; i++) {
Clause * c = *i;
if (c->collect ()) delete_clause (c);
else assert (c->moved), *j++ = c->copy, deallocate_clause (c);
}
clauses.resize (j - clauses.begin ());
if (clauses.size () < clauses.capacity ()/2) shrink_vector (clauses);
if (opts.arenasort)
rsort (clauses.begin (), clauses.end (), pointer_rank ());
// Release 'from' space completely and then swap 'to' with 'from'.
//
arena.swap ();
PHASE ("collect", stats.collections,
"collected %zd bytes %.0f%% of %zd garbage clauses",
collected_bytes,
percent (collected_bytes, collected_bytes + moved_bytes),
collected_clauses);
}
/*------------------------------------------------------------------------*/
// Maintaining clause statistics is complex and error prone but necessary
// for proper scheduling of garbage collection, particularly during bounded
// variable elimination. With this function we can check whether these
// statistics are updated correctly.
void Internal::check_clause_stats () {
#ifndef NDEBUG
int64_t irredundant = 0, redundant = 0, total = 0, irrbytes = 0;
for (const auto & c : clauses) {
if (c->garbage) continue;
if (c->redundant) redundant++; else irredundant++;
if (!c->redundant) irrbytes += c->bytes ();
total++;
}
assert (stats.current.irredundant == irredundant);
assert (stats.current.redundant == redundant);
assert (stats.current.total == total);
assert (stats.irrbytes == irrbytes);
#endif
}
/*------------------------------------------------------------------------*/
bool Internal::arenaing () {
return opts.arena && (stats.collections > 1);
}
void Internal::garbage_collection () {
if (unsat) return;
START (collect);
report ('G', 1);
stats.collections++;
mark_satisfied_clauses_as_garbage ();
if (!protected_reasons) protect_reasons ();
if (arenaing ()) copy_non_garbage_clauses ();
else delete_garbage_clauses ();
check_clause_stats ();
check_var_stats ();
unprotect_reasons ();
report ('C', 1);
STOP (collect);
}
}

416
hCaD_V2/src/compact.cpp Normal file
View File

@ -0,0 +1,416 @@
#include "internal.hpp"
namespace CaDiCaL {
/*------------------------------------------------------------------------*/
// Compacting removes holes generated by inactive variables (fixed,
// eliminated, substituted or pure) by mapping active variables indices down
// to a contiguous interval of indices.
/*------------------------------------------------------------------------*/
bool Internal::compacting () {
if (level) return false;
if (!opts.compact) return false;
if (stats.conflicts < lim.compact) return false;
int inactive = max_var - active ();
assert (inactive >= 0);
if (!inactive) return false;
if (inactive < opts.compactmin) return false;
return inactive >= (1e-3 * opts.compactlim) * max_var;
}
/*------------------------------------------------------------------------*/
struct Mapper {
Internal * internal;
int new_max_var; // New 'max_var' after compacting.
int * table; // Old variable index to new literal map.
int first_fixed; // First fixed variable index.
int map_first_fixed; // Mapped literal of first fixed variable.
signed char first_fixed_val; // Value of first fixed variable.
size_t new_vsize;
/*----------------------------------------------------------------------*/
// We produce a compacting garbage collector like map of old 'src' to
// new 'dst' variables. Inactive variables are just skipped except for
// fixed ones which will be mapped to the first fixed variable (in the
// appropriate phase). This avoids to handle the case 'fixed value'
// separately as it is done in Lingeling, where fixed variables are
// mapped to the internal variable '1'.
//
Mapper (Internal * i) :
internal (i),
new_max_var (0),
first_fixed (0),
map_first_fixed (0),
first_fixed_val (0)
{
table = new int [ internal->max_var + 1u ];
clear_n (table, internal->max_var + 1u);
assert (!internal->level);
for (auto src : internal->vars) {
const Flags & f = internal->flags (src);
if (f.active ()) table[src] = ++new_max_var;
else if (f.fixed () && !first_fixed)
table[first_fixed = src] = map_first_fixed = ++new_max_var;
}
first_fixed_val = first_fixed ? internal->val (first_fixed) : 0;
new_vsize = new_max_var + 1u;
}
~Mapper () { delete [] table; }
/*----------------------------------------------------------------------*/
// Map old variable indices. A result of zero means not mapped.
//
int map_idx (int src) {
assert (0 < src);
assert (src <= internal->max_var);
const int res = table[src];
assert (res <= new_max_var);
return res;
}
/*----------------------------------------------------------------------*/
// The 'map_idx' above is just a look-up into the 'table'. Here we have
// to care about signedness of 'src', and in addition that fixed variables
// have all to be mapped to the first fixed variable 'first_fixed'.
//
int map_lit (int src) {
int res = map_idx (abs (src));
if (!res) {
const signed char tmp = internal->val (src);
if (tmp) {
assert (first_fixed);
res = map_first_fixed;
if (tmp != first_fixed_val) res = -res;
}
} else if ((src) < 0) res = -res;
assert (abs (res) <= new_max_var);
return res;
}
/*----------------------------------------------------------------------*/
// Map positive variable indices in vector.
//
template<class T>
void map_vector (vector<T> & v) {
for (auto src : internal->vars) {
const int dst = map_idx (src);
if (!dst) continue;
assert (0 < dst);
assert (dst <= src);
v[dst] = v[src];
}
v.resize (new_vsize);
shrink_vector (v);
}
/*----------------------------------------------------------------------*/
// Map positive and negative variable indices in two-sided vector.
//
template<class T>
void map2_vector (vector<T> & v) {
for (auto src : internal->vars) {
const int dst = map_idx (src);
if (!dst) continue;
assert (0 < dst);
assert (dst <= src);
v[2*dst] = v[2*src];
v[2*dst + 1] = v[2*src + 1];
}
v.resize (2*new_vsize);
shrink_vector (v);
}
/*----------------------------------------------------------------------*/
// Map a vector of literals, flush inactive literals, then resize and
// shrink it to fit the new size after flushing.
//
void map_flush_and_shrink_lits (vector<int> & v) {
const auto end = v.end ();
auto j = v.begin (), i = j;
for (; i != end; i++) {
const int src = *i;
int dst = map_idx (abs (src));
assert (abs (dst) <= abs (src));
if (!dst) continue;
if (src < 0) dst = -dst;
*j++ = dst;
}
v.resize (j - v.begin ());
shrink_vector (v);
}
};
/*------------------------------------------------------------------------*/
static signed char * ignore_clang_analyze_memory_leak_warning;
void Internal::compact () {
START (compact);
assert (active () < max_var);
stats.compacts++;
assert (!level);
assert (!unsat);
assert (!conflict);
assert (clause.empty ());
assert (levels.empty ());
assert (analyzed.empty ());
assert (minimized.empty ());
assert (control.size () == 1);
assert (propagated == trail.size ());
garbage_collection ();
Mapper mapper (this);
if (mapper.first_fixed)
LOG ("found first fixed %d",
sign (mapper.first_fixed_val)*mapper.first_fixed);
else LOG ("no variable fixed");
if (!assumptions.empty ()) {
assert (!external->assumptions.empty ());
LOG ("temporarily reset internal assumptions");
reset_assumptions ();
}
/*======================================================================*/
// In this first part we only map stuff without reallocation / shrinking.
/*======================================================================*/
// Flush the external indices. This has to occur before we map 'vals'.
//
for (auto eidx : external->vars) {
int src = external->e2i[eidx];
if (!src) continue;
int dst = mapper.map_lit (src);
LOG ("compact %" PRId64 " maps external %d to internal %d from internal %d",
stats.compacts, eidx, dst, src);
external->e2i[eidx] = dst;
}
// Map the literals in all clauses.
//
for (const auto & c : clauses) {
assert (!c->garbage);
for (auto & src : *c) {
assert (!val (src));
int dst;
dst = mapper.map_lit (src);
assert (dst || c->garbage);
src = dst;
}
}
// Map the blocking literals in all watches.
//
if (!wtab.empty ())
for (auto lit : lits)
for (auto & w : watches (lit))
w.blit = mapper.map_lit (w.blit);
// We first flush inactive variables and map the links in the queue. This
// has to be done before we map the actual links data structure 'links'.
{
int prev = 0, mapped_prev = 0, next;
for (int idx = queue.first; idx; idx = next) {
next = links[idx].next;
if (idx == mapper.first_fixed) continue;
const int dst = mapper.map_idx (idx);
if (!dst) continue;
assert (active (idx));
if (prev) links[prev].next = dst; else queue.first = dst;
links[idx].prev = mapped_prev;
mapped_prev = dst;
prev = idx;
}
if (prev) links[prev].next = 0; else queue.first = 0;
queue.unassigned = queue.last = mapped_prev;
}
/*======================================================================*/
// In the second part we map, flush and shrink arrays.
/*======================================================================*/
mapper.map_flush_and_shrink_lits (trail);
propagated = trail.size ();
if (mapper.first_fixed) {
assert (trail.size () == 1);
var (mapper.first_fixed).trail = 0; // before mapping 'vtab'
} else assert (trail.empty ());
if (!probes.empty ())
mapper.map_flush_and_shrink_lits (probes);
/*======================================================================*/
// In the third part we map stuff and also reallocate memory.
/*======================================================================*/
// Now we continue in reverse order of allocated bytes, e.g., see
// 'Internal::enlarge' which reallocates in order of allocated bytes.
mapper.map_vector (ftab);
mapper.map_vector (parents);
mapper.map_vector (marks);
mapper.map_vector (phases.saved);
mapper.map_vector (phases.forced);
mapper.map_vector (phases.target);
mapper.map_vector (phases.best);
mapper.map_vector (phases.prev);
mapper.map_vector (phases.min);
if(opts.psids) mapper.map2_vector(phases.act);
// Special code for 'frozentab'.
//
for (auto src : vars) {
const int dst = mapper.map_idx (src);
if (!dst) continue;
if (src == dst) continue;
assert (dst < src);
frozentab[dst] += frozentab[src];
frozentab[src] = 0;
}
frozentab.resize (mapper.new_vsize);
shrink_vector (frozentab);
/*----------------------------------------------------------------------*/
if (!external->assumptions.empty ()) {
for (const auto & elit : external->assumptions) {
assert (elit);
assert (elit != INT_MIN);
int eidx = abs (elit);
assert (eidx <= external->max_var);
int ilit = external->e2i[eidx];
assert (ilit); // Because we froze all!!!
if (elit < 0) ilit = -ilit;
assume (ilit);
}
PHASE ("compact", stats.compacts,
"reassumed %zd external assumptions",
external->assumptions.size ());
}
// Special case for 'val' as for 'val' we trade branch less code for
// memory and always allocated an [-maxvar,...,maxvar] array.
{
signed char * new_vals = new signed char [ 2*mapper.new_vsize ];
ignore_clang_analyze_memory_leak_warning = new_vals;
new_vals += mapper.new_vsize;
for (auto src : vars)
new_vals[-mapper.map_idx (src)] = vals[-src];
for (auto src : vars)
new_vals[mapper.map_idx (src)] = vals[src];
new_vals[0] = 0;
vals -= vsize;
delete [] vals;
vals = new_vals;
}
mapper.map_vector (i2e);
mapper.map2_vector (ptab);
mapper.map_vector (btab);
mapper.map_vector (gtab);
mapper.map_vector (links);
mapper.map_vector (vtab);
if (!ntab.empty ()) mapper.map2_vector (ntab);
if (!wtab.empty ()) mapper.map2_vector (wtab);
if (!otab.empty ()) mapper.map2_vector (otab);
if (!big.empty ()) mapper.map2_vector (big);
/*======================================================================*/
// In the fourth part we map the binary heap for scores.
/*======================================================================*/
// The simplest way to map a binary heap is to get all elements from the
// heap and reinsert them. This could be slightly improved in terms of
// speed if we add a 'flush (int * map)' function to 'Heap', but that is
// pretty complicated and would require that the 'Heap' knows that mapped
// elements with 'zero' destination should be flushed.
vector<int> saved;
assert (saved.empty ());
if (!scores.empty ()) {
while (!scores.empty ()) {
const int src = scores.front ();
scores.pop_front ();
const int dst = mapper.map_idx (src);
if (!dst) continue;
if (src == mapper.first_fixed) continue;
saved.push_back (dst);
}
scores.erase ();
}
mapper.map_vector (stab);
if (!saved.empty ()) {
for (const auto & idx : saved)
scores.push_back (idx);
scores.shrink ();
}
/*----------------------------------------------------------------------*/
PHASE ("compact", stats.compacts,
"reducing internal variables from %d to %d",
max_var, mapper.new_max_var);
/*----------------------------------------------------------------------*/
// Need to adjust the target and best assigned counters too.
size_t new_target_assigned = 0, new_best_assigned = 0;
for (auto idx : Range (mapper.new_max_var)) {
if (phases.target[idx]) new_target_assigned++;
if (phases.best[idx]) new_best_assigned++;
}
LOG ("reset target assigned from %zd to %zd",
target_assigned, new_target_assigned);
LOG ("reset best assigned from %zd to %zd",
best_assigned, new_best_assigned);
target_assigned = new_target_assigned;
best_assigned = new_best_assigned;
no_conflict_until = 0;
INIT_EMA (averages.current.trail.fast, opts.ematrailfast);
INIT_EMA (averages.current.trail.slow, opts.ematrailslow);
/*----------------------------------------------------------------------*/
max_var = mapper.new_max_var;
vsize = mapper.new_vsize;
stats.unused = 0;
stats.inactive = stats.now.fixed = mapper.first_fixed ? 1 : 0;
stats.now.substituted = stats.now.eliminated = stats.now.pure = 0;
check_var_stats ();
int64_t delta = opts.compactint * (stats.compacts + 1);
lim.compact = stats.conflicts + delta;
PHASE ("compact", stats.compacts,
"new compact limit %" PRId64 " after %" PRId64 " conflicts",
lim.compact, delta);
STOP (compact);
}
}

898
hCaD_V2/src/condition.cpp Normal file
View File

@ -0,0 +1,898 @@
#include "internal.hpp"
namespace CaDiCaL {
/*------------------------------------------------------------------------*/
// Globally blocked clause elimination (which we call here 'conditioning')
// is described first in the PhD thesis of Benjamin Kiesl from 2019. An
// extended version, which in particular describes the algorithm implemented
// below is in our invited ATVA'19 paper [KieslHeuleBiere-ATVA'19]. This
// accordingly needs witnesses consisting potentially of more than one
// literal. It is the first technique implemented in CaDiCaL with this
// feature (PR clause elimination thus should work in principle too).
// Basically globally blocked clauses are like set blocked clauses, except
// that the witness cube (of literals to be flipped during reconstruction)
// can contain variables which are not in the blocked clause. This
// can simulate some interesting global optimizations like 'headlines' from
// the FAN algorithm for ATPG. The technique was actually motivated to
// simulate this optimization. It turns out that globally blocked clauses
// can be seen as 'conditional autarkies', where in essence the condition
// cube is the negation of the globally blocked redundant clause (it
// needs to contain one autarky literal though) and the autarky part
// represents the witness.
/*------------------------------------------------------------------------*/
// Elimination of globally blocked clauses is first tried in regular
// intervals in terms of the number of conflicts. Then the main heuristics
// is to trigger 'condition' if the decision level is above the current
// moving average of the back jump level.
// TODO We might need to consider less frequent conditioning.
bool Internal::conditioning () {
if (!opts.condition) return false;
if (!preprocessing && !opts.inprocessing) return false;
if (preprocessing) assert (lim.preprocessing);
// Triggered in regular 'opts.conditionint' conflict intervals.
//
if (lim.condition > stats.conflicts) return false;
if (!level) return false; // One decision necessary.
if (level <= averages.current.jump) return false; // Main heuristic.
if (!stats.current.irredundant) return false;
double remain = active ();
if (!remain) return false;
double ratio = stats.current.irredundant / remain;
return ratio <= opts.conditionmaxrat;
}
/*------------------------------------------------------------------------*/
// We start with the current assignment and then temporarily unassign
// literals. They are reassigned afterwards. The global state of the CDCL
// solver should not change though. Thus we copied from 'search_unassign'
// in 'backtrack.cpp' what is needed to unassign literals and then from
// 'search_assign' in 'propagate.cpp' what is needed for reassigning
// literals, but restricted the copied code to only updating the actual
// assignment (in 'vals') and not changing anything else.
// We use temporarily unassigning for two purposes. First, if a conditional
// literal does not occur negated in a candidate clause it is unassigned.
// Second, as a minor optimization, we first unassign all root-level
// assigned (fixed) literals, to avoid checking the decision level of
// literals during the procedure.
void Internal::condition_unassign (int lit) {
LOG ("condition unassign %d", lit);
assert (val (lit) > 0);
vals[lit] = vals[-lit] = 0;
}
void Internal::condition_assign (int lit) {
LOG ("condition assign %d", lit);
assert (!val (lit));
vals[lit] = 1;
vals[-lit] = -1;
assert (val (lit) > 0);
assert (val (-lit) < 0);
}
/*------------------------------------------------------------------------*/
// The current partition into conditional part and autarky part during
// refinement is represented through a conditional bit in 'marks'.
inline bool Internal::is_conditional_literal (int lit) const {
return val (lit) > 0 && getbit (lit, 0);
}
inline bool Internal::is_autarky_literal (int lit) const {
return val (lit) > 0 && !getbit (lit, 0);
}
inline void Internal::mark_as_conditional_literal (int lit) {
LOG ("marking %d as conditional literal", lit);
assert (val (lit) > 0);
setbit (lit, 0);
assert (is_conditional_literal (lit));
assert (!is_autarky_literal (lit));
}
inline void Internal::unmark_as_conditional_literal (int lit) {
LOG ("unmarking %d as conditional literal", lit);
assert (is_conditional_literal (lit));
unsetbit (lit, 0);
}
/*------------------------------------------------------------------------*/
// We also need to know the literals which are in the current clause. These
// are just marked (also in 'marks' but with the (signed) upper two bits).
// We need a signed mark here, since we have to distinguish positive and
// negative occurrences of literals in the candidate clause.
inline bool Internal::is_in_candidate_clause (int lit) const {
return marked67 (lit) > 0;
}
inline void Internal::mark_in_candidate_clause (int lit) {
LOG ("marking %d as literal of the candidate clause", lit);
mark67 (lit);
assert (is_in_candidate_clause (lit));
assert (!is_in_candidate_clause (-lit));
}
inline void Internal::unmark_in_candidate_clause (int lit) {
LOG ("unmarking %d as literal of the candidate clause", lit);
assert (is_in_candidate_clause (lit));
unmark67 (lit);
}
/*------------------------------------------------------------------------*/
struct less_conditioned {
bool operator () (Clause * a, Clause * b) {
return !a->conditioned && b->conditioned;
}
};
// This is the function for eliminating globally blocked clauses. It is
// triggered during CDCL search according to 'conditioning' above and uses
// the current assignment as basis to find globally blocked clauses.
long Internal::condition_round (long delta) {
long limit, props = 0;
if (LONG_MAX - delta < stats.condprops) limit = LONG_MAX;
else limit = stats.condprops + delta;
size_t initial_trail_level = trail.size ();
int initial_level = level;
LOG ("initial trail level %zd", initial_trail_level);
protect_reasons ();
#if defined(LOGGING) || !defined(NDEBUG)
int additionally_assigned = 0;
#endif
for (auto idx : vars) {
const signed char tmp = val (idx);
Var & v = var (idx);
if (tmp) {
if (v.level) {
const int lit = tmp < 0 ? -idx : idx;
if (!active (idx)) {
LOG ("temporarily unassigning inactive literal %d", lit);
condition_unassign (lit);
} if (frozen (idx)) {
LOG ("temporarily unassigning frozen literal %d", lit);
condition_unassign (lit);
}
}
} else if (frozen (idx)) {
LOG ("keeping frozen literal %d unassigned", idx);
} else if (!active (idx)) {
LOG ("keeping inactive literal %d unassigned", idx);
} else { // if (preprocessing) {
if (initial_level == level) {
level++;
LOG ("new condition decision level");
}
const int lit = decide_phase (idx, true);
condition_assign (lit);
v.level = level;
trail.push_back (lit);
#if defined(LOGGING) || !defined(NDEBUG)
additionally_assigned++;
#endif
}
}
LOG ("assigned %d additional literals", additionally_assigned);
// We compute statistics about the size of the assignments.
//
// The initial assignment consists of the non-root-level assigned literals
// split into a conditional and an autarky part. The conditional part
// consists of literals assigned true and occurring negated in a clause
// (touch the clause), which does not contain another literal assigned to
// true. This initial partition is the same for all refinements used in
// checking whether a candidate clause is globally blocked.
//
// For each candidate clause some of the conditional literals have to be
// unassigned, and the autarky is shrunken by turning some of the autarky
// literals into conditional literals (which might get unassigned in a
// later refinement though).
//
// The fix-point of this procedure produces a final assignment, which
// consists of the remaining assigned literals, again split into a
// conditional and an autarky part.
//
struct { size_t assigned, conditional, autarky; } initial, remain;
initial.assigned = 0;
for (auto idx : vars) {
const signed char tmp = val (idx);
if (!tmp) continue;
if (!var (idx).level) continue;
LOG ("initial assignment %ds", tmp < 0 ? -idx : idx);
initial.assigned++;
}
PHASE ("condition", stats.conditionings,
"initial assignment of size %zd", initial.assigned);
// For each candidate clause we refine the assignment (monotonically),
// by unassigning some conditional literals and turning some autarky
// literals into conditionals.
//
// As the conditional part is usually smaller than the autarky part our
// implementation only explicitly maintains the initial conditional part,
// with conditional bit set to true through 'mark_as_conditional_literal'.
// The autarky part consists of all literals assigned true which do not
// have their conditional bit set to true. Since in both cases the
// literal has to be assigned true, we only need a single bit for both the
// literal as well as its negation (it does not have to be 'signed').
//
vector<int> conditional;
vector<Clause *> candidates; // Gather candidate clauses.
size_t watched = 0; // Number of watched clauses.
initial.autarky = initial.assigned; // Initially all are in autarky
initial.conditional = 0; // and none in conditional part.
// Upper bound on the number of watched clauses. In principle one could
// use 'SIZE_MAX' but this is not available by default (yet).
//
const size_t size_max = clauses.size () + 1;
// Initialize additional occurrence lists.
//
init_occs ();
// Number of previously conditioned and unconditioned candidates.
//
size_t conditioned = 0, unconditioned = 0;
// Now go over all (non-garbage) irredundant clauses and check whether
// they are candidates, have to be watched, or whether they force the
// negation of some of their literals to be conditional initially.
//
for (const auto & c : clauses) {
if (c->garbage) continue; // Can already be ignored.
if (c->redundant) continue; // Ignore redundant clauses too.
// First determine the following numbers for the candidate clause
// (restricted to non-root-level assignments).
//
int positive = 0; // Number true literals.
int negative = 0; // Number false literals.
int watch = 0; // True Literal to watch.
//
size_t minsize = size_max; // Number of occurrences of 'watch'.
//
// But also ignore root-level satisfied but not yet garbage clauses.
//
bool satisfied = false; // Root level satisfied.
//
for (const_literal_iterator l = c->begin ();
!satisfied && l != c->end ();
l++)
{
const int lit = *l;
const signed char tmp = val (lit);
if (tmp && !var (lit).level) satisfied = (tmp > 0);
else if (tmp < 0) negative++;
else if (tmp > 0) {
const size_t size = occs (lit).size ();
if (size < minsize) watch = lit, minsize = size;
positive++;
}
}
if (satisfied) { // Ignore root-level satisfied clauses.
mark_garbage (c); // But mark them as garbage already now.
continue; // ... with next clause 'c'.
}
// Candidates are clauses with at least a positive literal in it.
//
if (positive > 0) {
LOG (c, "found %d positive literals in candidate", positive);
candidates.push_back (c);
if (c->conditioned) conditioned++;
else unconditioned++;
}
// Only one positive literal in each clauses with also at least one
// negative literal has to be watched in occurrence lists. These
// watched clauses will be checked to contain only negative literals as
// soon such a positive literal is unassigned. If this is the case
// these false literals have to be unassigned and potentially new
// conditional literals have to be determined.
//
// Note that only conditional literals are unassigned. However it does
// not matter that we might also watch autarky literals, because either
// such an autarky literal remains a witness that the clause is
// satisfied as long it remains an autarky literal. Otherwise at one
// point it becomes conditional and is unassigned, but then a
// replacement watch will be searched.
//
if (negative > 0 && positive > 0) {
LOG (c, "found %d negative literals in candidate", negative);
assert (watch);
assert (val (watch) > 0);
Occs & os = occs (watch);
assert (os.size () == minsize);
os.push_back (c);
watched++;
LOG (c, "watching %d with %zd occurrences in", watch, minsize);
}
// The initial global conditional part for the current assignment is
// extracted from clauses with only negative literals. It is the same
// for all considered candidate clauses. These negative literals make up
// the global conditional part, are marked here.
//
if (negative > 0 && !positive) {
size_t new_conditionals = 0;
for (const_literal_iterator l = c->begin ();
l != c->end ();
l++)
{
const int lit = *l;
signed char tmp = val (lit);
if (!tmp) continue;
assert (tmp < 0);
if (!var (lit).level) continue; // Not unassigned yet!
if (is_conditional_literal (-lit)) continue;
mark_as_conditional_literal (-lit);
conditional.push_back (-lit);
new_conditionals++;
}
if (new_conditionals > 0)
LOG (c, "marked %zu negations of literals as conditional in",
new_conditionals);
initial.conditional += new_conditionals;
assert (initial.autarky >= new_conditionals);
initial.autarky -= new_conditionals;
}
} // End of loop over all clauses to collect candidates etc.
PHASE ("condition", stats.conditionings,
"found %zd candidate clauses", candidates.size ());
PHASE ("condition", stats.conditionings,
"watching %zu literals and clauses", watched);
PHASE ("condition", stats.conditionings,
"initially %zd conditional literals %.0f%%",
initial.conditional, percent (initial.conditional, initial.assigned));
PHASE ("condition", stats.conditionings,
"initially %zd autarky literals %.0f%%",
initial.autarky, percent (initial.autarky, initial.assigned));
#ifdef LOGGING
for (size_t i = 0; i < conditional.size (); i++) {
LOG ("initial conditional %d", conditional[i]);
assert (is_conditional_literal (conditional[i]));
}
for (size_t i = 0; i < trail.size (); i++)
if (is_autarky_literal (trail[i]))
LOG ("initial autarky %d", trail[i]);
#endif
assert (initial.conditional == conditional.size ());
assert (initial.assigned == initial.conditional + initial.autarky);
stats.condassinit += initial.assigned;
stats.condcondinit += initial.conditional;
stats.condautinit += initial.autarky;
stats.condassvars += active ();
// To speed-up and particularly simplify the code we unassign all
// root-level variables temporarily, actually all inactive assigned
// variables. This allows us to avoid tests on whether an assigned
// literal is actually root-level assigned and thus should be ignored (not
// considered to be assigned). For this to work we have to ignore root
// level satisfied clauses as done above. These are neither candidates
// nor have to be watched. Remaining originally root-level assigned
// literals in clauses are only set to false.
//
for (const auto & lit : trail)
if (fixed (lit))
condition_unassign (lit);
// Stack to save temporarily unassigned (conditional) literals.
//
vector<int> unassigned;
// Make sure to focus on clauses not tried before by marking clauses which
// have been checked before using the 'conditioned' bit of clauses. If all
// candidates have their bit set, we have to reset it. Since the
// assignment might be completely different then last time and thus also
// the set of candidates this method does not really exactly lead to a
// round robin scheme of scheduling clauses.
//
// TODO consider computing conditioned and unconditioned over all clauses.
//
assert (conditioned + unconditioned == candidates.size ());
if (conditioned && unconditioned) {
stable_sort (candidates.begin (), candidates.end (), less_conditioned ());
PHASE ("condition", stats.conditionings,
"focusing on %zd candidates %.0f%% not tried last time",
unconditioned, percent (unconditioned, candidates.size ()));
} else if (conditioned && !unconditioned) {
for (auto const & c : candidates) {
assert (c->conditioned);
c->conditioned = false; // Reset 'conditioned' bit.
}
PHASE ("condition", stats.conditionings,
"all %zd candidates tried before", conditioned);
} else {
assert (!conditioned);
PHASE ("condition", stats.conditionings,
"all %zd candidates are fresh", unconditioned);
}
// TODO prune assignments further!
// And thus might result in less watched clauses.
// So watching should be done here and not earlier.
// Also, see below, we might need to consider the negation of unassigned
// literals in candidate clauses as being watched.
// Now try to block all candidate clauses.
//
long blocked = 0; // Number of Successfully blocked clauses.
//
size_t untried = candidates.size ();
for (const auto & c : candidates) {
if (initial.autarky <= 0) break;
if (c->reason) continue;
bool terminated_or_limit_hit = true;
if (terminated_asynchronously ())
LOG ("asynchronous termination detected");
else if (stats.condprops >= limit)
LOG ("condition propagation limit %ld hit", limit);
else terminated_or_limit_hit = false;
if (terminated_or_limit_hit) {
PHASE ("condition", stats.conditionings,
"%zd candidates %.0f%% not tried after %ld propagations",
untried, percent (untried, candidates.size ()), props);
break;
}
untried--;
assert (!c->garbage);
assert (!c->redundant);
LOG (c, "candidate");
c->conditioned = 1; // Next time later.
// We watch an autarky literal in the clause, and can stop trying to
// globally block the clause as soon it turns into a conditional
// literal and we can not find another one. If the fix-point assignment
// is reached and we still have an autarky literal left the watched one
// is reported as witness for this clause being globally blocked.
//
int watched_autarky_literal = 0;
// First mark all true literals in the candidate clause and find an
// autarky literal which witnesses that this clause has still a chance
// to be globally blocked.
//
for (const_literal_iterator l = c->begin (); l != c->end (); l++)
{
const int lit = *l;
mark_in_candidate_clause (lit);
if (watched_autarky_literal) continue;
if (!is_autarky_literal (lit)) continue;
watched_autarky_literal = lit;
// TODO assign non-assigned literals to false?
// Which might need to trigger watching additional clauses.
}
if (!watched_autarky_literal) {
LOG ("no initial autarky literal found");
for (const_literal_iterator l = c->begin (); l != c->end (); l++)
unmark_in_candidate_clause (*l);
continue;
}
stats.condcands++; // Only now ...
LOG ("watching first autarky literal %d", watched_autarky_literal);
// Save assignment sizes for statistics, logging and checking.
//
remain = initial;
// Position of next conditional and unassigned literal to process in the
// 'conditional' and the 'unassigned' stack.
//
struct { size_t conditional, unassigned; } next = { 0, 0 };
assert (unassigned.empty ());
assert (conditional.size () == initial.conditional);
while (watched_autarky_literal &&
stats.condprops < limit &&
next.conditional < conditional.size ()) {
assert (next.unassigned == unassigned.size ());
const int conditional_lit = conditional[next.conditional++];
LOG ("processing next conditional %d", conditional_lit);
assert (is_conditional_literal (conditional_lit));
if (is_in_candidate_clause (-conditional_lit)) {
LOG ("conditional %d negated in candidate clause", conditional_lit);
continue;
}
LOG ("conditional %d does not occur negated in candidate clause",
conditional_lit);
condition_unassign (conditional_lit);
assert (!is_conditional_literal (conditional_lit));
unassigned.push_back (conditional_lit);
assert (remain.assigned > 0);
assert (remain.conditional > 0);
remain.conditional--;
remain.assigned--;
while (watched_autarky_literal &&
stats.condprops < limit &&
next.unassigned < unassigned.size ())
{
const int unassigned_lit = unassigned[next.unassigned++];
LOG ("processing next unassigned %d", unassigned_lit);
assert (!val (unassigned_lit));
props++;
stats.condprops++;
Occs & os = occs (unassigned_lit);
if (os.empty ()) continue;
// Traverse all watched clauses of 'unassigned_lit' and find
// replacement watches or if none is found turn the negation of all
// false autarky literals in that clause into conditional literals.
// If one of those autarky literals is the watched autarky literal
// in the candidate clause, that one has to be updated too.
//
// We expect that this loop is a hot-spot for the procedure and thus
// are more careful about accessing end points for iterating.
//
auto i = os.begin (), j = i;
for (;
watched_autarky_literal && j != os.end ();
j++)
{
Clause * d = *i++ = *j;
int replacement = 0; // New watched literal in 'd'.
int negative = 0; // Negative autarky literals in 'd'.
for (const_literal_iterator l = d->begin ();
l != d->end ();
l++)
{
const int lit = *l;
const signed char tmp = val (lit);
if (tmp > 0) replacement = lit;
if (tmp < 0 && is_autarky_literal (-lit)) negative++;
}
if (replacement) {
LOG ("found replacement %d for unassigned %d",
replacement, unassigned_lit);
LOG (d, "unwatching %d in", unassigned_lit);
i--; // Drop watch!
LOG (d, "watching %d in", replacement);
assert (replacement != unassigned_lit);
occs (replacement).push_back (d);
continue; // ... with next watched clause 'd'.
}
LOG ("no replacement found for unassigned %d", unassigned_lit);
// Keep watching 'd' by 'unassigned_lit' if no replacement found.
if (!negative) {
LOG (d, "no negative autarky literals left in");
continue; // ... with next watched clause 'd'.
}
LOG (d, "found %d negative autarky literals in", negative);
for (const_literal_iterator l = d->begin ();
watched_autarky_literal && l != d->end ();
l++)
{
const int lit = *l;
if (!is_autarky_literal (-lit)) continue;
mark_as_conditional_literal (-lit);
conditional.push_back (-lit);
remain.conditional++;
assert (remain.autarky > 0);
remain.autarky--;
if (-lit != watched_autarky_literal) continue;
LOG ("need to replace autarky literal %d in candidate", -lit);
replacement = 0;
// TODO save starting point because we only move it forward?
for (const_literal_iterator k = c->begin ();
!replacement && k != c->end ();
k++)
{
const int other = *k;
if (is_autarky_literal (other)) replacement = other;
}
watched_autarky_literal = replacement;
if (replacement) {
LOG (c, "watching autarky %d instead %d in candidate",
replacement, watched_autarky_literal);
watched_autarky_literal = replacement;
} else {
LOG ("failed to find an autarky replacement");
watched_autarky_literal = 0; // Breaks out of 4 loops!!!!!
}
} // End of loop of turning autarky literals into conditionals.
} // End of loop of all watched clauses of an unassigned literal.
//
// We might abort the occurrence traversal early but already
// removed some watches, thus have to just copy the rest.
//
if (i < j) {
while (j != os.end ()) *i++ = *j++;
LOG ("flushed %zd occurrences of %d",
os.end () - i, unassigned_lit);
os.resize (i - os.begin ());
}
} // End of loop which goes over all unprocessed unassigned literals.
} // End of loop which goes over all unprocessed conditional literals.
// We are still processing the candidate 'c' and now have reached a
// final fix-point assignment partitioned into a conditional and an
// autarky part, or during unassigned literals figured that there is no
// positive autarky literal left in 'c'.
LOG ("remaining assignment of size %zd", remain.assigned);
LOG ("remaining conditional part of size %zd", remain.conditional);
LOG ("remaining autarky part of size %zd", remain.autarky);
//
assert (remain.assigned - remain.conditional == remain.autarky);
//
#if defined(LOGGING) || !defined(NDEBUG)
//
// This is a sanity check, that the size of our implicit representation
// of the autarky part matches our 'remain' counts. We need the same
// code for determining autarky literals as in the loop below which adds
// autarky literals to the extension stack.
//
struct { size_t assigned, conditional, autarky; } check;
check.assigned = check.conditional = check.autarky = 0;
for (size_t i = 0; i < trail.size (); i++) {
const int lit = trail[i];
if (val (lit)) {
check.assigned++;
if (is_conditional_literal (lit)) {
LOG ("remaining conditional %d", lit);
assert (!is_autarky_literal (lit));
check.conditional++;
} else {
assert (is_autarky_literal (lit));
LOG ("remaining autarky %d", lit);
check.autarky++;
}
} else {
assert (!is_autarky_literal (lit));
assert (!is_conditional_literal (lit));
}
}
assert (remain.assigned == check.assigned);
assert (remain.conditional == check.conditional);
assert (remain.autarky == check.autarky);
#endif
// Success if an autarky literal is left in the clause and
// we did not abort the loop too early because the propagation
// limit was hit.
//
if (watched_autarky_literal && stats.condprops < limit)
{
assert (is_autarky_literal (watched_autarky_literal));
assert (is_in_candidate_clause (watched_autarky_literal));
blocked++;
stats.conditioned++;
LOG (c,
"positive autarky literal %d globally blocks",
watched_autarky_literal);
LOG ("remaining %zd assigned literals %.0f%%",
remain.assigned, percent (remain.assigned, initial.assigned));
LOG ("remaining %zd conditional literals %.0f%%",
remain.conditional,
percent (remain.conditional, remain.assigned));
LOG ("remaining %zd autarky literals %.0f%%",
remain.autarky, percent (remain.autarky, remain.assigned));
// A satisfying assignment of a formula after removing a globally
// blocked clause might not satisfy that clause. As for variable
// elimination and classical blocked clauses, we thus maintain an
// extension stack for reconstructing an assignment which both
// satisfies the remaining formula as well as the clause.
//
// For globally blocked clauses we simply have to flip all literals in
// the autarky part and thus save the autarky on the extension stack
// in addition to the removed clause. In the classical situation (in
// bounded variable elimination etc.) we simply save one literal on
// the extension stack.
//
// TODO find a way to shrink the autarky part or some other way to
// avoid pushing too many literals on the extension stack.
//
external->push_zero_on_extension_stack ();
for (const auto & lit : trail)
if (is_autarky_literal (lit))
external->push_witness_literal_on_extension_stack (lit);
external->push_clause_on_extension_stack (c);
mark_garbage (c);
stats.condassrem += remain.assigned;
stats.condcondrem += remain.conditional;
stats.condautrem += remain.autarky;
stats.condassirem += initial.assigned;
}
// In this last part specific to one candidate clause, we have to get
// back to the initial assignment and reset conditionals. First we
// assign all the unassigned literals (if necessary).
//
if (!unassigned.empty ()) {
LOG ("reassigning %zd literals", unassigned.size ());
while (!unassigned.empty ()) {
const int lit = unassigned.back ();
unassigned.pop_back ();
condition_assign (lit);
}
}
// Then we remove from the conditional stack autarky literals which
// became conditional and also reset their 'conditional' bit.
//
if (initial.conditional < conditional.size ()) {
LOG ("flushing %zd autarky literals from conditional stack",
conditional.size () - initial.conditional);
while (initial.conditional < conditional.size ()) {
const int lit = conditional.back ();
conditional.pop_back ();
unmark_as_conditional_literal (lit);
}
}
// Finally unmark all literals in the candidate clause.
//
for (const_literal_iterator l = c->begin (); l != c->end (); l++)
unmark_in_candidate_clause (*l);
} // End of loop over all candidate clauses.
PHASE ("condition", stats.conditionings,
"globally blocked %ld clauses %.0f%%",
blocked, percent (blocked, candidates.size ()));
// Unmark initial conditional variables.
//
for (const auto & lit : conditional)
unmark_as_conditional_literal (lit);
erase_vector (unassigned);
erase_vector (conditional);
erase_vector (candidates);
// Unassign additionally assigned literals.
//
int additionally_unassigned = 0;
while (trail.size () > initial_trail_level) {
int lit = trail.back ();
trail.pop_back ();
condition_unassign (lit);
additionally_unassigned++;
}
LOG ("unassigned %d additionally assigned literals",
additionally_unassigned);
assert (additionally_unassigned == additionally_assigned);
if (level > initial_level) {
LOG ("reset condition decision level");
level = initial_level;
}
reset_occs ();
// Reassign previously assigned variables again.
//
LOG ("reassigning previously assigned variables");
for (size_t i = 0; i < initial_trail_level; i++) {
const int lit = trail[i];
const signed char tmp = val (lit);
assert (tmp >= 0);
if (!tmp) condition_assign (lit);
}
#ifndef NDEBUG
for (const auto & lit : trail)
assert (!marked (lit));
#endif
unprotect_reasons ();
return blocked;
}
void Internal::condition (bool update_limits) {
if (unsat) return;
if (!stats.current.irredundant) return;
START_SIMPLIFIER (condition, CONDITION);
stats.conditionings++;
// Propagation limit to avoid too much work in 'condition'. We mark
// tried candidate clauses after giving up, such that next time we run
// 'condition' we can try them.
//
long limit = stats.propagations.search;
limit *= opts.conditionreleff;
limit /= 1000;
if (limit < opts.conditionmineff) limit = opts.conditionmineff;
if (limit > opts.conditionmaxeff) limit = opts.conditionmaxeff;
assert (stats.current.irredundant);
limit *= 2.0 * active () / (double) stats.current.irredundant;
limit = max (limit, 2l* active ());
PHASE ("condition", stats.conditionings,
"started after %" PRIu64 " conflicts limited by %ld propagations",
stats.conflicts, limit);
long blocked = condition_round (limit);
STOP_SIMPLIFIER (condition, CONDITION);
report ('g', !blocked);
if (!update_limits) return;
long delta = opts.conditionint * (stats.conditionings + 1);
lim.condition = stats.conflicts + delta;
PHASE ("condition", stats.conditionings,
"next limit at %" PRIu64 " after %ld conflicts",
lim.condition, delta);
}
}

97
hCaD_V2/src/config.cpp Normal file
View File

@ -0,0 +1,97 @@
#include "internal.hpp"
namespace CaDiCaL {
/*------------------------------------------------------------------------*/
struct NameVal { const char * name; int val; };
/*------------------------------------------------------------------------*/
// These are dummy configurations, which require additional code.
static NameVal default_config[1]; // With '-pedantic' just '[]' or
static NameVal plain_config[1]; // '[0]' gave a warning.
/*------------------------------------------------------------------------*/
// Here we have the pre-defined default configurations.
static NameVal sat_config[] = {
{ "elimreleff", 10 },
{ "stabilizeonly", 1 },
{ "subsumereleff", 60 },
};
static NameVal unsat_config[] = {
{ "stabilize", 0 },
{ "walk", 0 },
};
/*------------------------------------------------------------------------*/
#define CONFIGS \
\
CONFIG(default,"set default advanced internal options") \
CONFIG(plain,"disable all internal preprocessing options") \
CONFIG(sat,"set internal options to target satisfiable instances") \
CONFIG(unsat,"set internal options to target unsatisfiable instances") \
static const char * configs [] = {
#define CONFIG(N,D) #N,
CONFIGS
#undef CONFIG
};
static size_t num_configs = sizeof configs / sizeof *configs;
/*------------------------------------------------------------------------*/
bool Config::has (const char * name) {
#define CONFIG(N,D) \
if (!strcmp (name, #N)) return true;
CONFIGS
#undef CONFIG
return false;
}
bool Config::set (Options & opts, const char * name) {
if (!strcmp (name, "default")) {
opts.reset_default_values ();
return true;
}
if (!strcmp (name, "plain")) {
opts.disable_preprocessing ();
return true;
}
#define CONFIG(N,D) \
do { \
if (strcmp (name, #N)) break; \
const NameVal * BEGIN = N ## _config; \
const NameVal * END = BEGIN + sizeof N ##_config / sizeof (NameVal); \
for (const NameVal * P = BEGIN; P != END; P++) { \
assert (Options::has (P->name)); \
opts.set (P->name, P->val); \
} \
return true; \
} while (0);
CONFIGS
#undef CONFIG
return false;
}
/*------------------------------------------------------------------------*/
void Config::usage () {
#define CONFIG(N,D) \
printf (" %-14s " D "\n", "--" #N);
CONFIGS
#undef CONFIG
}
/*------------------------------------------------------------------------*/
const char ** Config::begin () { return configs; }
const char ** Config::end () { return &configs[num_configs]; }
}

20
hCaD_V2/src/config.hpp Normal file
View File

@ -0,0 +1,20 @@
#ifndef _config_hpp_INCLUDED
#define _config_hpp_INCLUDED
namespace CaDiCaL {
class Options;
struct Config {
static bool has (const char *);
static bool set (Options &, const char *);
static void usage ();
static const char ** begin ();
static const char ** end ();
};
}
#endif

494
hCaD_V2/src/configure vendored Normal file
View File

@ -0,0 +1,494 @@
#!/bin/sh
#--------------------------------------------------------------------------#
# Run './configure' to produce a 'makefile' in the 'build' sub-directory or
# in any immediate sub-directory different from the 'src', 'scripts' and
# 'test' directories.
#--------------------------------------------------------------------------#
rm -f configure.log
#--------------------------------------------------------------------------#
# Common default options.
all=no
debug=no
logging=no
check=no
competition=no
coverage=no
profile=no
contracts=yes
tracing=yes
unlocked=yes
pedantic=no
options=""
quiet=no
m32=no
#--------------------------------------------------------------------------#
if [ -f ./scripts/colors.sh ]
then
. ./scripts/colors.sh
elif [ -f ../scripts/colors.sh ]
then
. ../scripts/colors.sh
else
BAD=""
HILITE=""
BOLD=""
NORMAL=""
fi
die () {
if [ -f configure.log ]
then
checklog=" (check also 'configure.log')"
else
checklog=""
fi
cecho "${BOLD}configure:${NORMAL} ${BAD}error:${NORMAL} $*${checklog}"
exit 1
}
msg () {
cecho "${BOLD}configure:${NORMAL} $*"
}
# if we can find the 'color.sh' script source it and overwrite color codes
for dir in . ..
do
[ -f $dir/scripts/colors.sh ] || continue
. $dir/scripts/colors.sh || exit 1
break
done
#--------------------------------------------------------------------------#
# Parse and handle command line options.
usage () {
cat << EOF
usage: configure [ <option> ... ]
where '<option>' is one of the following
-h|--help print this command line summary
-g|--debug compile with debugging information
-c|--check compile with assertion checking (default for '-g')
-l|--log[ging] include logging code (but disabled by default)
-a|--all short cut for all above, e.g., '-g -l' (thus also '-c')
-q|--quiet exclude message and profiling code (logging too)
-p|--pedantic add '--pedantic' and '-Werror' compilation flag
-s|--symbols add '-ggdb3' (even for optimized compilation)
--coverage compile with '-ftest-coverage -fprofile-arcs' for 'gcov'
--profile compile with '-pg' to profile with 'gprof'
--no-contracts compile without API contract checking code
--no-tracing compile without API call tracing code
--competition configure for the competition
('--quiet', '--no-contracts', '--no-tracing')
-f... pass '-f<option>[=<val>]' options to the makefile
-m32 pass '-m32' to the compiler (compile for 32 bit)
-ggdb3 pass '-ggdb3' to makefile (like '-s')
-O|-O[123] pass '-O' or '-O[123]' to the makefile
-static... pass '-static...' option to makefile
The environment variable CXX can be used to set a different C++
compiler than the default 'g++'. Similarly you can add additional
compilation options by setting CXXFLAGS. For example
CXX=clang++ CXXFLAGS=-fPIC ./configure
will enforce to use 'clang++' as C++ compiler and also produce
position independent code. In order to be shell independent we also
allow to have the following form. Thus for instance
./configure CXX="g++-8" CXXFLAGS="-fPIC -fsanitize=address"
will have the same effect as
CXX="g++-8" ./configure -fPIC -fsanitize=address
The following configuration options might be usefull during porting the
code to a new platform and are usually not necessary to change.
--no-unlocked force compilation without unlocked IO
EOF
exit 0
}
#--------------------------------------------------------------------------#
while [ $# -gt 0 ]
do
case $1 in
-h|--help) usage;;
-a|--all) all=yes;;
-g|--debug) debug=yes;;
-c|--check) check=yes;;
-l|--log|--logging) logging=yes;;
-p|--pedantic) pedantic=yes;;
-q|--quiet) quiet=yes;;
--no-contracts | --no-contract) contracts=no;;
--no-tracing | --no-trace) tracing=no;;
--coverage) coverage=yes;;
--profile) profile=yes;;
--competition) competition=yes;;
--no-unlocked) unlocked=no;;
-m32) options="$options $1";m32=yes;;
-f*|-ggdb3|-O|-O1|-O2|-O3) options="$options $1";;
-s|--symbols) options="$options -ggdb3";;
-static*) options="$options $1";;
CXX=*)
CXX="`expr \"$1\" : 'CXX=\(.*\)'`"
;;
CXXFLAGS=*)
CXXFLAGS="`expr \"$1\" : 'CXXFLAGS=\(.*\)'`"
;;
*) die "invalid option '$1' (try '-h')";;
esac
shift
done
#--------------------------------------------------------------------------#
if [ $quiet = yes ]
then
[ $logging = yes ] && die "can not combine '-q' with '-l'"
fi
if [ $all = yes ]
then
[ $check = yes ] && die "'-a' subsumes '-c'"
[ $debug = yes ] && die "'-a' subsumes '-g'"
[ $logging = yes ] && die "'-a' subsumes '-l'"
check=yes
debug=yes
logging=yes
elif [ $debug = yes ]
then
[ $check = yes ] && die "'-g' subsumes '-c'"
check=yes
fi
#--------------------------------------------------------------------------#
# Generate and enter 'build' directory if not already in sub-directory.
build_in_default_build_sub_directory () {
if [ -d build ]
then
msg "reusing default 'build' directory"
else
mkdir build 2>/dev/null || \
die "failed to generate 'build' directory"
msg "making default 'build' directory"
fi
cd build
msg "building in default ${HILITE}'`pwd`'${NORMAL}"
build=build
}
if [ -f configure -a -f makefile.in -a -d src ]
then
root="`pwd`"
build_in_default_build_sub_directory
elif [ -f ../configure -a -f ../makefile.in -a -d ../src ]
then
cwd="`pwd`"
build=`basename "$cwd"`
root=`dirname "$cwd"`
case x"$build" in
xsrc|xtest|xscripts)
cd ..
build_in_default_build_sub_directory
;;
*)
msg "building in ${HILITE}'$build'${NORMAL} sub-directory"
;;
esac
else
die "call 'configure' from root of CaDiCaL source or a sub-directory"
fi
msg "root directory '$root'"
#--------------------------------------------------------------------------#
src="$root/src"
if [ -d "$src" ]
then
msg "source directory '$src'"
else
die "could not find source director '$src'"
fi
#--------------------------------------------------------------------------#
# Prepare '@CXX@' and '@CXXFLAGS@' parameters for 'makefile.in'
[ x"$CXX" = x ] && CXX=g++
[ x"$CXXFLAGS" = x ] || CXXFLAGS="$CXXFLAGS "
case x"$CXX" in
x*g++*|x*clang++*) CXXFLAGS="${CXXFLAGS}-Wall -Wextra";;
*) CXXFLAGS="${CXXFLAGS}-W";;
esac
if [ $debug = yes ]
then
CXXFLAGS="$CXXFLAGS -g"
else
case x"$CXX" in
x*g++*|x*clang++*) CXXFLAGS="$CXXFLAGS -O3";;
*) CXXFLAGS="$CXXFLAGS -O";;
esac
fi
if [ $m32 = yes ]
then
case x"$CXX" in
x*g++*)
options="$options -mpc64"
msg "forcing portable 64-bit FPU mode ('-mpc64') for '$CXX'"
;;
esac
fi
if [ $competition = yes ]
then
quiet=yes
contracts=no
tracing=no
fi
[ $check = no ] && CXXFLAGS="$CXXFLAGS -DNDEBUG"
[ $logging = yes ] && CXXFLAGS="$CXXFLAGS -DLOGGING"
[ $quiet = yes ] && CXXFLAGS="$CXXFLAGS -DQUIET"
[ $profile = yes ] && CXXFLAGS="$CXXFLAGS -pg"
[ $coverage = yes ] && CXXFLAGS="$CXXFLAGS -ftest-coverage -fprofile-arcs"
if [ $pedantic = yes ]
then
CXXFLAGS="$CXXFLAGS --pedantic -Werror -std=c++11"
case x"$CXX" in
x*g++*|x*clang++*) CXXFLAGS="${CXXFLAGS} -Wp,-D_GLIBCXX_ASSERTIONS"
;;
esac
fi
[ $contracts = no ] && CXXFLAGS="$CXXFLAGS -DNCONTRACTS"
[ $tracing = no ] && CXXFLAGS="$CXXFLAGS -DNTRACING"
CXXFLAGS="$CXXFLAGS$options"
#--------------------------------------------------------------------------#
case x"$CXX" in
x*g++* | x*clang++*) WERROR="-Werror -pedantic";;
*) WERROR="";;
esac
# Check that compilation flags work.
feature=./configure-hello-world
cat <<EOF > $feature.cpp
#include <iostream>
int main () { std::cout << "hello world" << std::endl; }
EOF
if $CXX $CXXFLAGS $WERROR -o $feature.exe $feature.cpp 2>>configure.log
then
if [ ! "`$feature.exe 2>>configure.log|tr -d '\r'`" = "hello world" ]
then
die "execution of '$feature.exe' failed"
fi
else
die "test compilation '$feature.cpp'"
fi
#--------------------------------------------------------------------------#
# Since C99/C++11 is becoming the standard newer versions of 'g++' (7.3 for
# instance) discourage certain GCC extensions, particularly the GCC version
# of variadic macros, if the same concept exists in the standard. This
# forced us to replace all GCC style 'ARGS...' macros with '...' and
# '__VA_ARGS__'. Otherwise compiling the library with '--pedantic -Werror'
# would fail (configuration flag '-p'). However, older versions of 'gcc'
# (such as 4.8) would disallow these new forms of macros unless we
# explicitly enforce the new standard with '-std=c++11'. Here we try to
# figure out whether we need that flag. In earlier versions we used
# '-std=c++0x' but due to issues with older MacOS builds we switched to
# '-std=c++11' instead.
feature=./configure-requires-c++11
cat <<EOF > $feature.cpp
#include <cstdio>
#include <vector>
// old variadic macro usage 'ARGS...' / '#ARGS' discouraged in g++-7...
// new variadic macro usage '...' / '__VA_ARGS__' available in C99/C++11
//
#define MACRO(FMT, ...) printf (FMT "\n", __VA_ARGS__)
// we use ranged for loops which became available in gcc 4.6 and for
// the gcc 4.6 as well as 4.8 requires '-std=c++11' too.
//
unsigned f (const std::vector<unsigned> & a) {
unsigned res = 0;
for (auto i : a) res += i;
return res;
}
int main () { MACRO ("%d", 42); return 0; }
EOF
if $CXX $CXXFLAGS $WERROR -o $feature.exe $feature.cpp 2>>configure.log
then
if [ "`$feature.exe 2>>configure.log|tr -d '\r'`" = 42 ]
then
msg "compiler supports all required C99/C++11 extensions"
else
die "checking compilation without '-std=c++11' failed"
fi
else
CXXFLAGS="$CXXFLAGS -std=c++11"
if $CXX $CXXFLAGS -o $feature.exe $feature.cpp 2>>configure.log
then
if [ "`$feature.exe 2>>configure.log|tr -d '\r'`" = 42 ]
then
msg "using '-std=c++11' for all required C99/C++11 extensions"
else
die "checking compilation with '-std=c++11' failed"
fi
else
die "compiler does not support C99/C++11 even with '-std=c++11'"
fi
fi
#--------------------------------------------------------------------------#
# Unlocked IO is much faster but not necessarily supported.
if [ $unlocked = yes ]
then
feature=./configure-have-unlocked-io
cat <<EOF > $feature.cpp
#include <cstdio>
int main () {
const char * path = "$feature.log";
FILE * file = fopen (path, "w");
if (!file) return 1;
if (putc_unlocked (42, file) != 42) return 1;
if (fclose (file)) return 1;
file = fopen (path, "r");
if (!file) return 1;
if (getc_unlocked (file) != 42) return 1;
if (fclose (file)) return 1;
return 0;
}
EOF
if $CXX $CXXFLAGS -o $feature.exe $feature.cpp 2>>configure.log
then
if $feature.exe
then
msg "unlocked IO with '{putc,getc}_unlocked' seems to work"
else
msg "not using unlocked IO (running '$feature.exe' failed)"
unlocked=no
fi
else
msg "not using unlocked IO (failed to compile '$feature.cpp')"
unlocked=no
fi
else
msg "not using unlocked IO (since '--no-unlocked' specified)"
fi
[ $unlocked = no ] && CXXFLAGS="$CXXFLAGS -DNUNLOCKED"
#--------------------------------------------------------------------------#
# Instantiate '../makefile.in' template to produce 'makefile' in 'build'.
msg "compiling with ${HILITE}'$CXX $CXXFLAGS'${NORMAL}"
rm -f makefile
sed \
-e "2c\\
# This 'makefile' is generated from '../makefile.in'." \
-e "s,@CXX@,$CXX," \
-e "s#@CXXFLAGS@#$CXXFLAGS#" \
../makefile.in > makefile
msg "generated '$build/makefile' from '../makefile.in'"
#--------------------------------------------------------------------------#
build="`pwd`"
makefile="`dirname "$build"`/makefile"
cat <<EOF > "$makefile"
CADICALBUILD=$build
all:
\$(MAKE) -C "\$(CADICALBUILD)"
clean:
@if [ -d "\$(CADICALBUILD)" ]; \\
then \\
if [ -f "\$(CADICALBUILD)"/makefile ]; \\
then \\
touch "\$(CADICALBUILD)"/build.hpp; \\
\$(MAKE) -C "\$(CADICALBUILD)" clean; \\
fi; \\
rm -rf "\$(CADICALBUILD)"; \\
fi
rm -f "$src/makefile"
rm -f "$makefile"
test:
\$(MAKE) -C "\$(CADICALBUILD)" test
cadical:
\$(MAKE) -C "\$(CADICALBUILD)" cadical
mobical:
\$(MAKE) -C "\$(CADICALBUILD)" mobical
update:
\$(MAKE) -C "\$(CADICALBUILD)" update
.PHONY: all cadical clean mobical test update
EOF
msg "generated '../makefile' as proxy to ..."
msg "... '$build/makefile'"
#--------------------------------------------------------------------------#
if [ -f $src/makefile ]
then
msg "removing '$src/makefile'"
rm -f $src/makefile
fi
msg "linking '$root/makefile'"
ln -s $root/makefile $src/makefile
#--------------------------------------------------------------------------#
msg "now run ${HILITE}'make'${NORMAL} to compile CaDiCaL"
msg "optionally run 'make test'"

27
hCaD_V2/src/contract.cpp Normal file
View File

@ -0,0 +1,27 @@
#ifndef NCONTRACTS
#include "internal.hpp"
namespace CaDiCaL {
void fatal_message_start ();
// See comments in 'contract.hpp'. Ugly hack we keep for now.
void require_solver_pointer_to_be_non_zero (const void * ptr,
const char * function_name,
const char * file_name)
{
if (ptr) return;
fatal_message_start ();
fprintf (stderr,
"invalid API usage of '%s' in '%s': "
"solver 'this' pointer zero (not initialized)\n",
function_name, file_name);
fflush (stderr);
abort ();
}
}
#endif

112
hCaD_V2/src/contract.hpp Normal file
View File

@ -0,0 +1,112 @@
#ifndef _contract_hpp_INCLUDED
#define _contract_hpp_INCLUDED
/*------------------------------------------------------------------------*/
#ifndef NCONTRACTS
/*------------------------------------------------------------------------*/
// If the user violates API contracts while calling functions declared in
// 'cadical.hpp' and implemented in 'solver.cpp' then an error is reported.
// Currently we also force aborting the program. In the future it might be
// better to allow the user to provide a call back function, which then can
// for instance throw a C++ exception or execute a 'longjmp' in 'C' etc.
#define CONTRACT_VIOLATED(...) \
do { \
fatal_message_start (); \
fprintf (stderr, \
"invalid API usage of '%s' in '%s': ", \
__PRETTY_FUNCTION__, __FILE__); \
fprintf (stderr, __VA_ARGS__); \
fputc ('\n', stderr); \
fflush (stderr); \
abort (); \
} while (0)
/*------------------------------------------------------------------------*/
namespace CaDiCaL {
// It would be much easier to just write 'REQUIRE (this, "not initialized")'
// which however produces warnings due to the '-Wnonnull' check. Note, that
// 'this' is always assumed to be non zero in modern C++. Much worse, if we
// use instead 'this != 0' or something similar like 'this != nullptr' then
// optimization silently removes this check ('gcc-7.4.0' at least) even
// though of course a zero pointer might be used as 'this' if the user did
// not initialize it. The only solution I found is to disable optimization
// for this check. It does not seem to be necessary for 'clang++' though
// ('clang++-6.0.0' at least). The alternative is to not check that the
// user forgot to initialize the solver pointer, but as long this works we
// keep this ugly hack. It also forces the function not to be inlined.
// The actual code I is in 'contract.cpp'.
//
void require_solver_pointer_to_be_non_zero (const void *ptr,
const char * function_name,
const char * file_name);
#define REQUIRE_NON_ZERO_THIS() \
do { \
require_solver_pointer_to_be_non_zero (this, \
__PRETTY_FUNCTION__, __FILE__); \
} while (0)
}
/*------------------------------------------------------------------------*/
// These are common shortcuts for 'Solver' API contracts (requirements).
#define REQUIRE(COND,...) \
do { \
if ((COND)) break; \
CONTRACT_VIOLATED (__VA_ARGS__); \
} while (0)
#define REQUIRE_INITIALIZED() \
do { \
REQUIRE_NON_ZERO_THIS (); \
REQUIRE(external, "external solver not initialized"); \
REQUIRE(internal, "internal solver not initialized"); \
} while (0)
#define REQUIRE_VALID_STATE() \
do { \
REQUIRE_INITIALIZED (); \
REQUIRE(this->state () & VALID, "solver in invalid state"); \
} while (0)
#define REQUIRE_READY_STATE() \
do { \
REQUIRE_VALID_STATE (); \
REQUIRE (state () != ADDING, \
"clause incomplete (terminating zero not added)"); \
} while (0)
#define REQUIRE_VALID_OR_SOLVING_STATE() \
do { \
REQUIRE_INITIALIZED (); \
REQUIRE(this->state () & (VALID | SOLVING), \
"solver neither in valid nor solving state"); \
} while (0)
#define REQUIRE_VALID_LIT(LIT) \
do { \
REQUIRE ((int)(LIT) && ((int) (LIT)) != INT_MIN, \
"invalid literal '%d'", (int)(LIT)); \
} while (0)
/*------------------------------------------------------------------------*/
#else // NCONTRACTS
/*------------------------------------------------------------------------*/
#define REQUIRE(...) do { } while (0)
#define REQUIRE_INITIALIZED() do { } while (0)
#define REQUIRE_VALID_STATE() do { } while (0)
#define REQUIRE_READY_STATE() do { } while (0)
#define REQUIRE_VALID_OR_SOLVING_STATE() do { } while (0)
#define REQUIRE_VALID_LIT(...) do { } while (0)
/*------------------------------------------------------------------------*/
#endif
/*------------------------------------------------------------------------*/
#endif

588
hCaD_V2/src/cover.cpp Normal file
View File

@ -0,0 +1,588 @@
#include "internal.hpp"
namespace CaDiCaL {
/*------------------------------------------------------------------------*/
// Covered clause elimination (CCE) is described in our short LPAR-10 paper
// and later in more detail in our JAIR'15 article. Actually implement
// the asymmetric version which adds asymmetric literals too but still call
// it 'CCE' in the following (and not 'ACCE'). This implementation provides
// a simplified and cleaner version of the one implemented before in
// Lingeling. We still follow quite closely the original description in the
// literature, which is based on asymmetric literal addition (ALA) and
// covered literal addition (CLA). Both can be seen as kind of propagation,
// where the literals in the original and then extended clause are assigned
// to false, and the literals on the trail (actually we use our own 'added'
// stack for that) make up the extended clause. The ALA steps can be
// implemented by simple propagation (copied from 'propagate.cpp') using
// watches, while the CLA steps need full occurrence lists to determine the
// resolution candidate clauses. The CCE is successful if a conflict is
// found during ALA steps or if during a CLA step all resolution candidates
// of a literal on the trail are satisfied (the extended clause is blocked).
struct Coveror
{
std::vector<int> added; // acts as trail
std::vector<int> extend; // extension stack for witness
std::vector<int> covered; // clause literals or added through CLA
std::vector<int> intersection; // of literals in resolution candidates
size_t alas, clas; // actual number of ALAs and CLAs
struct { size_t added, covered; } next; // propagate next ...
Coveror () : alas (0), clas (0) { }
};
/*------------------------------------------------------------------------*/
// Push on the extension stack a clause made up of the given literal, the
// original clause (initially copied to 'covered') and all the added covered
// literals so far. The given literal will act as blocking literal for that
// clause, if CCE is successful. Only in this case, this private extension
// stack is copied to the actual extension stack of the solver. Note, that
// even though all 'added' clauses correspond to the extended clause, we
// only need to save the original and added covered literals.
inline void
Internal::cover_push_extension (int lit, Coveror & coveror) {
coveror.extend.push_back (0);
coveror.extend.push_back (lit); // blocking literal comes first
bool found = false;
for (const auto & other : coveror.covered)
if (lit == other) assert (!found), found = true;
else coveror.extend.push_back (other);
assert (found);
(void) found;
}
// Successful covered literal addition (CLA) step.
inline void
Internal::covered_literal_addition (int lit, Coveror & coveror)
{
require_mode (COVER);
assert (level == 1);
cover_push_extension (lit, coveror);
for (const auto & other : coveror.intersection) {
LOG ("covered literal addition %d", other);
assert (!vals[other]), assert (!vals[-other]);
vals[other] = -1, vals[-other] = 1;
coveror.covered.push_back (other);
coveror.added.push_back (other);
coveror.clas++;
}
coveror.next.covered = 0;
}
// Successful asymmetric literal addition (ALA) step.
inline void
Internal::asymmetric_literal_addition (int lit, Coveror & coveror)
{
require_mode (COVER);
assert (level == 1);
LOG ("initial asymmetric literal addition %d", lit);
assert (!vals[lit]), assert (!vals[-lit]);
vals[lit] = -1, vals[-lit] = 1;
coveror.added.push_back (lit);
coveror.alas++;
coveror.next.covered = 0;
}
/*------------------------------------------------------------------------*/
// In essence copied and adapted from 'propagate' in 'propagate.cpp'. Since
// this function is also a hot-spot here in 'cover' we specialize it in the
// same spirit as 'probe_propagate' and 'vivify_propagate'. Please refer to
// the detailed comments for 'propagate' in 'propagate.cpp' for details.
bool
Internal::cover_propagate_asymmetric (int lit,
Clause * ignore,
Coveror & coveror)
{
require_mode (COVER);
stats.propagations.cover++;
assert (val (lit) < 0);
bool subsumed = false;
LOG ("asymmetric literal propagation of %d", lit);
Watches & ws = watches (lit);
const const_watch_iterator eow = ws.end ();
watch_iterator j = ws.begin ();
const_watch_iterator i = j;
while (!subsumed && i != eow) {
const Watch w = *j++ = *i++;
if (w.clause == ignore) continue; // costly but necessary here ...
const signed char b = val (w.blit);
if (b > 0) continue;
if (w.clause->garbage) j--;
else if (w.binary ()) {
if (b < 0) {
LOG (w.clause, "found subsuming");
subsumed = true;
} else asymmetric_literal_addition (-w.blit, coveror);
} else {
literal_iterator lits = w.clause->begin ();
const int other = lits[0]^lits[1]^lit;
lits[0] = other, lits[1] = lit;
const signed char u = val (other);
if (u > 0) j[-1].blit = other;
else {
const int size = w.clause->size;
const const_literal_iterator end = lits + size;
const literal_iterator middle = lits + w.clause->pos;
literal_iterator k = middle;
signed char v = -1;
int r = 0;
while (k != end && (v = val (r = *k)) < 0)
k++;
if (v < 0) {
k = lits + 2;
assert (w.clause->pos <= size);
while (k != middle && (v = val (r = *k)) < 0)
k++;
}
w.clause->pos = k - lits;
assert (lits + 2 <= k), assert (k <= w.clause->end ());
if (v > 0) j[-1].blit = r;
else if (!v) {
LOG (w.clause, "unwatch %d in", lit);
lits[1] = r;
*k = lit;
watch_literal (r, lit, w.clause);
j--;
} else if (!u) {
assert (v < 0);
asymmetric_literal_addition (-other, coveror);
} else {
assert (u < 0), assert (v < 0);
LOG (w.clause, "found subsuming");
subsumed = true;
break;
}
}
}
}
if (j != i) {
while (i != eow) *j++ = *i++;
ws.resize (j - ws.begin ());
}
return subsumed;
}
// Covered literal addition (which needs full occurrence lists). The
// function returns 'true' if the extended clause is blocked on 'lit.'
bool
Internal::cover_propagate_covered (int lit, Coveror & coveror)
{
require_mode (COVER);
assert (val (lit) < 0);
if (frozen (lit)) {
LOG ("no covered propagation on frozen literal %d", lit);
return false;
}
stats.propagations.cover++;
LOG ("covered propagation of %d", lit);
assert (coveror.intersection.empty ());
Occs & os = occs (-lit);
const auto end = os.end ();
bool first = true;
// Compute the intersection of the literals in all the clauses with
// '-lit'. If all these clauses are double satisfied then we know that
// the extended clauses (in 'added') is blocked. All literals in the
// intersection can be added as covered literal. As soon the intersection
// becomes empty (during traversal of clauses with '-lit') we abort.
for (auto i = os.begin (); i != end; i++) {
Clause * c = *i;
if (c->garbage) continue;
// First check whether clause is 'blocked', i.e., is double satisfied.
bool blocked = false;
for (const auto & other : *c) {
if (other == -lit) continue;
const signed char tmp = val (other);
if (tmp < 0) continue;
if (tmp > 0) { blocked = true; break; }
}
if (blocked) { // ... if 'c' is double satisfied.
LOG (c, "blocked");
continue; // with next clause with '-lit'.
}
if (first) {
// Copy and mark literals of first clause.
for (const auto & other : *c) {
if (other == -lit) continue;
const signed char tmp = val (other);
if (tmp < 0) continue;
assert (!tmp);
coveror.intersection.push_back (other);
mark (other);
}
first = false;
} else {
// Unmark all literals in current clause.
for (const auto & other : *c) {
if (other == -lit) continue;
signed char tmp = val (other);
if (tmp < 0) continue;
assert (!tmp);
tmp = marked (other);
if (tmp > 0) unmark (other);
}
// Then remove from intersection all marked literals.
const auto end = coveror.intersection.end ();
auto j = coveror.intersection.begin ();
for (auto k = j; k != end; k++) {
const int other = *j++ = *k;
const int tmp = marked (other);
assert (tmp >= 0);
if (tmp) j--, unmark (other); // remove marked and unmark it
else mark (other); // keep unmarked and mark it
}
const size_t new_size = j - coveror.intersection.begin ();
coveror.intersection.resize (new_size);
if (!coveror.intersection.empty ()) continue;
// No covered literal addition candidates in the intersection left!
// Move this clause triggering early abort to the beginning.
// This is a common move to front strategy to minimize effort.
auto begin = os.begin ();
while (i != begin) {
auto prev = i - 1;
*i = *prev;
i = prev;
}
*begin = c;
break; // early abort ...
}
}
bool res = false;
if (first) {
LOG ("all resolution candidates with %d blocked", -lit);
assert (coveror.intersection.empty ());
cover_push_extension (lit, coveror);
res = true;
} else if (coveror.intersection.empty ()) {
LOG ("empty intersection of resolution candidate literals");
} else {
LOG (coveror.intersection,
"non-empty intersection of resolution candidate literals");
covered_literal_addition (lit, coveror);
unmark (coveror.intersection);
coveror.intersection.clear ();
coveror.next.covered = 0; // Restart covering.
}
unmark (coveror.intersection);
coveror.intersection.clear ();
return res;
}
/*------------------------------------------------------------------------*/
bool Internal::cover_clause (Clause * c, Coveror & coveror) {
require_mode (COVER);
assert (!c->garbage);
LOG (c, "trying covered clauses elimination on");
bool satisfied = false;
for (const auto & lit : *c)
if (val (lit) > 0)
satisfied = true;
if (satisfied) {
LOG (c, "clause already satisfied");
mark_garbage (c);
return false;
}
assert (coveror.added.empty ());
assert (coveror.extend.empty ());
assert (coveror.covered.empty ());
assert (!level);
level = 1;
LOG ("assuming literals of candidate clause");
for (const auto & lit : *c) {
if (val (lit)) continue;
asymmetric_literal_addition (lit, coveror);
coveror.covered.push_back (lit);
}
bool tautological = false;
coveror.next.added = coveror.next.covered = 0;
while (!tautological) {
if (coveror.next.added < coveror.added.size ()) {
const int lit = coveror.added[coveror.next.added++];
tautological = cover_propagate_asymmetric (lit, c, coveror);
} else if (coveror.next.covered < coveror.covered.size ()) {
const int lit = coveror.covered[coveror.next.covered++];
tautological = cover_propagate_covered (lit, coveror);
} else break;
}
if (tautological) {
if (coveror.extend.empty ()) {
stats.cover.asymmetric++;
stats.cover.total++;
LOG (c, "asymmetric tautological");
mark_garbage (c);
} else {
stats.cover.blocked++;
stats.cover.total++;
LOG (c, "covered tautological");
mark_garbage (c);
// Only copy extension stack if successful.
int prev = INT_MIN;
for (const auto & other : coveror.extend) {
if (!prev) {
external->push_zero_on_extension_stack ();
external->push_witness_literal_on_extension_stack (other);
external->push_zero_on_extension_stack ();
}
if (other)
external->push_clause_literal_on_extension_stack (other);
prev = other;
}
}
}
// Backtrack and 'unassign' all literals.
assert (level == 1);
for (const auto & lit : coveror.added)
vals[lit] = vals[-lit] = 0;
level = 0;
coveror.covered.clear ();
coveror.extend.clear ();
coveror.added.clear ();
return tautological;
}
/*------------------------------------------------------------------------*/
// Not yet tried and larger clauses are tried first.
struct clause_covered_or_smaller {
bool operator () (const Clause * a, const Clause * b) {
if (a->covered && !b->covered) return true;
if (!a->covered && b->covered) return false;
return a->size < b->size;
}
};
int64_t Internal::cover_round () {
if (unsat) return 0;
init_watches ();
connect_watches (true); // irredundant watches only is enough
int64_t delta = stats.propagations.search;
delta *= 1e-3 * opts.coverreleff;
if (delta < opts.covermineff) delta = opts.covermineff;
if (delta > opts.covermaxeff) delta = opts.covermaxeff;
delta = max (delta, ((int64_t) 2) * active ());
PHASE ("cover", stats.cover.count,
"covered clause elimination limit of %" PRId64 " propagations", delta);
int64_t limit = stats.propagations.cover + delta;
init_occs ();
vector<Clause *> schedule;
Coveror coveror;
// First connect all clauses and find all not yet tried clauses.
//
int64_t untried = 0;
//
for (auto c : clauses) {
assert (!c->frozen);
if (c->garbage) continue;
if (c->redundant) continue;
bool satisfied = false, allfrozen = true;
for (const auto & lit : *c)
if (val (lit) > 0) { satisfied = true; break; }
else if (allfrozen && !frozen (lit)) allfrozen = false;
if (satisfied) { mark_garbage (c); continue; }
if (allfrozen) { c->frozen = true; continue; }
for (const auto & lit : *c)
occs (lit).push_back (c);
if (c->size < opts.coverminclslim) continue;
if (c->size > opts.covermaxclslim) continue;
if (c->covered) continue;
schedule.push_back (c);
untried++;
}
if (schedule.empty ()) {
PHASE ("cover", stats.cover.count,
"no previously untried clause left");
for (auto c : clauses) {
if (c->garbage) continue;
if (c->redundant) continue;
if (c->frozen) { c->frozen = false; continue; }
if (c->size < opts.coverminclslim) continue;
if (c->size > opts.covermaxclslim) continue;
assert (c->covered);
c->covered = false;
schedule.push_back (c);
}
} else { // Mix of tried and not tried clauses ....
for (auto c : clauses) {
if (c->garbage) continue;
if (c->redundant) continue;
if (c->frozen) { c->frozen = false; continue; }
if (c->size < opts.coverminclslim) continue;
if (c->size > opts.covermaxclslim) continue;
if (!c->covered) continue;
schedule.push_back (c);
}
}
stable_sort (schedule.begin (), schedule.end (),
clause_covered_or_smaller ());
#ifndef QUIET
const size_t scheduled = schedule.size ();
PHASE ("cover", stats.cover.count,
"scheduled %zd clauses %.0f%% with %" PRId64 " untried %.0f%%",
scheduled, percent (scheduled, stats.current.irredundant),
untried, percent (untried, scheduled));
#endif
// Heuristically it should be beneficial to intersect with smaller clauses
// first, since then the chances are higher that the intersection of
// resolution candidates becomes emptier earlier.
for (auto lit : lits) {
if (!active (lit)) continue;
Occs & os = occs (lit);
stable_sort (os.begin (), os.end (), clause_smaller_size ());
}
// This is the main loop of trying to do CCE of candidate clauses.
//
int64_t covered = 0;
//
while (!terminated_asynchronously () &&
!schedule.empty () &&
stats.propagations.cover < limit) {
Clause * c = schedule.back ();
schedule.pop_back ();
c->covered = true;
if (cover_clause (c, coveror)) covered++;
}
#ifndef QUIET
const size_t remain = schedule.size ();
const size_t tried = scheduled - remain;
PHASE ("cover", stats.cover.count,
"eliminated %" PRId64 " covered clauses out of %zd tried %.0f%%",
covered, tried, percent (covered, tried));
if (remain)
PHASE ("cover", stats.cover.count,
"remaining %zu clauses %.0f%% untried",
remain, percent (remain, scheduled));
else
PHASE ("cover", stats.cover.count,
"all scheduled clauses tried");
#endif
reset_occs ();
reset_watches ();
return covered;
}
/*------------------------------------------------------------------------*/
bool Internal::cover () {
if (!opts.cover) return false;
if (unsat) return false;
if (terminated_asynchronously ()) return false;
if (!stats.current.irredundant) return false;
// TODO: Our current algorithm for producing the necessary clauses on the
// reconstruction stack for extending the witness requires a covered
// literal addition step which (empirically) conflicts with flushing
// during restoring clauses (see 'regr00{48,51}.trace') even though
// flushing during restore is disabled by default (as is covered clause
// elimination). The consequence of combining these two options
// ('opts.cover' and 'opts.restoreflush') can thus produce incorrect
// witness reconstruction and thus invalid witnesses. This is quite
// infrequent (one out of half billion mobical test cases) but as the two
// regression traces show, does happen. Thus we disable the combination.
//
if (opts.restoreflush) return false;
START_SIMPLIFIER (cover, COVER);
stats.cover.count++;
// During variable elimination unit clauses can be generated which need to
// be propagated properly over redundant clauses too. Since variable
// elimination avoids to have occurrence lists and watches at the same
// time this propagation is delayed until the end of variable elimination.
// Since we want to interleave CCE with it, we have to propagate here.
// Otherwise this triggers inconsistencies.
//
if (propagated < trail.size ()) {
init_watches ();
connect_watches (); // need to propagated over all clauses!
LOG ("elimination produced %zd units",
(size_t)(trail.size () - propagated));
if (!propagate ()) {
LOG ("propagating units before covered clause elimination "
"results in empty clause");
learn_empty_clause ();
assert (unsat);
}
reset_watches ();
}
assert (unsat || propagated == trail.size ());
int64_t covered = cover_round ();
STOP_SIMPLIFIER (cover, COVER);
report ('c', !opts.reportall && !covered);
return covered;
}
}

33
hCaD_V2/src/cover.hpp Normal file
View File

@ -0,0 +1,33 @@
#ifndef _cover_hpp_INCLUDED
#define _cover_hpp_INCLUDED
/*------------------------------------------------------------------------*/
// This header only provides the 'COVER' macro for testing. It is unrelated
// to 'cover.cpp' which implements covered clause elimination (CCE), but we
// wanted to use the name base name in both cases. More explanation on CCE
// is provided in 'cover.cpp'.
/*------------------------------------------------------------------------*/
// Coverage goal, used similar to 'assert' (but with flipped condition) and
// also included even if 'NDEBUG' is defined (in optimizing compilation).
//
// This should in essence not be used in production code.
//
// There seems to be no problem overloading the name 'COVER' of this macro
// with the constant 'COVER' of 'Internal::Mode' (surprisingly).
#define COVER(COND) \
do { \
if (!(COND)) break; \
fprintf (stderr, \
"%scadical%s: %s:%d: %s: Coverage goal %s`%s'%s reached.\n", \
terr.bold_code (), terr.normal_code (), \
__FUNCTION__, __LINE__, __FILE__, \
terr.green_code (), # COND, terr.normal_code ()); \
fflush (stderr); \
abort (); \
} while (0)
#endif

123
hCaD_V2/src/decide.cpp Normal file
View File

@ -0,0 +1,123 @@
#include "internal.hpp"
namespace CaDiCaL {
// This function determines the next decision variable on the queue, without
// actually removing it from the decision queue, e.g., calling it multiple
// times without any assignment will return the same result. This is of
// course used below in 'decide' but also in 'reuse_trail' to determine the
// largest decision level to backtrack to during 'restart' without changing
// the assigned variables (if 'opts.restartreusetrail' is non-zero).
int Internal::next_decision_variable_on_queue () {
int64_t searched = 0;
int res = queue.unassigned;
while (val (res))
res = link (res).prev, searched++;
if (searched) {
stats.searched += searched;
update_queue_unassigned (res);
}
LOG ("next queue decision variable %d bumped %" PRId64 "", res, bumped (res));
return res;
}
// This function determines the best decision with respect to score.
//
int Internal::next_decision_variable_with_best_score () {
int res = 0;
for (;;) {
res = scores.front ();
if (!val (res)) break;
(void) scores.pop_front ();
}
LOG ("next decision variable %d with score %g", res, score (res));
return res;
}
int Internal::next_decision_variable () {
if (use_scores ()) return next_decision_variable_with_best_score ();
else return next_decision_variable_on_queue ();
}
/*------------------------------------------------------------------------*/
// Implements phase saving as well using a target phase during
// stabilization unless decision phase is forced to the initial value
// of a phase is forced through the 'phase' option.
int Internal::decide_phase (int idx, bool target) {
const int initial_phase = opts.phase ? 1 : -1;
int phase = 0;
if (force_saved_phase) phase = phases.saved[idx];
if (!phase && opts.forcephase) phase = initial_phase;
if (!phase) phase = phases.forced[idx]; // TODO swap?
if (!phase && target) phase = phases.target[idx];
if (!phase) phase = phases.saved[idx];
if(opts.psids && phases.act[vlit(idx)] != phases.act[vlit(-idx)])
phase = phases.act[vlit(idx)] > phases.act[vlit(-idx)] ? 1: -1;
// The following should no be necessary and in some version we had even
// a hard 'COVER' assertion here to check for this. Unfortunately it
// triggered for some users and we could not get to the root cause of
// 'phase' still not being set here. The logic for phase and target
// saving is pretty complex, particularly in combination with local
// search, and to avoid running in such an issue in the future again, we
// now use this 'defensive' code here, even though such defensive code is
// considered bad programming practice.
//
if (!phase) phase = initial_phase;
return phase * idx;
}
// The likely phase of an variable used in 'collect' for optimizing
// co-location of clauses likely accessed together during search.
int Internal::likely_phase (int idx) { return decide_phase (idx, false); }
/*------------------------------------------------------------------------*/
bool Internal::satisfied () {
size_t assigned = trail.size ();
if (propagated < assigned) return false;
if ((size_t) level < assumptions.size ()) return false;
return (assigned == (size_t) max_var);
}
// Search for the next decision and assign it to the saved phase. Requires
// that not all variables are assigned.
int Internal::decide () {
assert (!satisfied ());
START (decide);
int res = 0;
if ((size_t) level < assumptions.size ()) {
const int lit = assumptions[level];
assert (assumed (lit));
const signed char tmp = val (lit);
if (tmp < 0) {
LOG ("assumption %d falsified", lit);
failing ();
res = 20;
} else if (tmp > 0) {
LOG ("assumption %d already satisfied", lit);
level++;
control.push_back (Level (0, trail.size ()));
LOG ("added pseudo decision level");
} else {
LOG ("deciding assumption %d", lit);
search_assume_decision (lit);
}
} else {
stats.decisions++;
int idx = next_decision_variable ();
const bool target = (opts.target > 1 || (stable && opts.target));
int decision = decide_phase (idx, target);
search_assume_decision (decision);
}
STOP (decide);
return res;
}
}

361
hCaD_V2/src/decompose.cpp Normal file
View File

@ -0,0 +1,361 @@
#include "internal.hpp"
namespace CaDiCaL {
// This implements Tarjan's algorithm for decomposing the binary implication
// graph intro strongly connected components (SCCs). Literals in one SCC
// are equivalent and we replace them all by the literal with the smallest
// index in the SCC. These variables are marked 'substituted' and will be
// removed from all clauses. Their value will be fixed during 'extend'.
#define TRAVERSED UINT_MAX // mark completely traversed
struct DFS {
unsigned idx; // depth first search index
unsigned min; // minimum reachable index
DFS () : idx (0), min (0) { }
};
// This performs one round of Tarjan's algorithm, e.g., equivalent literal
// detection and substitution, on the whole formula. We might want to
// repeat it since its application might produce new binary clauses or
// units. Such units might even result in an empty clause.
bool Internal::decompose_round () {
if (!opts.decompose) return false;
if (unsat) return false;
if (terminated_asynchronously ()) return false;
assert (!level);
START_SIMPLIFIER (decompose, DECOMP);
stats.decompositions++;
const size_t size_dfs = 2*(1 + (size_t) max_var);
DFS * dfs = new DFS[size_dfs];
int * reprs = new int[size_dfs];
clear_n (reprs, size_dfs);
int non_trivial_sccs = 0, substituted = 0;
#ifndef QUIET
int before = active ();
#endif
unsigned dfs_idx = 0;
vector<int> work; // depth first search working stack
vector<int> scc; // collects members of one SCC
// The binary implication graph might have disconnected components and
// thus we have in general to start several depth first searches.
for (auto root_idx : vars) {
if (unsat) break;
if (!active (root_idx)) continue;
for (int root_sign = -1; !unsat && root_sign <= 1; root_sign += 2) {
int root = root_sign * root_idx;
if (dfs[vlit (root)].min == TRAVERSED) continue; // skip traversed
LOG ("new dfs search starting at root %d", root);
assert (work.empty ());
assert (scc.empty ());
work.push_back (root);
while (!unsat && !work.empty ()) {
int parent = work.back ();
DFS & parent_dfs = dfs[vlit (parent)];
if (parent_dfs.min == TRAVERSED) { // skip traversed
assert (reprs [vlit (parent)]);
work.pop_back ();
} else {
assert (!reprs [vlit (parent)]);
// Go over all implied literals, thus need to iterate over all
// binary watched clauses with the negation of 'parent'.
Watches & ws = watches (-parent);
// Two cases: Either the node has never been visited before, i.e.,
// it's depth first search index is zero, then perform the
// 'pre-fix' work before visiting it's children. Otherwise all
// it's children and nodes reachable from those children have been
// visited and their minimum reachable depth first search index
// has been computed. This second case is the 'post-fix' work.
if (parent_dfs.idx) { // post-fix
work.pop_back (); // 'parent' done
// Get the minimum reachable depth first search index reachable
// from the children of 'parent'.
unsigned new_min = parent_dfs.min;
for (const auto & w : ws) {
if (!w.binary ()) continue;
const int child = w.blit;
if (!active (child)) continue;
const DFS & child_dfs = dfs[vlit (child)];
if (new_min > child_dfs.min) new_min = child_dfs.min;
}
LOG ("post-fix work dfs search %d index %u reaches minimum %u",
parent, parent_dfs.idx, new_min);
if (parent_dfs.idx == new_min) { // entry to SCC
// All nodes on the 'scc' stack after and including 'parent'
// are in the same SCC. Their representative is computed as
// the smallest literal (index-wise) in the SCC. If the SCC
// contains both a literal and its negation, then the formula
// becomes unsatisfiable.
int other, size = 0, repr = parent;
assert (!scc.empty ());
size_t j = scc.size ();
do {
assert (j > 0);
other = scc[--j];
if (other == -parent) {
LOG ("both %d and %d in one SCC", parent, -parent);
assign_unit (parent);
learn_empty_clause ();
} else {
if (abs (other) < abs (repr)) repr = other;
size++;
}
} while (!unsat && other != parent);
if (!unsat) {
LOG ("SCC of representative %d of size %d", repr, size);
do {
assert (!scc.empty ());
other = scc.back ();
scc.pop_back ();
dfs[vlit (other)].min = TRAVERSED;
if (frozen (other)) {
reprs[vlit (other) ] = other;
} else {
reprs[vlit (other)] = repr;
if (other != repr) {
substituted++;
LOG ("literal %d in SCC of %d", other, repr);
}
}
} while (other != parent);
if (size > 1) non_trivial_sccs++;
}
} else {
// Current node 'parent' is in a non-trivial SCC but is not
// the entry point of the SCC in this depth first search, so
// keep it on the SCC stack until the entry point is reached.
parent_dfs.min = new_min;
}
} else { // pre-fix
dfs_idx++;
assert (dfs_idx < TRAVERSED);
parent_dfs.idx = parent_dfs.min = dfs_idx;
scc.push_back (parent);
LOG ("pre-fix work dfs search %d index %u", parent, dfs_idx);
// Now traverse all the children in the binary implication
// graph but keep 'parent' on the stack for 'post-fix' work.
for (const auto & w : ws) {
if (!w.binary ()) continue;
const int child = w.blit;
if (!active (child)) continue;
const DFS & child_dfs = dfs[vlit (child)];
if (child_dfs.idx) continue;
work.push_back (child);
}
}
}
}
}
}
erase_vector (work);
erase_vector (scc);
delete [] dfs;
// Only keep the representatives 'repr' mapping.
PHASE ("decompose",
stats.decompositions,
"%d non-trivial sccs, %d substituted %.2f%%",
non_trivial_sccs, substituted, percent (substituted, before));
bool new_unit = false, new_binary_clause = false;
vector<Clause*> postponed_garbage;
// Now go over all clauses and find clause which contain literals that
// should be substituted by their representative.
size_t clauses_size = clauses.size (), garbage = 0, replaced = 0;
for (size_t i = 0; substituted && !unsat && i < clauses_size; i++) {
Clause * c = clauses[i];
if (c->garbage) continue;
int j, size = c->size;
for (j = 0; j < size; j++) {
const int lit = c->literals[j];
if (reprs [ vlit (lit) ] != lit) break;
}
if (j == size) continue;
replaced++;
LOG (c, "first substituted literal %d in", substituted);
// Now copy the result to 'clause'. Substitute literals if they have a
// different representative. Skip duplicates and false literals. If a
// literal occurs in both phases or is assigned to true the clause is
// satisfied and can be marked as garbage.
assert (clause.empty ());
bool satisfied = false;
for (int k = 0; !satisfied && k < size; k++) {
const int lit = c->literals[k];
signed char tmp = val (lit);
if (tmp > 0) satisfied = true;
else if (tmp < 0) continue;
else {
const int other = reprs [vlit (lit)];
tmp = val (other);
if (tmp < 0) continue;
else if (tmp > 0) satisfied = true;
else {
tmp = marked (other);
if (tmp < 0) satisfied = true;
else if (!tmp) {
mark (other);
clause.push_back (other);
}
}
}
}
if (satisfied) {
LOG (c, "satisfied after substitution (postponed)");
postponed_garbage.push_back (c);
garbage++;
} else if (!clause.size ()) {
LOG ("learned empty clause during decompose");
learn_empty_clause ();
} else if (clause.size () == 1) {
LOG (c, "unit %d after substitution", clause[0]);
assign_unit (clause[0]);
mark_garbage (c);
new_unit = true;
garbage++;
} else if (c->literals[0] != clause[0] ||
c->literals[1] != clause[1]) {
LOG ("need new clause since at least one watched literal changed");
if (clause.size () == 2) new_binary_clause = true;
size_t d_clause_idx = clauses.size ();
Clause * d = new_clause_as (c);
assert (clauses[d_clause_idx] == d);
clauses[d_clause_idx] = c;
clauses[i] = d;
mark_garbage (c);
garbage++;
} else {
LOG ("simply shrinking clause since watches did not change");
assert (c->size > 2);
if (!c->redundant) mark_removed (c);
if (proof) {
proof->add_derived_clause (clause);
proof->delete_clause (c);
}
size_t l;
for (l = 2; l < clause.size (); l++)
c->literals[l] = clause[l];
int flushed = c->size - (int) l;
if (flushed) {
if (l == 2) new_binary_clause = true;
LOG ("flushed %d literals", flushed);
(void) shrink_clause (c, l);
} else if (likely_to_be_kept_clause (c)) mark_added (c);
LOG (c, "substituted");
}
while (!clause.empty ()) {
int lit = clause.back ();
clause.pop_back ();
assert (marked (lit) > 0);
unmark (lit);
}
}
if (!unsat && !postponed_garbage.empty ()) {
LOG ("now marking %zd postponed garbage clauses",
postponed_garbage.size ());
for (const auto & c : postponed_garbage)
mark_garbage (c);
}
erase_vector (postponed_garbage);
PHASE ("decompose",
stats.decompositions,
"%zd clauses replaced %.2f%% producing %zd garbage clauses %.2f%%",
replaced, percent (replaced, clauses_size),
garbage, percent (garbage, replaced));
erase_vector (scc);
// Propagate found units.
if (!unsat && propagated < trail.size () && !propagate ()) {
LOG ("empty clause after propagating units from substitution");
learn_empty_clause ();
}
// Finally, mark substituted literals as such and push the equivalences of
// the substituted literals to their representative on the extension
// stack to fix an assignment during 'extend'.
//
// TODO instead of adding the clauses to the extension stack one could
// also just simply use the 'e2i' map as a union find data structure.
// This would avoid the need to restore these clauses.
for (auto idx : vars) {
if (unsat) break;
if (!active (idx)) continue;
int other = reprs [ vlit (idx) ];
if (other == idx) continue;
assert (!flags (other).eliminated ());
assert (!flags (other).substituted ());
if (!flags (other).fixed ()) mark_substituted (idx);
external->push_binary_clause_on_extension_stack (-idx, other);
external->push_binary_clause_on_extension_stack (idx, -other);
}
delete [] reprs;
flush_all_occs_and_watches (); // particularly the 'blit's
bool success = unsat ||
(substituted > 0 && (new_unit || new_binary_clause));
report ('d', !opts.reportall && !success);
STOP_SIMPLIFIER (decompose, DECOMP);
return success;
}
void Internal::decompose () {
for (int round = 1; round <= opts.decomposerounds; round++)
if (!decompose_round ())
break;
}
}

139
hCaD_V2/src/deduplicate.cpp Normal file
View File

@ -0,0 +1,139 @@
#include "internal.hpp"
namespace CaDiCaL {
// Equivalent literal substitution in 'decompose' and shrinking in 'subsume'
// or 'vivify' might produce duplicated binary clauses. They can not be
// found in 'subsume' nor 'vivify' since we explicitly do not consider
// binary clauses as candidates to be shrunken or subsumed. They are
// detected here by a simple scan of watch lists and then marked as garbage.
// This is actually also quite fast.
// Further it might also be possible that two binary clauses can be resolved
// to produce a unit (we call it 'hyper unary resolution'). For example
// resolving the binary clauses '1 -2' and '1 2' produces the unit '1'.
// This could be found by probing in 'probe' unless '-1' also occurs in a
// binary clause (add the clause '-1 2' to those two clauses) in which case
// '1' as well as '2' both occur positively as well as negatively and none
// of them nor their negation is considered as probe
void Internal::mark_duplicated_binary_clauses_as_garbage () {
if (!opts.deduplicate) return;
if (unsat) return;
if (terminated_asynchronously ()) return;
START_SIMPLIFIER (deduplicate, DEDUP);
stats.deduplications++;
assert (!level);
assert (watching ());
vector<int> stack; // To save marked literals and unmark them later.
int64_t subsumed = 0;
int64_t units = 0;
for (auto idx : vars) {
if (unsat) break;
if (!active (idx)) continue;
int unit = 0;
for (int sign = -1; !unit && sign <= 1; sign += 2) {
const int lit = sign * idx; // Consider all literals.
assert (stack.empty ());
Watches & ws = watches (lit);
// We are removing references to garbage clause. Thus no 'auto'.
const const_watch_iterator end = ws.end ();
watch_iterator j = ws.begin ();
const_watch_iterator i;
for (i = j; !unit && i != end; i++) {
Watch w = *j++ = *i;
if (!w.binary ()) continue;
int other = w.blit;
const int tmp = marked (other);
Clause * c = w.clause;
if (tmp > 0) { // Found duplicated binary clause.
if (c->garbage) { j--; continue; }
LOG (c, "found duplicated");
// The previous identical clause 'd' might be redundant and if the
// second clause 'c' is not (so irredundant), then we have to keep
// 'c' instead of 'd', thus we search for it and replace it.
if (!c->redundant) {
watch_iterator k;
for (k = ws.begin ();;k++) {
assert (k != i);
if (!k->binary ()) continue;
if (k->blit != other) continue;
Clause * d = k->clause;
if (d->garbage) continue;
c = d;
break;
}
*k = w;
}
LOG (c, "mark garbage duplicated");
stats.subsumed++;
stats.deduplicated++;
subsumed++;
mark_garbage (c);
j--;
} else if (tmp < 0) { // Hyper unary resolution.
LOG ("found %d %d and %d %d which produces unit %d",
lit, -other, lit, other, lit);
unit = lit;
j = ws.begin (); // Flush 'ws'.
units++;
} else {
if (c->garbage) continue;
mark (other);
stack.push_back (other);
}
}
if (j == ws.begin ()) erase_vector (ws);
else if (j != end)
ws.resize (j - ws.begin ()); // Shrink watchers.
for (const auto & other : stack)
unmark (other);
stack.clear ();
}
// Propagation potentially messes up the watches and thus we can not
// propagate the unit immediately after finding it. Instead we break
// out of both loops and assign and propagate the unit here.
if (unit) {
stats.failed++;
stats.hyperunary++;
assign_unit (unit);
if (!propagate ()) {
LOG ("empty clause after propagating unit");
learn_empty_clause ();
}
}
}
STOP_SIMPLIFIER (deduplicate, DEDUP);
report ('2', !opts.reportall && !(subsumed + units));
}
}

960
hCaD_V2/src/elim.cpp Normal file
View File

@ -0,0 +1,960 @@
#include "internal.hpp"
namespace CaDiCaL {
/*------------------------------------------------------------------------*/
// Implements a variant of bounded variable elimination as originally
// described in our SAT'05 paper introducing 'SATeLite'. This is an
// inprocessing version, i.e., it is interleaved with search and triggers
// subsumption and strengthening, blocked and covered clause elimination
// during elimination rounds. It focuses only those variables which
// occurred in removed irredundant clauses since the last time an
// elimination round was run. By bounding the maximum resolvent size we can
// run each elimination round until completion. See the code of 'elim' for
// how elimination rounds are interleaved with blocked clause elimination
// and subsumption (which in turn also calls vivification and transitive
// reduction of the binary implication graph).
/*------------------------------------------------------------------------*/
inline double Internal::compute_elim_score (unsigned lit) {
assert (1 <= lit), assert (lit <= (unsigned) max_var);
const unsigned uidx = 2*lit;
const double pos = internal->ntab [uidx];
const double neg = internal->ntab [uidx + 1];
if (!pos) return -neg;
if (!neg) return -pos;
double sum = 0, prod = 0;
if (opts.elimsum) sum = opts.elimsum * (pos + neg);
if (opts.elimprod) prod = opts.elimprod * (pos * neg);
return prod + sum;
}
/*------------------------------------------------------------------------*/
inline bool elim_more::operator () (unsigned a, unsigned b) {
const auto s = internal->compute_elim_score (a);
const auto t = internal->compute_elim_score (b);
if (s > t) return true;
if (s < t) return false;
return a > b;
}
/*------------------------------------------------------------------------*/
// Note that the new fast subsumption algorithm implemented in 'subsume'
// does not distinguish between irredundant and redundant clauses and is
// also run during search to strengthen and remove 'sticky' redundant
// clauses but also irredundant ones. So beside learned units during search
// or as consequence of other preprocessors, these subsumption rounds during
// search can remove (irredundant) clauses (and literals), which in turn
// might make new bounded variable elimination possible. This is tested
// in the 'bool eliminating ()' guard.
bool Internal::eliminating () {
if (!opts.elim) return false;
if (!preprocessing && !opts.inprocessing) return false;
if (preprocessing) assert (lim.preprocessing);
// Respect (increasing) conflict limit.
//
if (lim.elim >= stats.conflicts) return false;
// Wait until there are new units or new removed variables
// (in removed or shrunken irredundant clauses and thus marked).
//
if (last.elim.fixed < stats.all.fixed) return true;
if (last.elim.marked < stats.mark.elim) return true;
return false;
}
/*------------------------------------------------------------------------*/
// Update the global elimination schedule after adding or removing a clause.
void
Internal::elim_update_added_clause (Eliminator & eliminator, Clause * c) {
assert (!c->redundant);
ElimSchedule & schedule = eliminator.schedule;
for (const auto & lit : *c) {
if (!active (lit)) continue;
occs (lit).push_back (c);
if (frozen (lit)) continue;
noccs (lit)++;
const int idx = abs (lit);
if (schedule.contains (idx)) schedule.update (idx);
}
}
void Internal::elim_update_removed_lit (Eliminator & eliminator, int lit) {
if (!active (lit)) return;
if (frozen (lit)) return;
int64_t & score = noccs (lit);
assert (score > 0);
score--;
const int idx = abs (lit);
ElimSchedule & schedule = eliminator.schedule;
if (schedule.contains (idx)) schedule.update (idx);
else {
LOG ("rescheduling %d for elimination after removing clause", idx);
schedule.push_back (idx);
}
}
void
Internal::elim_update_removed_clause (Eliminator & eliminator,
Clause * c, int except)
{
assert (!c->redundant);
for (const auto & lit : *c) {
if (lit == except) continue;
assert (lit != -except);
elim_update_removed_lit (eliminator, lit);
}
}
/*------------------------------------------------------------------------*/
// Since we do not have watches we have to do our own unit propagation
// during elimination as soon we find a unit clause. This finds new units
// and also marks clauses satisfied by those units as garbage immediately.
void Internal::elim_propagate (Eliminator & eliminator, int root) {
assert (val (root) > 0);
vector<int> work;
size_t i = 0;
work.push_back (root);
while (i < work.size ()) {
int lit = work[i++];
LOG ("elimination propagation of %d", lit);
assert (val (lit) > 0);
const Occs & ns = occs (-lit);
for (const auto & c : ns) {
if (c->garbage) continue;
int unit = 0, satisfied = 0;
for (const auto & other : *c) {
const signed char tmp = val (other);
if (tmp < 0) continue;
if (tmp > 0) { satisfied = other; break; }
if (unit) unit = INT_MIN;
else unit = other;
}
if (satisfied) {
LOG (c, "elimination propagation of %d finds %d satisfied",
lit, satisfied);
elim_update_removed_clause (eliminator, c, satisfied);
mark_garbage (c);
} else if (!unit) {
LOG ("empty clause during elimination propagation of %d", lit);
learn_empty_clause ();
break;
} else if (unit != INT_MIN) {
LOG ("new unit %d during elimination propagation of %d", unit, lit);
assign_unit (unit);
work.push_back (unit);
}
}
if (unsat) break;
const Occs & ps = occs (lit);
for (const auto & c : ps) {
if (c->garbage) continue;
LOG (c, "elimination propagation of %d produces satisfied", lit);
elim_update_removed_clause (eliminator, c, lit);
mark_garbage (c);
}
}
}
/*------------------------------------------------------------------------*/
// On-the-fly self-subsuming resolution during variable elimination is due
// to HyoJung Han, Fabio Somenzi, SAT'09. Basically while resolving two
// clauses we test the resolvent to be smaller than one of the antecedents.
// If this is the case the pivot can be removed from the antecedent
// on-the-fly and the resolution can be skipped during elimination.
void Internal::elim_on_the_fly_self_subsumption (Eliminator & eliminator,
Clause * c, int pivot)
{
LOG (c, "pivot %d on-the-fly self-subsuming resolution", pivot);
stats.elimotfstr++;
stats.strengthened++;
assert (clause.empty ());
for (const auto & lit : *c) {
if (lit == pivot) continue;
const signed char tmp = val (lit);
assert (tmp <= 0);
if (tmp < 0) continue;
clause.push_back (lit);
}
Clause * r = new_resolved_irredundant_clause ();
elim_update_added_clause (eliminator, r);
clause.clear ();
elim_update_removed_clause (eliminator, c, pivot);
mark_garbage (c);
}
/*------------------------------------------------------------------------*/
// Resolve two clauses on the pivot literal 'pivot', which is assumed to
// occur in opposite phases in 'c' and 'd'. The actual resolvent is stored
// in the temporary global 'clause' if it is not redundant. It is
// considered redundant if one of the clauses is already marked as garbage
// it is root level satisfied, the resolvent is empty, a unit, or produces a
// self-subsuming resolution, which results in the pivot to be removed from
// at least one of the antecedents.
// Note that current root level assignments are taken into account, i.e., by
// removing root level falsified literals. The function returns true if the
// resolvent is not redundant and for instance has to be taken into account
// during bounded variable elimination.
// Detected units are immediately assigned and in case the last argument is
// true also propagated eagerly in a elimination specific propagation
// routine, which not only finds units but also updates the schedule.
// When this function is called during computation of the number of
// non-trivial (or non-satisfied) resolvents we can eagerly propagate units.
// But during actually adding the resolvents this results in problems as we
// found one rare test case '../test/trace/reg0056.trace' (out of billions),
// where the pivot itself was assigned during such a propagation while
// adding resolvents and lead to pushing a clause to the reconstruction
// stack that later flipped the value of the pivot (while all other literals
// in that clause were unit implied too). Not pushing the pivot clauses to
// the reconstruction stack produced a wrong model too. Our fix is to only
// eagerly propagate during computation of the number of resolvents and
// otherwise delay propagation until the end of elimination (which is less
// precise regarding scheduling but very rarely happens).
bool Internal::resolve_clauses (Eliminator & eliminator,
Clause * c, int pivot, Clause * d,
const bool propagate_eagerly) {
assert (!c->redundant);
assert (!d->redundant);
stats.elimres++;
if (c->garbage || d->garbage) return false;
if (c->size > d->size) { pivot = -pivot; swap (c, d); }
assert (!level);
assert (clause.empty ());
int satisfied = 0; // Contains this satisfying literal.
int tautological = 0; // Clashing literal if tautological.
int s = 0; // Actual literals from 'c'.
int t = 0; // Actual literals from 'd'.
// First determine whether the first antecedent is satisfied, add its
// literals to 'clause' and mark them (except for 'pivot').
//
for (const auto & lit : *c) {
if (lit == pivot) { s++; continue; }
assert (lit != -pivot);
const signed char tmp = val (lit);
if (tmp > 0) { satisfied = lit; break; }
else if (tmp < 0) continue;
else mark (lit), clause.push_back (lit), s++;
}
if (satisfied) {
LOG (c, "satisfied by %d antecedent", satisfied);
elim_update_removed_clause (eliminator, c, satisfied);
mark_garbage (c);
clause.clear ();
unmark (c);
return false;
}
// Then determine whether the second antecedent is satisfied, add its
// literal to 'clause' and check whether a clashing literal is found, such
// that the resolvent would be tautological.
//
for (const auto & lit : *d) {
if (lit == -pivot) { t++; continue; }
assert (lit != pivot);
signed char tmp = val (lit);
if (tmp > 0) { satisfied = lit; break; }
else if (tmp < 0) continue;
else if ((tmp = marked (lit)) < 0) { tautological = lit; break; }
else if (!tmp) clause.push_back (lit), t++;
else assert (tmp > 0), t++;
}
unmark (c);
const int64_t size = clause.size ();
if (satisfied) {
LOG (d, "satisfied by %d antecedent", satisfied);
elim_update_removed_clause (eliminator, d, satisfied);
mark_garbage (d);
clause.clear ();
return false;
}
LOG (c, "first antecedent");
LOG (d, "second antecedent");
if (tautological) {
clause.clear ();
LOG ("resolvent tautological on %d", tautological);
return false;
}
if (!size) {
clause.clear ();
LOG ("empty resolvent");
learn_empty_clause ();
return false;
}
if (size == 1) {
int unit = clause[0];
LOG ("unit resolvent %d", unit);
clause.clear ();
assign_unit (unit);
if (propagate_eagerly)
elim_propagate (eliminator, unit);
return false;
}
LOG (clause, "resolvent");
// Double self-subsuming resolution. The clauses 'c' and 'd' are
// identical except for the pivot which occurs in different phase. The
// resolvent subsumes both antecedents.
if (s > size && t > size) {
assert (s == size + 1);
assert (t == size + 1);
clause.clear ();
elim_on_the_fly_self_subsumption (eliminator, c, pivot);
LOG (d, "double pivot %d on-the-fly self-subsuming resolution", -pivot);
stats.elimotfsub++;
stats.subsumed++;
elim_update_removed_clause (eliminator, d, -pivot);
mark_garbage (d);
return false;
}
// Single self-subsuming resolution: The pivot can be removed from 'c',
// which is implemented by adding a clause which is the same as 'c' but
// with 'pivot' removed and then marking 'c' as garbage.
if (s > size) {
assert (s == size + 1);
clause.clear ();
elim_on_the_fly_self_subsumption (eliminator, c, pivot);
return false;
}
// Same single self-subsuming resolution situation, but only for 'd'.
if (t > size) {
assert (t == size + 1);
clause.clear ();
elim_on_the_fly_self_subsumption (eliminator, d, -pivot);
return false;
}
return true;
}
/*------------------------------------------------------------------------*/
// Check whether the number of non-tautological resolvents on 'pivot' is
// smaller or equal to the number of clauses with 'pivot' or '-pivot'. This
// is the main criteria of bounded variable elimination. As a side effect
// it flushes garbage clauses with that variable, sorts its occurrence lists
// (smallest clauses first) and also negates pivot if it has more positive
// than negative occurrences.
bool
Internal::elim_resolvents_are_bounded (Eliminator & eliminator, int pivot)
{
const bool substitute = !eliminator.gates.empty ();
if (substitute) LOG ("trying to substitute %d", pivot);
stats.elimtried++;
assert (!unsat);
assert (active (pivot));
const Occs & ps = occs (pivot);
const Occs & ns = occs (-pivot);
const int64_t pos = ps.size ();
const int64_t neg = ns.size ();
if (!pos || !neg) return lim.elimbound >= 0;
const int64_t bound = pos + neg + lim.elimbound;
LOG ("checking number resolvents on %d bounded by "
"%" PRId64 " = %" PRId64 " + %" PRId64 " + %" PRId64,
pivot, bound, pos, neg, lim.elimbound);
// Try all resolutions between a positive occurrence (outer loop) of
// 'pivot' and a negative occurrence of 'pivot' (inner loop) as long the
// bound on non-tautological resolvents is not hit and the size of the
// generated resolvents does not exceed the resolvent clause size limit.
int64_t resolvents = 0; // Non-tautological resolvents.
for (const auto & c : ps) {
assert (!c->redundant);
if (c->garbage) continue;
for (const auto & d : ns) {
assert (!d->redundant);
if (d->garbage) continue;
if (substitute && c->gate == d->gate) continue;
stats.elimrestried++;
if (resolve_clauses (eliminator, c, pivot, d, true)) {
resolvents++;
int size = clause.size ();
clause.clear ();
LOG ("now at least %" PRId64
" non-tautological resolvents on pivot %d",
resolvents, pivot);
if (size > opts.elimclslim) {
LOG ("resolvent size %d too big after %" PRId64
" resolvents on %d",
size, resolvents, pivot);
return false;
}
if (resolvents > bound) {
LOG ("too many non-tautological resolvents on %d", pivot);
return false;
}
} else if (unsat) return false;
else if (val (pivot)) return false;
}
}
LOG ("need %" PRId64 " <= %" PRId64 " non-tautological resolvents",
resolvents, bound);
return true;
}
/*------------------------------------------------------------------------*/
// Add all resolvents on 'pivot' and connect them.
inline void
Internal::elim_add_resolvents (Eliminator & eliminator, int pivot) {
const bool substitute = !eliminator.gates.empty ();
if (substitute) {
LOG ("substituting pivot %d by resolving with %zd gate clauses",
pivot, eliminator.gates.size ());
stats.elimsubst++;
}
LOG ("adding all resolvents on %d", pivot);
assert (!val (pivot));
assert (!flags (pivot).eliminated ());
const Occs & ps = occs (pivot);
const Occs & ns = occs (-pivot);
int64_t resolvents = 0;
for (auto & c : ps) {
if (unsat) break;
if (c->garbage) continue;
for (auto & d : ns) {
if (unsat) break;
if (d->garbage) continue;
if (substitute && c->gate == d->gate) continue;
if (!resolve_clauses (eliminator, c, pivot, d, false)) continue;
Clause * r = new_resolved_irredundant_clause ();
elim_update_added_clause (eliminator, r);
eliminator.enqueue (r);
clause.clear ();
resolvents++;
}
}
LOG ("added %" PRId64 " resolvents to eliminate %d", resolvents, pivot);
}
/*------------------------------------------------------------------------*/
// Remove clauses with 'pivot' and '-pivot' by marking them as garbage and
// push them on the extension stack.
void
Internal::mark_eliminated_clauses_as_garbage (Eliminator & eliminator,
int pivot)
{
assert (!unsat);
LOG ("marking irredundant clauses with %d as garbage", pivot);
const int64_t substitute = eliminator.gates.size ();
if (substitute)
LOG ("pushing %" PRId64 " gate clauses on extension stack", substitute);
int64_t pushed = 0;
Occs & ps = occs (pivot);
for (const auto & c : ps) {
if (c->garbage) continue;
mark_garbage (c);
assert (!c->redundant);
if (!substitute || c->gate) {
external->push_clause_on_extension_stack (c, pivot);
pushed++;
}
elim_update_removed_clause (eliminator, c, pivot);
}
erase_occs (ps);
LOG ("marking irredundant clauses with %d as garbage", -pivot);
Occs & ns = occs (-pivot);
for (const auto & d : ns) {
if (d->garbage) continue;
mark_garbage (d);
assert (!d->redundant);
if (!substitute || d->gate) {
external->push_clause_on_extension_stack (d, -pivot);
pushed++;
}
elim_update_removed_clause (eliminator, d, -pivot);
}
erase_occs (ns);
if (substitute) assert (pushed <= substitute);
// Unfortunately, we can not use the trick by Niklas Soerensson anymore,
// which avoids saving all clauses on the extension stack. This would
// break our new incremental 'restore' logic.
}
/*------------------------------------------------------------------------*/
// Try to eliminate 'pivot' by bounded variable elimination.
void
Internal::try_to_eliminate_variable (Eliminator & eliminator, int pivot) {
if (!active (pivot)) return;
assert (!frozen (pivot));
// First flush garbage clauses.
//
int64_t pos = flush_occs (pivot);
int64_t neg = flush_occs (-pivot);
if (pos > neg) { pivot = -pivot; swap (pos, neg); }
LOG ("pivot %d occurs positively %" PRId64 " times and negatively %" PRId64 " times",
pivot, pos, neg);
assert (!eliminator.schedule.contains (abs (pivot)));
assert (pos <= neg);
if (pos && neg > opts.elimocclim) {
LOG ("too many occurrences thus not eliminated %d", pivot);
assert (!eliminator.schedule.contains (abs (pivot)));
return;
}
LOG ("trying to eliminate %d", pivot);
assert (!flags (pivot).eliminated ());
// Sort occurrence lists, such that shorter clauses come first.
Occs & ps = occs (pivot);
stable_sort (ps.begin (), ps.end (), clause_smaller_size ());
Occs & ns = occs (-pivot);
stable_sort (ns.begin (), ns.end (), clause_smaller_size ());
if (pos) find_gate_clauses (eliminator, pivot);
if (!unsat && !val (pivot)) {
if (elim_resolvents_are_bounded (eliminator, pivot)) {
LOG ("number of resolvents on %d are bounded", pivot);
elim_add_resolvents (eliminator, pivot);
if (!unsat) mark_eliminated_clauses_as_garbage (eliminator, pivot);
if (active (pivot)) mark_eliminated (pivot);
} else LOG ("too many resolvents on %d so not eliminated", pivot);
}
unmark_gate_clauses (eliminator);
elim_backward_clauses (eliminator);
}
/*------------------------------------------------------------------------*/
void
Internal::mark_redundant_clauses_with_eliminated_variables_as_garbage () {
for (const auto & c : clauses) {
if (c->garbage || !c->redundant) continue;
bool clean = true;
for (const auto & lit : *c) {
Flags & f = flags (lit);
if (f.eliminated ()) { clean = false; break; }
if (f.pure ()) { clean = false; break; }
}
if (!clean) mark_garbage (c);
}
}
/*------------------------------------------------------------------------*/
// This function performs one round of bounded variable elimination and
// returns the number of eliminated variables. The additional result
// 'completed' is true if this elimination round ran to completion (all
// variables have been tried). Otherwise it was asynchronously terminated
// or the resolution limit was hit.
int Internal::elim_round (bool & completed) {
assert (opts.elim);
assert (!unsat);
START_SIMPLIFIER (elim, ELIM);
stats.elimrounds++;
last.elim.marked = stats.mark.elim;
assert (!level);
int64_t resolution_limit;
if (opts.elimlimited) {
int64_t delta = stats.propagations.search;
delta *= 1e-3 * opts.elimreleff;
if (delta < opts.elimineff) delta = opts.elimineff;
if (delta > opts.elimaxeff) delta = opts.elimaxeff;
delta = max (delta, (int64_t) 2l * active ());
PHASE ("elim-round", stats.elimrounds,
"limit of %" PRId64 " resolutions", delta);
resolution_limit = stats.elimres + delta;
} else {
PHASE ("elim-round", stats.elimrounds, "resolutions unlimited");
resolution_limit = LONG_MAX;
}
init_noccs ();
// First compute the number of occurrences of each literal and at the same
// time mark satisfied clauses and update 'elim' flags of variables in
// clauses with root level assigned literals (both false and true).
//
for (const auto & c : clauses) {
if (c->garbage || c->redundant) continue;
bool satisfied = false, falsified = false;
for (const auto & lit : *c) {
const signed char tmp = val (lit);
if (tmp > 0) satisfied = true;
else if (tmp < 0) falsified = true;
else assert (active (lit));
}
if (satisfied) mark_garbage (c); // forces more precise counts
else {
for (const auto & lit : *c) {
if (!active (lit)) continue;
if (falsified) mark_elim (lit); // simulate unit propagation
noccs (lit)++;
}
}
}
init_occs ();
Eliminator eliminator (this);
ElimSchedule & schedule = eliminator.schedule;
// Now find elimination candidates which occurred in clauses removed since
// the last time we ran bounded variable elimination, which in turned
// triggered their 'elim' bit to be set.
//
for (auto idx : vars) {
if (!active (idx)) continue;
if (frozen (idx)) continue;
if (!flags (idx).elim) continue;
flags (idx).elim = false;
LOG ("scheduling %d for elimination initially", idx);
schedule.push_back (idx);
}
schedule.shrink ();
#ifndef QUIET
int64_t scheduled = schedule.size ();
#endif
PHASE ("elim-round", stats.elimrounds,
"scheduled %" PRId64 " variables %.0f%% for elimination",
scheduled, percent (scheduled, active ()));
// Connect irredundant clauses.
//
for (const auto & c : clauses)
if (!c->garbage && !c->redundant)
for (const auto & lit : *c)
if (active (lit))
occs (lit).push_back (c);
#ifndef QUIET
const int64_t old_resolutions = stats.elimres;
#endif
const int old_eliminated = stats.all.eliminated;
const int old_fixed = stats.all.fixed;
// Limit on garbage bytes during variable elimination. If the limit is hit
// a garbage collection is performed.
//
const int64_t garbage_limit = (2*stats.irrbytes/3) + (1<<20);
// Main loops tries to eliminate variables according to the schedule. The
// schedule is updated dynamically and variables are potentially
// rescheduled to be tried again if they occur in a removed clause.
//
#ifndef QUIET
int64_t tried = 0;
#endif
while (!unsat &&
!terminated_asynchronously () &&
stats.elimres <= resolution_limit &&
!schedule.empty ()) {
int idx = schedule.front ();
schedule.pop_front ();
flags (idx).elim = false;
try_to_eliminate_variable (eliminator, idx);
#ifndef QUIET
tried++;
#endif
if (stats.garbage <= garbage_limit) continue;
mark_redundant_clauses_with_eliminated_variables_as_garbage ();
garbage_collection ();
}
// If the schedule is empty all variables have been tried (even
// rescheduled ones). Otherwise asynchronous termination happened or we
// ran into the resolution limit (or derived unsatisfiability).
//
completed = !schedule.size ();
PHASE ("elim-round", stats.elimrounds,
"tried to eliminate %" PRId64 " variables %.0f%% (%zd remain)",
tried, percent (tried, scheduled), schedule.size ());
schedule.erase ();
// Collect potential literal clause instantiation pairs, which needs full
// occurrence lists and thus we have it here before resetting them.
//
Instantiator instantiator;
if (!unsat &&
!terminated_asynchronously () &&
opts.instantiate)
collect_instantiation_candidates (instantiator);
reset_occs ();
reset_noccs ();
// Mark all redundant clauses with eliminated variables as garbage.
//
if (!unsat)
mark_redundant_clauses_with_eliminated_variables_as_garbage ();
int eliminated = stats.all.eliminated - old_eliminated;
#ifndef QUIET
int64_t resolutions = stats.elimres - old_resolutions;
PHASE ("elim-round", stats.elimrounds,
"eliminated %d variables %.0f%% in %" PRId64 " resolutions",
eliminated, percent (eliminated, scheduled), resolutions);
#endif
last.elim.subsumephases = stats.subsumephases;
const int units = stats.all.fixed - old_fixed;
report ('e', !opts.reportall && !(eliminated + units));
STOP_SIMPLIFIER (elim, ELIM);
if (!unsat &&
!terminated_asynchronously () &&
instantiator) // Do we have candidate pairs?
instantiate (instantiator);
return eliminated; // non-zero if successful
}
/*------------------------------------------------------------------------*/
// Increase elimination bound (additional clauses allowed during variable
// elimination), which is triggered if elimination with last bound completed
// (including no new subsumptions). This was pioneered by GlueMiniSAT in
// the SAT Race 2015 and then picked up Chanseok Oh in his COMinisatPS
// solver, which in turn is used in the Maple series of SAT solvers.
// The bound is no increased if the maximum bound is reached.
void Internal::increase_elimination_bound () {
if (lim.elimbound >= opts.elimboundmax) return;
if (lim.elimbound < 0) lim.elimbound = 0;
else if (!lim.elimbound) lim.elimbound = 1;
else lim.elimbound *= 2;
if (lim.elimbound > opts.elimboundmax)
lim.elimbound = opts.elimboundmax;
PHASE ("elim-phase", stats.elimphases,
"new elimination bound %" PRId64 "", lim.elimbound);
// Now reschedule all active variables for elimination again.
//
int count = 0;
for (auto idx : vars) {
if (!active (idx)) continue;
if (flags (idx).elim) continue;
mark_elim (idx);
count++;
}
LOG ("marked %d variables as elimination candidates", count);
report ('^');
}
/*------------------------------------------------------------------------*/
void Internal::elim (bool update_limits) {
if (unsat) return;
if (level) backtrack ();
if (!propagate ()) { learn_empty_clause (); return; }
stats.elimphases++;
PHASE ("elim-phase", stats.elimphases,
"starting at most %d elimination rounds",
opts.elimrounds);
#ifndef QUIET
int old_active_variables = active ();
int old_eliminated = stats.all.eliminated;
#endif
// Make sure there was a complete subsumption phase since last
// elimination including vivification etc.
//
if (last.elim.subsumephases == stats.subsumephases)
subsume (update_limits);
reset_watches (); // saves lots of memory
// Alternate one round of bounded variable elimination ('elim_round') and
// subsumption ('subsume_round'), blocked ('block') and covered clause
// elimination ('cover') until nothing changes, or the round limit is hit.
// The loop also aborts early if no variable could be eliminated, the
// empty clause is resolved, it is asynchronously terminated or a
// resolution limit is hit.
// This variable determines whether the whole loop of this bounded
// variable elimination phase ('elim') ran until completion. This
// potentially triggers an incremental increase of the elimination bound.
//
bool phase_complete = false;
int round = 1;
while (!unsat &&
!phase_complete &&
!terminated_asynchronously ()) {
bool round_complete;
#ifndef QUIET
int eliminated =
#endif
elim_round (round_complete);
if (!round_complete) {
PHASE ("elim-phase", stats.elimphases,
"last round %d incomplete %s",
round, eliminated ? "but successful" : "and unsuccessful");
assert (!phase_complete);
break;
}
if (round++ >= opts.elimrounds) {
PHASE ("elim-phase", stats.elimphases,
"round limit %d hit (%s)", round-1,
eliminated ? "though last round successful" :
"last round unsuccessful anyhow");
assert (!phase_complete);
break;
}
// Prioritize 'subsumption' over blocked and covered clause elimination.
if (subsume_round ()) continue;
if (block ()) continue;
if (cover ()) continue;
// Was not able to generate new variable elimination candidates after
// variable elimination round, neither through subsumption, nor blocked,
// nor covered clause elimination.
//
PHASE ("elim-phase", stats.elimphases,
"no new variable elimination candidates");
assert (round_complete);
phase_complete = true;
}
if (phase_complete) {
stats.elimcompleted++;
PHASE ("elim-phase", stats.elimphases,
"fully completed elimination %" PRId64
" at elimination bound %" PRId64 "",
stats.elimcompleted, lim.elimbound);
} else {
PHASE ("elim-phase", stats.elimphases,
"incomplete elimination %" PRId64
" at elimination bound %" PRId64 "",
stats.elimcompleted + 1, lim.elimbound);
}
init_watches ();
connect_watches ();
if (unsat) LOG ("elimination derived empty clause");
else if (propagated < trail.size ()) {
LOG ("elimination produced %zd units",
(size_t)(trail.size () - propagated));
if (!propagate ()) {
LOG ("propagating units after elimination results in empty clause");
learn_empty_clause ();
}
}
// If we ran variable elimination until completion we increase the
// variable elimination bound and reschedule elimination of all variables.
//
if (phase_complete) increase_elimination_bound ();
#ifndef QUIET
int eliminated = stats.all.eliminated - old_eliminated;
PHASE ("elim-phase", stats.elimphases,
"eliminated %d variables %.2f%%",
eliminated, percent (eliminated, old_active_variables));
#endif
if (!update_limits) return;
int64_t delta = scale (opts.elimint * (stats.elimphases + 1));
lim.elim = stats.conflicts + delta;
PHASE ("elim-phase", stats.elimphases,
"new limit at %" PRId64 " conflicts after %" PRId64 " conflicts",
lim.elim, delta);
last.elim.fixed = stats.all.fixed;
}
}

37
hCaD_V2/src/elim.hpp Normal file
View File

@ -0,0 +1,37 @@
#ifndef _elim_hpp_INCLUDED
#define _elim_hpp_INCLUDED
#include "heap.hpp" // Alphabetically after 'elim.hpp'.
namespace CaDiCaL {
struct Internal;
struct elim_more {
Internal * internal;
elim_more (Internal * i) : internal (i) { }
bool operator () (unsigned a, unsigned b);
};
typedef heap<elim_more> ElimSchedule;
struct Eliminator {
Internal * internal;
ElimSchedule schedule;
Eliminator (Internal * i) : internal (i), schedule (elim_more (i)) { }
~Eliminator ();
queue<Clause*> backward;
Clause * dequeue ();
void enqueue (Clause *);
vector<Clause *> gates;
vector<int> marked;
};
}
#endif

95
hCaD_V2/src/ema.cpp Normal file
View File

@ -0,0 +1,95 @@
#include "internal.hpp"
namespace CaDiCaL {
// Updating an exponential moving average is placed here since we want to
// log both updates and phases of initialization, thus need 'LOG'.
//
// We now use initialization bias correction as in the ADAM method
// [KingmaBa-ICLR'15] instead of our ad-hoc initialization method used
// before. Our old variant used exponentially decreasing alphas:
//
// 1,
// 1/2, 1/2,
// 1/4, 1/4, 1/4, 1/4
// 1/8, 1/8, 1/8, 1/8, 1/8, 1/8, 1/8, 1/8,
// ...
// 2^-n, ..., 2^-n 'n' times
// alpha, alpha, ... now 'alpha' forever.
//
// where 2^-n is the smallest negative power of two above 'alpha'
//
// This old method is better than the initializations described in our
// [BiereFroehlich-POS'15] paper and actually faster than the ADAM method,
// but less precise. This old method, which we consider obsolete but it
// could still be useful for implementations relying on integers instead of
// floating points thus only needs shifts and integer arithmetic.
//
// Our new method for unbiased initialization of the exponential averages
// works as follows. First the biased moving average is computed as usual.
// Note that (as already before) we use the simpler equation
//
// new_biased = old_biased + alpha * (y - old_biased);
//
// which in principle (and thus easy to remember) can be implemented as
//
// biased += alpha * (y - biased);
//
// The original formulation in the ADAM paper (with 'alpha = 1 - beta') is
//
// new_biased = beta * old_biased + (1 - beta) * y
//
// To show that these are equivalent (modulo floating point issues)
// consider the following equivalent expressions:
//
// old_biased + alpha * (y - old_biased)
// old_biased + alpha * y - alpha * old_biased
// (1 - alpha) * old_biased + alpha * y
// beta * old_biased + (1 - beta) * y
//
// The real new idea taken from the ADAM paper is however to fix the biased
// moving average with a correction term '1.0 / (1.0 - pow (beta, updated))'
// by multiplication to obtain an unbiased moving average (called simply
// 'value' in our 'code'). In order to avoid computing 'pow' every time, we
// use 'exp' which is multiplied in every update with 'beta'.
void EMA::update (Internal * internal, double y, const char * name) {
#ifdef LOGGING
updated++;
const double old_value = value;
#endif
const double old_biased = biased;
const double delta = y - old_biased;
const double scaled_delta = alpha * delta;
const double new_biased = old_biased + scaled_delta;
LOG ("update %" PRIu64 " of biased %s EMA %g with %g (delta %g) "
"yields %g (scaled delta %g)",
updated, name, old_biased, y, delta, new_biased, scaled_delta);
biased = new_biased;
const double old_exp = exp;
double new_exp, div, new_value;
if (old_exp) {
new_exp = old_exp * beta;
assert (new_exp < 1);
exp = new_exp;
div = 1 - new_exp;
assert (div > 0);
new_value = new_biased / div;
} else {
new_value = new_biased;
#ifdef LOGGING
new_exp = 0;
div = 1;
#endif
}
value = new_value;
LOG ("update %" PRIu64 " of corrected %s EMA %g with %g (delta %g) "
"yields %g (exponent %g, divisor %g)" ,
updated, name, old_value, y, delta, new_value, new_exp, div);
#ifndef LOGGING
(void) internal;
(void) name;
#endif
}
}

61
hCaD_V2/src/ema.hpp Normal file
View File

@ -0,0 +1,61 @@
#ifndef _ema_hpp_INCLUDED
#define _ema_hpp_INCLUDED
namespace CaDiCaL {
struct Internal;
// This is a more complex generic exponential moving average class to
// support more robust initialization (see comments in the 'update'
// implementation).
struct EMA {
#ifdef LOGGING
uint64_t updated;
#endif
double value; // unbiased (corrected) moving average
double biased; // biased initialized moving average
double alpha; // input scaling with 'alpha = 1 - beta'
double beta; // decay of 'biased' with 'beta = 1 - alpha'
double exp; // 'exp = pow (beta, updated)'
EMA () :
#ifdef LOGGING
updated (0),
#endif
value (0), biased (0), alpha (0), beta (0), exp (0) { }
EMA (double a) :
#ifdef LOGGING
updated (0),
#endif
value (0), biased (0), alpha (a), beta (1 - a), exp (!!beta)
{
assert (beta >= 0);
}
operator double () const { return value; }
void update (Internal *, double y, const char * name);
};
}
/*------------------------------------------------------------------------*/
// Compact average update and initialization macros for better logging.
#define UPDATE_AVERAGE(A,Y) \
do { A.update (internal, (Y), #A); } while (0)
#define INIT_EMA(E,WINDOW) \
do { \
assert ((WINDOW) >= 1); \
double ALPHA = 1.0 / (double)(WINDOW); \
E = EMA (ALPHA); \
LOG ("init " #E " EMA target alpha %g window %d", ALPHA, (int)WINDOW); \
} while (0)
/*------------------------------------------------------------------------*/
#endif

208
hCaD_V2/src/extend.cpp Normal file
View File

@ -0,0 +1,208 @@
#include "internal.hpp"
namespace CaDiCaL {
void External::push_zero_on_extension_stack () {
extension.push_back (0);
LOG ("pushing 0 on extension stack");
}
void External::push_clause_literal_on_extension_stack (int ilit) {
assert (ilit);
const int elit = internal->externalize (ilit);
assert (elit);
extension.push_back (elit);
LOG ("pushing clause literal %d on extension stack (internal %d)",
elit, ilit);
}
void External::push_witness_literal_on_extension_stack (int ilit) {
assert (ilit);
const int elit = internal->externalize (ilit);
assert (elit);
extension.push_back (elit);
LOG ("pushing witness literal %d on extension stack (internal %d)",
elit, ilit);
if (marked (witness, elit)) return;
LOG ("marking witness %d", elit);
mark (witness, elit);
}
// The extension stack allows to reconstruct a satisfying assignment for the
// original formula after removing eliminated clauses. This was pioneered
// by Niklas Soerensson in MiniSAT and for instance is described in our
// inprocessing paper, published at IJCAR'12. This first function adds a
// clause to this stack. First the blocking or eliminated literal is added,
// and then the rest of the clause.
void External::push_clause_on_extension_stack (Clause * c) {
internal->stats.weakened++;
internal->stats.weakenedlen += c->size;
push_zero_on_extension_stack ();
for (const auto & lit : *c)
push_clause_literal_on_extension_stack (lit);
}
void External::push_clause_on_extension_stack (Clause * c, int pivot) {
push_zero_on_extension_stack ();
push_witness_literal_on_extension_stack (pivot);
push_clause_on_extension_stack (c);
}
void
External::push_binary_clause_on_extension_stack (int pivot, int other) {
internal->stats.weakened++;
internal->stats.weakenedlen += 2;
push_zero_on_extension_stack ();
push_witness_literal_on_extension_stack (pivot);
push_zero_on_extension_stack ();
push_clause_literal_on_extension_stack (pivot);
push_clause_literal_on_extension_stack (other);
}
/*------------------------------------------------------------------------*/
void External::push_external_clause_and_witness_on_extension_stack (
const vector<int> & c, const vector<int> & w) {
extension.push_back (0);
for (const auto & elit : w) {
assert (elit != INT_MIN);
init (abs (elit));
extension.push_back (elit);
mark (witness, elit);
}
extension.push_back (0);
for (const auto & elit : c) {
assert (elit != INT_MIN);
init (abs (elit));
extension.push_back (elit);
}
}
/*------------------------------------------------------------------------*/
// This is the actual extension process. It goes backward over the clauses
// on the extension stack and flips the assignment of one of the blocking
// literals in the conditional autarky stored before the clause. In the
// original algorithm for witness construction for variable elimination and
// blocked clause removal the conditional autarky consists of a single
// literal from the removed clause, while in general the autarky witness can
// contain an arbitrary set of literals. We are using the more general
// witness reconstruction here which for instance would also work for
// super-blocked or set-blocked clauses.
void External::extend () {
assert (!extended);
START (extend);
internal->stats.extensions++;
PHASE ("extend", internal->stats.extensions,
"mapping internal %d assignments to %d assignments",
internal->max_var, max_var);
int64_t updated = 0;
for (unsigned i = 1; i <= (unsigned) max_var; i++) {
const int ilit = e2i[i];
if (!ilit) continue;
while (i >= vals.size ())
vals.push_back (false);
vals[i] = (internal->val (ilit) > 0);
updated++;
}
PHASE ("extend", internal->stats.extensions,
"updated %" PRId64 " external assignments", updated);
PHASE ("extend", internal->stats.extensions,
"extending through extension stack of size %zd",
extension.size ());
const auto begin = extension.begin ();
auto i = extension.end ();
int64_t flipped = 0;
while (i != begin) {
bool satisfied = false;
int lit;
assert (i != begin);
while ((lit = *--i)) {
if (satisfied) continue;
if (ival (lit) > 0) satisfied = true;
assert (i != begin);
}
assert (i != begin);
if (satisfied)
while (*--i)
assert (i != begin);
else {
while ((lit = *--i)) {
const int tmp = ival (lit); // not 'signed char'!!!
if (tmp < 0) {
LOG ("flipping blocking literal %d", lit);
assert (lit);
assert (lit != INT_MIN);
int idx = abs (lit);
while ((size_t) idx >= vals.size ())
vals.push_back (false);
vals[idx] = !vals[idx];
internal->stats.extended++;
flipped++;
}
assert (i != begin);
}
}
}
PHASE ("extend", internal->stats.extensions,
"flipped %" PRId64 " literals during extension", flipped);
extended = true;
LOG ("extended");
STOP (extend);
}
/*------------------------------------------------------------------------*/
bool External::traverse_witnesses_backward (WitnessIterator & it) {
if (internal->unsat) return true;
vector<int> clause, witness;
const auto begin = extension.begin ();
auto i = extension.end ();
while (i != begin) {
int lit;
while ((lit = *--i))
clause.push_back (lit);
while ((lit = *--i))
witness.push_back (lit);
reverse (clause.begin (), clause.end ());
reverse (witness.begin (), witness.end ());
if (!it.witness (clause, witness))
return false;
clause.clear ();
witness.clear ();
}
return true;
}
bool External::traverse_witnesses_forward (WitnessIterator & it) {
if (internal->unsat) return true;
vector<int> clause, witness;
const auto end = extension.end ();
auto i = extension.begin ();
if (i != end) {
int lit = *i++;
do {
assert (!lit), (void) lit;
while ((lit = *i++))
witness.push_back (lit);
assert (!lit);
assert (i != end);
while (i != end && (lit = *i++))
clause.push_back (lit);
if (!it.witness (clause, witness))
return false;
clause.clear ();
witness.clear ();
} while (i != end);
}
return true;
}
/*------------------------------------------------------------------------*/
}

541
hCaD_V2/src/external.cpp Normal file
View File

@ -0,0 +1,541 @@
#include "internal.hpp"
namespace CaDiCaL {
External::External (Internal * i)
:
internal (i),
max_var (0),
vsize (0),
extended (false),
terminator (0),
learner (0),
solution (0),
vars (max_var)
{
assert (internal);
assert (!internal->external);
internal->external = this;
}
External::~External () {
if (solution) delete [] solution;
}
void External::enlarge (int new_max_var) {
assert (!extended);
size_t new_vsize = vsize ? 2*vsize : 1 + (size_t) new_max_var;
while (new_vsize <= (size_t) new_max_var) new_vsize *= 2;
LOG ("enlarge external size from %zd to new size %zd", vsize, new_vsize);
vsize = new_vsize;
}
void External::init (int new_max_var) {
assert (!extended);
if (new_max_var <= max_var) return;
int new_vars = new_max_var - max_var;
int old_internal_max_var = internal->max_var;
int new_internal_max_var = old_internal_max_var + new_vars;
internal->init_vars (new_internal_max_var);
if ((size_t) new_max_var >= vsize) enlarge (new_max_var);
LOG ("initialized %d external variables", new_vars);
if (!max_var) {
assert (e2i.empty ());
e2i.push_back (0);
assert (internal->i2e.empty ());
internal->i2e.push_back (0);
} else {
assert (e2i.size () == (size_t) max_var + 1);
assert (internal->i2e.size () == (size_t) old_internal_max_var + 1);
}
unsigned iidx = old_internal_max_var + 1, eidx;
for (eidx = max_var + 1u;
eidx <= (unsigned) new_max_var;
eidx++, iidx++) {
LOG ("mapping external %u to internal %u", eidx, iidx);
assert (e2i.size () == eidx);
e2i.push_back (iidx);
internal->i2e.push_back (eidx);
assert (internal->i2e[iidx] == (int) eidx);
assert (e2i[eidx] == (int) iidx);
}
if (internal->opts.checkfrozen)
while (new_max_var >= (int64_t) moltentab.size ())
moltentab.push_back (false);
assert (iidx == (size_t) new_internal_max_var + 1);
assert (eidx == (size_t) new_max_var + 1);
max_var = new_max_var;
}
/*------------------------------------------------------------------------*/
void External::reset_assumptions () {
assumptions.clear ();
internal->reset_assumptions ();
}
void External::reset_extended () {
if (!extended) return;
LOG ("reset extended");
extended = false;
}
void External::reset_limits () {
internal->reset_limits ();
}
/*------------------------------------------------------------------------*/
int External::internalize (int elit) {
int ilit;
if (elit) {
assert (elit != INT_MIN);
const int eidx = abs (elit);
if (eidx > max_var) init (eidx);
ilit = e2i [eidx];
if (elit < 0) ilit = -ilit;
if (!ilit) {
assert (internal->max_var < INT_MAX);
ilit = internal->max_var + 1u;
internal->init_vars (ilit);
e2i[eidx] = ilit;
LOG ("mapping external %d to internal %d", eidx, ilit);
e2i[eidx] = ilit;
internal->i2e.push_back (eidx);
assert (internal->i2e[ilit] == eidx);
assert (e2i[eidx] == ilit);
if (elit < 0) ilit = -ilit;
}
if (internal->opts.checkfrozen) {
assert (eidx < (int64_t) moltentab.size ());
if (moltentab[eidx])
FATAL ("can not reuse molten literal %d", eidx);
}
Flags & f = internal->flags (ilit);
if (f.status == Flags::UNUSED) internal->mark_active (ilit);
else if (f.status != Flags::ACTIVE &&
f.status != Flags::FIXED) internal->reactivate (ilit);
if (!marked (tainted, elit) && marked (witness, -elit)) {
assert (!internal->opts.checkfrozen);
LOG ("marking tainted %d", elit);
mark (tainted, elit);
}
} else ilit = 0;
return ilit;
}
void External::add (int elit) {
assert (elit != INT_MIN);
reset_extended ();
if (internal->opts.check &&
(internal->opts.checkwitness || internal->opts.checkfailed))
original.push_back (elit);
const int ilit = internalize (elit);
assert (!elit == !ilit);
if (elit) LOG ("adding external %d as internal %d", elit, ilit);
internal->add_original_lit (ilit);
}
void External::assume (int elit) {
assert (elit);
reset_extended ();
assumptions.push_back (elit);
const int ilit = internalize (elit);
assert (ilit);
LOG ("assuming external %d as internal %d", elit, ilit);
internal->assume (ilit);
}
bool External::failed (int elit) {
assert (elit);
assert (elit != INT_MIN);
int eidx = abs (elit);
if (eidx > max_var) return 0;
int ilit = e2i[eidx];
if (!ilit) return 0;
if (elit < 0) ilit = -ilit;
return internal->failed (ilit);
}
void External::phase (int elit) {
assert (elit);
assert (elit != INT_MIN);
int eidx = abs (elit);
if (eidx > max_var) return;
int ilit = e2i[eidx];
if (!ilit) return;
if (elit < 0) ilit = -ilit;
internal->phase (ilit);
}
void External::unphase (int elit) {
assert (elit);
assert (elit != INT_MIN);
int eidx = abs (elit);
if (eidx > max_var) return;
int ilit = e2i[eidx];
if (!ilit) return;
if (elit < 0) ilit = -ilit;
internal->unphase (ilit);
}
/*------------------------------------------------------------------------*/
// Internal checker if 'solve' claims the formula to be satisfiable.
void External::check_satisfiable () {
LOG ("checking satisfiable");
if (internal->opts.checkwitness)
check_assignment (&External::ival);
if (internal->opts.checkassumptions && !assumptions.empty ())
check_assumptions_satisfied ();
}
// Internal checker if 'solve' claims formula to be unsatisfiable.
void External::check_unsatisfiable () {
LOG ("checking unsatisfiable");
if (internal->opts.checkfailed && !assumptions.empty ())
check_assumptions_failing ();
}
// Check result of 'solve' to be correct.
void External::check_solve_result (int res) {
if (!internal->opts.check) return;
if (res == 10) check_satisfiable ();
if (res == 20) check_unsatisfiable ();
}
// Prepare checking that completely molten literals are not used as argument
// of 'add' or 'assume', which is invalid under freezing semantics. This
// case would be caught by our 'restore' implementation so is only needed
// for checking the deprecated 'freeze' semantics.
void External::update_molten_literals () {
if (!internal->opts.checkfrozen) return;
assert ((size_t) max_var + 1 == moltentab.size ());
int registered = 0, molten = 0;
for (auto lit : vars) {
if (moltentab[lit]) {
LOG ("skipping already molten literal %d", lit);
molten++;
} else if (frozen (lit))
LOG ("skipping currently frozen literal %d", lit);
else {
LOG ("new molten literal %d", lit);
moltentab[lit] = true;
registered++;
molten++;
}
}
LOG ("registered %d new molten literals", registered);
LOG ("reached in total %d molten literals", molten);
}
int External::solve (bool preprocess_only) {
reset_extended ();
update_molten_literals ();
int res = internal->solve (preprocess_only);
if (res == 10) extend ();
check_solve_result (res);
reset_limits ();
return res;
}
void External::terminate () { internal->terminate (); }
int External::lookahead () {
reset_extended ();
update_molten_literals ();
int ilit = internal->lookahead ();
const int elit = (ilit && ilit != INT_MIN) ? internal->externalize (ilit) : 0;
LOG ("lookahead internal %d external %d", ilit, elit);
return elit;
}
CaDiCaL::CubesWithStatus External::generate_cubes (int depth, int min_depth = 0) {
reset_extended ();
update_molten_literals ();
reset_limits ();
auto cubes = internal->generate_cubes (depth, min_depth);
auto externalize = [this](int ilit) {
const int elit = ilit ? internal->externalize (ilit) : 0;
MSG ("lookahead internal %d external %d", ilit, elit);
return elit;
};
auto externalize_map = [this, externalize](std::vector<int> cube) {
(void) this;
MSG("Cube : ");
std::for_each(begin(cube), end(cube), externalize);
};
std::for_each(begin(cubes.cubes), end(cubes.cubes), externalize_map);
return cubes;
}
/*------------------------------------------------------------------------*/
void External::freeze (int elit) {
reset_extended ();
int ilit = internalize (elit);
unsigned eidx = vidx (elit);
while (eidx >= frozentab.size ())
frozentab.push_back (0);
unsigned & ref = frozentab[eidx];
if (ref < UINT_MAX) {
ref++;
LOG ("external variable %d frozen once and now frozen %u times",
eidx, ref);
} else
LOG ("external variable %d frozen but remains frozen forever", eidx);
internal->freeze (ilit);
}
void External::melt (int elit) {
reset_extended ();
int ilit = internalize (elit);
unsigned eidx = vidx (elit);
assert (eidx < frozentab.size ());
unsigned & ref = frozentab[eidx];
assert (ref > 0);
if (ref < UINT_MAX) {
if (!--ref)
LOG ("external variable %d melted once and now completely melted",
eidx);
else
LOG ("external variable %d melted once but remains frozen %u times",
eidx, ref);
} else
LOG ("external variable %d melted but remains frozen forever", eidx);
internal->melt (ilit);
}
/*------------------------------------------------------------------------*/
void External::check_assignment (int (External::*a)(int) const) {
// First check all assigned and consistent.
//
for (auto idx : vars) {
if (!(this->*a) (idx)) FATAL ("unassigned variable: %d", idx);
if ((this->*a) (idx) != -(this->*a)(-idx))
FATAL ("inconsistently assigned literals %d and %d", idx, -idx);
}
// Then check that all (saved) original clauses are satisfied.
//
bool satisfied = false;
const auto end = original.end ();
auto start = original.begin (), i = start;
int64_t count = 0;
for (; i != end; i++) {
int lit = *i;
if (!lit) {
if (!satisfied) {
fatal_message_start ();
fputs ("unsatisfied clause:\n", stderr);
for (auto j = start; j != i; j++)
fprintf (stderr, "%d ", *j);
fputc ('0', stderr);
fatal_message_end ();
}
satisfied = false;
start = i + 1;
count++;
} else if (!satisfied && (this->*a) (lit) > 0) satisfied = true;
}
VERBOSE (1,
"satisfying assignment checked on %" PRId64 " clauses",
count);
}
/*------------------------------------------------------------------------*/
void External::check_assumptions_satisfied () {
for (const auto & lit : assumptions) {
// Not 'signed char' !!!!
const int tmp = ival (lit);
if (tmp < 0) FATAL ("assumption %d falsified", lit);
if (!tmp) FATAL ("assumption %d unassigned", lit);
}
VERBOSE (1,
"checked that %zd assumptions are satisfied",
assumptions.size ());
}
void External::check_assumptions_failing () {
Solver * checker = new Solver ();
checker->prefix ("checker ");
#ifdef LOGGING
if (internal->opts.log) checker->set ("log", true);
#endif
for (const auto & lit : original)
checker->add (lit);
for (const auto & lit : assumptions) {
if (!failed (lit)) continue;
LOG ("checking failed literal %d in core", lit);
checker->add (lit);
checker->add (0);
}
int res = checker->solve ();
if (res != 20) FATAL ("failed assumptions do not form a core");
delete checker;
VERBOSE (1,
"checked that %zd failing assumptions form a core",
assumptions.size ());
}
/*------------------------------------------------------------------------*/
// Traversal of unit clauses is implemented here.
// In principle we want to traverse the clauses of the simplified formula
// only, particularly eliminated variables should be completely removed.
// This poses the question what to do with unit clauses. Should they be
// considered part of the simplified formula or of the witness to construct
// solutions for the original formula? Ideally they should be considered
// to be part of the witness only, i.e., as they have been simplified away.
// Therefore we distinguish frozen and non-frozen units during clause
// traversal. Frozen units are treated as unit clauses while non-frozen
// units are treated as if they were already eliminated and put on the
// extension stack as witness clauses.
// Furthermore, eliminating units during 'compact' could be interpreted as
// variable elimination, i.e., just add the resolvents (remove falsified
// literals), then drop the clauses with the unit, and push the unit on the
// extension stack. This is of course only OK if the user did not freeze
// that variable (even implicitly during assumptions).
// Thanks go to Fahiem Bacchus for asking why there is a necessity to
// distinguish these two cases (frozen and non-frozen units). The answer is
// that it is not strictly necessary, and this distinction could be avoided
// by always treating units as remaining unit clauses, thus only using the
// first of the two following functions and dropping the 'if (!frozen (idx))
// continue;' check in it. This has however the down-side that those units
// are still in the simplified formula and only as units. I would not
// consider such a formula as really being 'simplified'. On the other hand
// if the user explicitly freezes a literal, then it should continue to be in
// the simplified formula during traversal. So also only using the second
// function is not ideal.
// There is however a catch where this solution breaks down (in the sense of
// producing less optimal results - that is keeping units in the formula
// which better would be witness clauses). The problem is with compact
// preprocessing which removes eliminated but also fixed internal variables.
// One internal unit (fixed) variable is kept and all the other external
// literals which became unit are mapped to that internal literal (negated
// or positively). Compact is called non-deterministically from the point
// of the user and thus there is no control on when this happens. If
// compact happens those external units are mapped to a single internal
// literal now and then all share the same 'frozen' counter. So if the
// user freezes one of them all in essence get frozen, which in turn then
// makes a difference in terms of traversing such a unit as unit clause or
// as unit witness.
bool
External::traverse_all_frozen_units_as_clauses (ClauseIterator & it)
{
if (internal->unsat) return true;
vector<int> clause;
for (auto idx : vars) {
if (!frozen (idx)) continue;
const int tmp = fixed (idx);
if (!tmp) continue;
int unit = tmp < 0 ? -idx : idx;
clause.push_back (unit);
if (!it.clause (clause))
return false;
clause.clear ();
}
return true;
}
bool
External::traverse_all_non_frozen_units_as_witnesses (WitnessIterator & it)
{
if (internal->unsat) return true;
vector<int> clause_and_witness;
for (auto idx : vars) {
if (frozen (idx)) continue;
const int tmp = fixed (idx);
if (!tmp) continue;
int unit = tmp < 0 ? -idx : idx;
clause_and_witness.push_back (unit);
if (!it.witness (clause_and_witness, clause_and_witness))
return false;
clause_and_witness.clear ();
}
return true;
}
/*------------------------------------------------------------------------*/
void External::copy_flags (External & other) const {
const vector<Flags> & this_ftab = internal->ftab;
vector<Flags> & other_ftab = other.internal->ftab;
const unsigned limit = min (max_var, other.max_var);
for (unsigned eidx = 1; eidx <= limit; eidx++) {
const int this_ilit = e2i[eidx];
if (!this_ilit) continue;
const int other_ilit = other.e2i[eidx];
if (!other_ilit) continue;
if (!internal->active (this_ilit)) continue;
if (!other.internal->active (other_ilit)) continue;
assert (this_ilit != INT_MIN);
assert (other_ilit != INT_MIN);
const Flags & this_flags = this_ftab[abs (this_ilit)];
Flags & other_flags = other_ftab[abs (other_ilit)];
this_flags.copy (other_flags);
}
}
/*------------------------------------------------------------------------*/
void External::export_learned_empty_clause () {
assert (learner);
if (learner->learning (0)) {
LOG ("exporting learned empty clause");
learner->learn (0);
} else
LOG ("not exporting learned empty clause");
}
void External::export_learned_unit_clause (int ilit) {
assert (learner);
if (learner->learning (1)) {
LOG ("exporting learned unit clause");
const int elit = internal->externalize (ilit);
assert (elit);
learner->learn (elit);
learner->learn (0);
} else
LOG ("not exporting learned unit clause");
}
void External::export_learned_large_clause (const vector<int> & clause) {
assert (learner);
size_t size = clause.size ();
assert (size <= (unsigned) INT_MAX);
if (learner->learning ((int) size)) {
LOG ("exporting learned clause of size %zu", size);
for (auto ilit : clause) {
const int elit = internal->externalize (ilit);
assert (elit);
learner->learn (elit);
}
learner->learn (0);
} else
LOG ("not exporting learned clause of size %zu", size);
}
}

356
hCaD_V2/src/external.hpp Normal file
View File

@ -0,0 +1,356 @@
#ifndef _external_hpp_INCLUDED
#define _external_hpp_INCLUDED
/*------------------------------------------------------------------------*/
#include "range.hpp"
/*------------------------------------------------------------------------*/
namespace CaDiCaL {
using namespace std;
/*------------------------------------------------------------------------*/
// The CaDiCaL code is split into three layers:
//
// Solver: facade object providing the actual API of the solver
// External: communication layer between 'Solver' and 'Internal'
// Internal: the actual solver code
//
// Note, that 'Solver' is defined in 'cadical.hpp' and 'solver.cpp', while
// 'External' and 'Internal' in '{external,internal}.{hpp,cpp}'.
//
// Also note, that any user should access the library only through the
// 'Solver' API. For the library internal 'Parser' code we make an
// exception and allow access to both 'External' and 'Internal'. The former
// to enforce the same external to internal mapping of variables and the
// latter for profiling and messages. The same applies to 'App'.
//
// The 'External' class provided here stores the information needed to map
// external variable indices to internal variables (actually literals).
// This is helpful for shrinking the working size of the internal solver
// after many variables become inactive. It will also help to provide
// support for extended resolution in the future, since it allows to
// introduce only internally visible variables (even though we do not know
// how to support generating incremental proofs in this situation yet).
//
// External literals are usually called 'elit' and internal 'ilit'.
/*------------------------------------------------------------------------*/
struct Clause;
struct Internal;
struct CubesWithStatus;
/*------------------------------------------------------------------------*/
/*------------------------------------------------------------------------*/
struct External {
/*==== start of state ==================================================*/
Internal * internal; // The actual internal solver.
int max_var; // External maximum variable index.
size_t vsize; // Allocated external size.
vector<bool> vals; // Current external (extended) assignment.
vector<int> e2i; // External 'idx' to internal 'lit'.
vector<int> assumptions; // External assumptions.
// The extension stack for reconstructing complete satisfying assignments
// (models) of the original external formula is kept in this external
// solver object. It keeps track of blocked clauses and clauses containing
// eliminated variable. These irredundant clauses are stored in terms of
// external literals on the 'extension' stack after mapping the
// internal literals given as arguments with 'externalize'.
bool extended; // Have been extended.
vector<int> extension; // Solution reconstruction extension stack.
vector<bool> witness; // Literal witness on extension stack.
vector<bool> tainted; // Literal tainted in adding literals.
vector<unsigned> frozentab; // Reference counts for frozen variables.
// Regularly checked terminator if non-zero. The terminator is set from
// 'Solver::set (Terminator *)' and checked by 'Internal::terminating ()'.
Terminator * terminator;
// If there is a learner export learned clauses.
Learner * learner;
void export_learned_empty_clause ();
void export_learned_unit_clause (int ilit);
void export_learned_large_clause (const vector<int> &);
//----------------------------------------------------------------------//
signed char * solution; // Given solution checking for debugging.
vector<int> original; // Saved original formula for checking.
// If 'opts.checkfrozen' is set make sure that only literals are added
// which were never completely molten before. These molten literals are
// marked at the beginning of the 'solve' call. Note that variables
// larger than 'max_var' are not molten and can thus always be used in the
// future. Only needed to check and debug old style freeze semantics.
//
vector<bool> moltentab;
//----------------------------------------------------------------------//
const Range vars; // Provides safe variable iterations.
/*==== end of state ====================================================*/
// These two just factor out common sanity (assertion) checking code.
inline int vidx (int elit) const {
assert (elit);
assert (elit != INT_MIN);
int res = abs (elit);
assert (res <= max_var);
return res;
}
inline int vlit (int elit) const {
assert (elit);
assert (elit != INT_MIN);
assert (abs (elit) <= max_var);
return elit;
}
/*----------------------------------------------------------------------*/
// The following five functions push individual literals or clauses on the
// extension stack. They all take internal literals as argument, and map
// them back to external literals first, before pushing them on the stack.
void push_zero_on_extension_stack ();
// Our general version of extension stacks always pushes a set of witness
// literals (for variable elimination the literal of the eliminated
// literal and for blocked clauses the blocking literal) followed by all
// the clause literals starting with and separated by zero.
//
void push_clause_literal_on_extension_stack (int ilit);
void push_witness_literal_on_extension_stack (int ilit);
void push_clause_on_extension_stack (Clause *);
void push_clause_on_extension_stack (Clause *, int witness);
void push_binary_clause_on_extension_stack (int witness, int other);
// The main 'extend' function which extends an internal assignment to an
// external assignment using the extension stack (and sets 'extended').
//
void extend ();
/*----------------------------------------------------------------------*/
// Marking external literals.
unsigned elit2ulit (int elit) const {
assert (elit);
assert (elit != INT_MIN);
const int idx = abs (elit) - 1;
assert (idx <= max_var);
return 2u*idx + (elit < 0);
}
bool marked (const vector<bool> & map, int elit) const {
const unsigned ulit = elit2ulit (elit);
return ulit < map.size () ? map[ulit] : false;
}
void mark (vector<bool> & map, int elit) {
const unsigned ulit = elit2ulit (elit);
while (ulit >= map.size ()) map.push_back (false);
map[ulit] = true;
}
void unmark (vector<bool> & map, int elit) {
const unsigned ulit = elit2ulit (elit);
if (ulit < map.size ()) map[ulit] = false;
}
/*----------------------------------------------------------------------*/
void push_external_clause_and_witness_on_extension_stack (
const vector<int> & clause, const vector<int> & witness);
// Restore a clause, which was pushed on the extension stack.
void restore_clause (
const vector<int>::const_iterator & begin,
const vector<int>::const_iterator & end);
void restore_clauses ();
/*----------------------------------------------------------------------*/
// Explicitly freeze and melt literals (instead of just freezing
// internally and implicitly assumed literals). Passes on freezing and
// melting to the internal solver, which has separate frozen counters.
void freeze (int elit);
void melt (int elit);
bool frozen (int elit) {
assert (elit);
assert (elit != INT_MIN);
int eidx = abs (elit);
if (eidx > max_var) return false;
if (eidx >= (int) frozentab.size ()) return false;
return frozentab[eidx] > 0;
}
/*----------------------------------------------------------------------*/
External (Internal *);
~External ();
void enlarge (int new_max_var); // Enlarge allocated 'vsize'.
void init (int new_max_var); // Initialize up-to 'new_max_var'.
int internalize (int); // Translate external to internal literal.
/*----------------------------------------------------------------------*/
// According to the CaDiCaL API contract (as well as IPASIR) we have to
// forget about the previous assumptions after a 'solve' call. This
// should however be delayed until we transition out of an 'UNSATISFIED'
// state, i.e., after no more 'failed' calls are expected. Note that
// 'failed' requires to know the failing assumptions, and the 'failed'
// status of those should cleared before at start of the next 'solve'.
// As a consequence 'reset_assumptions' is only called from
// 'transition_to_unknown_state' in API calls in 'solver.cpp'.
void reset_assumptions ();
// Similarly a valid external assignment obtained through 'extend' has to
// be reset at each point it risks to become invalid. This is done
// in the external layer in 'external.cpp' functions..
void reset_extended ();
// Finally, the semantics of incremental solving also require that limits
// are only valid for the next 'solve' call. Since the limits can not
// really be queried, handling them is less complex and they are just
// reset immediately at the end of 'External::solve'.
void reset_limits ();
/*----------------------------------------------------------------------*/
// Proxies to IPASIR functions.
void add (int elit);
void assume (int elit);
int solve (bool preprocess_only);
// We call it 'ival' as abbreviation for 'val' with 'int' return type to
// avoid bugs due to using 'signed char tmp = val (lit)', which might turn
// a negative value into a positive one (happened in 'extend').
//
inline int ival (int elit) const {
assert (elit != INT_MIN);
int eidx = abs (elit), res;
if (eidx > max_var) res = -1;
else if ((size_t) eidx >= vals.size ()) res = -1;
else res = vals[eidx] ? eidx : -eidx;
if (elit < 0) res = -res;
return res;
}
bool failed (int elit);
void terminate ();
// Other important non IPASIR functions.
int lookahead();
CaDiCaL::CubesWithStatus generate_cubes(int, int);
int fixed (int elit) const; // Implemented in 'internal.hpp'.
/*----------------------------------------------------------------------*/
void phase (int elit);
void unphase (int elit);
/*----------------------------------------------------------------------*/
// Traversal functions for the witness stack and units. The explanation
// in 'external.cpp' for why we have to distinguish these cases.
bool traverse_all_frozen_units_as_clauses (ClauseIterator &);
bool traverse_all_non_frozen_units_as_witnesses (WitnessIterator &);
bool traverse_witnesses_backward (WitnessIterator &);
bool traverse_witnesses_forward (WitnessIterator &);
/*----------------------------------------------------------------------*/
// Copy flags for determining preprocessing state.
void copy_flags (External & other) const;
/*----------------------------------------------------------------------*/
// Check solver behaves as expected during testing and debugging.
void check_assumptions_satisfied ();
void check_assumptions_failing ();
void check_solution_on_learned_clause ();
void check_solution_on_shrunken_clause (Clause *);
void check_solution_on_learned_unit_clause (int unit);
void check_no_solution_after_learning_empty_clause ();
void check_learned_empty_clause () {
if (solution) check_no_solution_after_learning_empty_clause ();
}
void check_learned_unit_clause (int unit) {
if (solution) check_solution_on_learned_unit_clause (unit);
}
void check_learned_clause () {
if (solution) check_solution_on_learned_clause ();
}
void check_shrunken_clause (Clause * c) {
if (solution) check_solution_on_shrunken_clause (c);
}
void check_assignment (int (External::*assignment) (int) const);
void check_satisfiable ();
void check_unsatisfiable ();
void check_solve_result (int res);
void update_molten_literals ();
/*----------------------------------------------------------------------*/
// For debugging and testing only. See 'solution.hpp' for more details.
//
inline int sol (int elit) const {
assert (solution);
assert (elit != INT_MIN);
int eidx = abs (elit);
if (eidx > max_var) return 0;
int res = solution[eidx];
if (elit < 0) res = -res;
return res;
}
};
}
#endif

306
hCaD_V2/src/file.cpp Normal file
View File

@ -0,0 +1,306 @@
#include "internal.hpp"
/*------------------------------------------------------------------------*/
// Some more low-level 'C' headers.
extern "C" {
#include <string.h>
#include <errno.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
}
/*------------------------------------------------------------------------*/
namespace CaDiCaL {
/*------------------------------------------------------------------------*/
// Private constructor.
File::File (Internal *i, bool w, int c, FILE * f, const char * n)
:
#ifndef QUIET
internal (i),
#endif
#if !defined(QUIET) || !defined(NDEBUG)
writing (w),
#endif
close_file (c), file (f),
_name (n), _lineno (1), _bytes (0)
{
(void) i, (void) w;
assert (f), assert (n);
}
/*------------------------------------------------------------------------*/
bool File::exists (const char * path) {
struct stat buf;
if (stat (path, &buf)) return false;
if (access (path, R_OK)) return false;
return true;
}
bool File::writable (const char * path) {
int res;
if (!path) res = 1;
else if (!strcmp (path, "/dev/null")) res = 0;
else {
if (!*path) res = 2;
else {
struct stat buf;
const char * p = strrchr (path, '/');
if (!p) {
if (stat (path, &buf)) res = ((errno == ENOENT) ? 0 : -2);
else if (S_ISDIR (buf.st_mode)) res = 3;
else res = (access (path, W_OK) ? 4 : 0);
} else if (!p[1]) res = 5;
else {
size_t len = p - path;
char * dirname = new char[len + 1];
strncpy (dirname, path, len);
dirname[len] = 0;
if (stat (dirname, &buf)) res = 6;
else if (!S_ISDIR (buf.st_mode)) res = 7;
else if (access (dirname, W_OK)) res = 8;
else if (stat (path, &buf)) res = (errno == ENOENT) ? 0 : -3;
else res = access (path, W_OK) ? 9 : 0;
delete [] dirname;
}
}
}
return !res;
}
// These are signatures for supported compressed file types. In 2018 the
// SAT Competition was running on StarExec and used internally 'bzip2'
// compressed files, but gave them uncompressed to the solver using exactly
// the same path (with '.bz2' suffix). Then 'CaDiCaL' tried to read that
// actually uncompressed file through 'bzip2', which of course failed. Now
// we double check and fall back to reading the file as is, if the signature
// does not match after issuing a warning.
static int xzsig[] = { 0xFD, 0x37, 0x7A, 0x58, 0x5A, 0x00, 0x00, EOF };
static int bz2sig[] = { 0x42, 0x5A, 0x68, EOF };
static int gzsig[] = { 0x1F, 0x8B, EOF };
static int sig7z[] = { 0x37, 0x7A, 0xBC, 0xAF, 0x27, 0x1C, EOF };
static int lzmasig[] = { 0x5D, 0x00, 0x00, 0x80, 0x00, EOF };
bool File::match (Internal * internal,
const char * path, const int * sig) {
assert (path);
FILE * tmp = fopen (path, "r");
if (!tmp) {
WARNING ("failed to open '%s' to check signature", path);
return false;
}
bool res = true;
for (const int *p = sig; res && (*p != EOF); p++)
res = (cadical_getc_unlocked (tmp) == *p);
fclose (tmp);
if (!res) WARNING ("file type signature check for '%s' failed", path);
return res;
}
size_t File::size (const char * path) {
struct stat buf;
if (stat (path, &buf)) return 0;
return (size_t) buf.st_size;
}
// Check that 'prg' is in the 'PATH' and thus can be found if executed
// through 'popen'.
char * File::find (const char * prg) {
size_t prglen = strlen (prg);
const char * c = getenv ("PATH");
if (!c) return 0;;
size_t len = strlen (c);
char * e = new char[len + 1];
strcpy (e, c);
char * res = 0;
for (char * p = e, * q; !res && p < e + len; p = q) {
for (q = p; *q && *q != ':'; q++)
;
*q++ = 0;
size_t pathlen = (q - p) + prglen;
char * path = new char [pathlen + 1];
sprintf (path, "%s/%s", p, prg);
assert (strlen (path) == pathlen);
if (exists (path)) res = path;
else delete [] path;
}
delete [] e;
return res;
}
/*------------------------------------------------------------------------*/
FILE * File::open_file (Internal * internal, const char * path,
const char * mode) {
(void) internal;
return fopen (path, mode);
}
FILE * File::read_file (Internal * internal, const char * path) {
MSG ("opening file to read '%s'", path);
return open_file (internal, path, "r");
}
FILE * File::write_file (Internal * internal, const char * path) {
MSG ("opening file to write '%s'", path);
return open_file (internal, path, "w");
}
/*------------------------------------------------------------------------*/
FILE * File::open_pipe (Internal * internal,
const char * fmt, const char * path,
const char * mode) {
#ifdef QUIET
(void) internal;
#endif
size_t prglen = 0;
while (fmt[prglen] && fmt[prglen] != ' ') prglen++;
char * prg = new char [prglen + 1];
strncpy (prg, fmt, prglen);
prg[prglen] = 0;
char * found = find (prg);
if (found) MSG ("found '%s' in path for '%s'", found, prg);
if (!found) MSG ("did not find '%s' in path", prg);
delete [] prg;
if (!found) return 0;
delete [] found;
char * cmd = new char [strlen (fmt) + strlen (path)];
sprintf (cmd, fmt, path);
FILE * res = popen (cmd, mode);
delete [] cmd;
return res;
}
FILE * File::read_pipe (Internal * internal,
const char * fmt,
const int * sig,
const char * path) {
if (!File::exists (path)) {
LOG ("file '%s' does not exist", path);
return 0;
}
LOG ("file '%s' exists", path);
if (sig && !File::match (internal, path, sig)) return 0;
LOG ("file '%s' matches signature for '%s'", path, fmt);
MSG ("opening pipe to read '%s'", path);
return open_pipe (internal, fmt, path, "r");
}
FILE * File::write_pipe (Internal * internal,
const char * fmt, const char * path) {
MSG ("opening pipe to write '%s'", path);
return open_pipe (internal, fmt, path, "w");
}
/*------------------------------------------------------------------------*/
File * File::read (Internal * internal, FILE * f, const char * n) {
return new File (internal, false, 0, f, n);
}
File * File::write (Internal * internal, FILE * f, const char * n) {
return new File (internal, true, 0, f, n);
}
File * File::read (Internal * internal, const char * path) {
FILE * file;
int close_input = 2;
if (has_suffix (path, ".xz")) {
file = read_pipe (internal, "xz -c -d %s", xzsig, path);
if (!file) goto READ_FILE;
} else if (has_suffix (path, ".lzma")) {
file = read_pipe (internal, "lzma -c -d %s", lzmasig, path);
if (!file) goto READ_FILE;
} else if (has_suffix (path, ".bz2")) {
file = read_pipe (internal, "bzip2 -c -d %s", bz2sig, path);
if (!file) goto READ_FILE;
} else if (has_suffix (path, ".gz")) {
file = read_pipe (internal, "gzip -c -d %s", gzsig, path);
if (!file) goto READ_FILE;
} else if (has_suffix (path, ".7z")) {
file = read_pipe (internal, "7z x -so %s 2>/dev/null", sig7z, path);
if (!file) goto READ_FILE;
} else {
READ_FILE:
file = read_file (internal, path);
close_input = 1;
}
return file ? new File (internal, false, close_input, file, path) : 0;
}
File * File::write (Internal * internal, const char * path) {
FILE * file;
int close_input = 2;
if (has_suffix (path, ".xz"))
file = write_pipe (internal, "xz -c > %s", path);
else if (has_suffix (path, ".bz2"))
file = write_pipe (internal, "bzip2 -c > %s", path);
else if (has_suffix (path, ".gz"))
file = write_pipe (internal, "gzip -c > %s", path);
else if (has_suffix (path, ".7z"))
file = write_pipe (internal,
"7z a -an -txz -si -so > %s 2>/dev/null", path);
else
file = write_file (internal, path), close_input = 1;
return file ? new File (internal, true, close_input, file, path) : 0;
}
void File::close () {
assert (file);
if (close_file == 0) {
MSG ("disconnecting from '%s'", name ());
}
if (close_file == 1) {
MSG ("closing file '%s'", name ());
fclose (file);
}
if (close_file == 2) {
MSG ("closing pipe command on '%s'", name ());
pclose (file);
}
file = 0; // mark as closed
#ifndef QUIET
if (internal->opts.verbose > 1) return;
double mb = bytes () / (double) (1 << 20);
if (writing)
MSG ("after writing %" PRIu64 " bytes %.1f MB", bytes (), mb);
else
MSG ("after reading %" PRIu64 " bytes %.1f MB", bytes (), mb);
if (close_file == 2) {
int64_t s = size (name ());
double mb = s / (double) (1<<20);
if (writing)
MSG ("deflated to %" PRId64 " bytes %.1f MB by factor %.2f "
"(%.2f%% compression)",
s, mb, relative (bytes (), s), percent (bytes () - s, bytes ()));
else
MSG ("inflated from %" PRId64 " bytes %.1f MB by factor %.2f "
"(%.2f%% compression)",
s, mb, relative (bytes (), s), percent (bytes () - s, bytes ()));
}
#endif
}
void File::flush () {
assert (file);
fflush (file);
}
File::~File () { if (file) close (); }
}

177
hCaD_V2/src/file.hpp Normal file
View File

@ -0,0 +1,177 @@
#ifndef _file_hpp_INCLUDED
#define _file_hpp_INCLUDED
#include <cstdio>
#include <cassert>
#include <cstdlib>
#ifndef NDEBUG
#include <climits>
#endif
/*------------------------------------------------------------------------*/
#ifndef NUNLOCKED
#define cadical_putc_unlocked putc_unlocked
#define cadical_getc_unlocked getc_unlocked
#else
#define cadical_putc_unlocked putc
#define cadical_getc_unlocked getc
#endif
/*------------------------------------------------------------------------*/
namespace CaDiCaL {
// Wraps a 'C' file 'FILE' with name and supports zipped reading and writing
// through 'popen' using external helper tools. Reading has line numbers.
// Compression and decompression relies on external utilities, e.g., 'gzip',
// 'bzip2', 'xz', and '7z', which should be in the 'PATH'.
struct Internal;
class File {
#ifndef QUIET
Internal * internal;
#endif
#if !defined(QUIET) || !defined(NDEBUG)
bool writing;
#endif
int close_file; // need to close file (1=fclose, 2=pclose)
FILE * file;
const char * _name;
uint64_t _lineno;
uint64_t _bytes;
File (Internal *, bool, int, FILE *, const char *);
static FILE * open_file (Internal *,
const char * path, const char * mode);
static FILE * read_file (Internal *, const char * path);
static FILE * write_file (Internal *, const char * path);
static FILE * open_pipe (Internal *,
const char * fmt,
const char * path,
const char * mode);
static FILE * read_pipe (Internal *,
const char * fmt,
const int * sig,
const char * path);
static FILE * write_pipe (Internal *,
const char * fmt, const char * path);
public:
static char* find (const char * prg); // search in 'PATH'
static bool exists (const char * path); // file exists?
static bool writable (const char * path);// can write to that file?
static size_t size (const char * path); // file size in bytes
// Does the file match the file type signature.
//
static bool match (Internal *, const char * path, const int * sig);
// Read from existing file. Assume given name.
//
static File * read (Internal *, FILE * f, const char * name);
// Open file from path name for reading (possibly through opening a pipe
// to a decompression utility, based on the suffix).
//
static File * read (Internal *, const char * path);
// Same for writing as for reading above.
//
static File * write (Internal *, FILE *, const char * name);
static File * write (Internal *, const char * path);
~File ();
// Using the 'unlocked' versions here is way faster but
// not thread safe if the same file is used by different
// threads, which on the other hand currently is impossible.
int get () {
assert (!writing);
int res = cadical_getc_unlocked (file);
if (res == '\n') _lineno++;
if (res != EOF) _bytes++;
return res;
}
bool put (char ch) {
assert (writing);
if (cadical_putc_unlocked (ch, file) == EOF) return false;
_bytes++;
return true;
}
bool put (unsigned char ch) {
assert (writing);
if (cadical_putc_unlocked (ch, file) == EOF) return false;
_bytes++;
return true;
}
bool put (const char * s) {
for (const char * p = s; *p; p++)
if (!put (*p)) return false;
return true;
}
bool put (int lit) {
assert (writing);
if (!lit) return put ('0');
else if (lit == -2147483648) {
assert (lit == INT_MIN);
return put ("-2147483648");
} else {
char buffer[11];
int i = sizeof buffer;
buffer[--i] = 0;
assert (lit != INT_MIN);
unsigned idx = abs (lit);
while (idx) {
assert (i > 0);
buffer[--i] = '0' + idx % 10;
idx /= 10;
}
if (lit < 0 && !put ('-')) return false;
return put (buffer + i);
}
}
bool put (int64_t l) {
assert (writing);
if (!l) return put ('0');
else if (l == INT64_MIN) {
assert (sizeof l == 8);
return put ("-9223372036854775808");
} else {
char buffer[21];
int i = sizeof buffer;
buffer[--i] = 0;
assert (l != INT64_MIN);
uint64_t k = l < 0 ? -l : l;
while (k) {
assert (i > 0);
buffer[--i] = '0' + k % 10;
k /= 10;
}
if (l < 0 && !put ('-')) return false;
return put (buffer + i);
}
}
const char * name () const { return _name; }
uint64_t lineno () const { return _lineno; }
uint64_t bytes () const { return _bytes; }
bool closed () { return !file; }
void close ();
void flush ();
};
}
#endif

119
hCaD_V2/src/flags.cpp Normal file
View File

@ -0,0 +1,119 @@
#include "internal.hpp"
namespace CaDiCaL {
void Internal::mark_fixed (int lit) {
Flags & f = flags (lit);
assert (f.status == Flags::ACTIVE);
f.status = Flags::FIXED;
LOG ("fixed %d", abs (lit));
stats.all.fixed++;
stats.now.fixed++;
stats.inactive++;
assert (stats.active);
stats.active--;
assert (!active (lit));
assert (f.fixed ());
}
void Internal::mark_eliminated (int lit) {
Flags & f = flags (lit);
assert (f.status == Flags::ACTIVE);
f.status = Flags::ELIMINATED;
LOG ("eliminated %d", abs (lit));
stats.all.eliminated++;
stats.now.eliminated++;
stats.inactive++;
assert (stats.active);
stats.active--;
assert (!active (lit));
assert (f.eliminated ());
}
void Internal::mark_pure (int lit) {
Flags & f = flags (lit);
assert (f.status == Flags::ACTIVE);
f.status = Flags::PURE;
LOG ("pure %d", abs (lit));
stats.all.pure++;
stats.now.pure++;
stats.inactive++;
assert (stats.active);
stats.active--;
assert (!active (lit));
assert (f.pure ());
}
void Internal::mark_substituted (int lit) {
Flags & f = flags (lit);
assert (f.status == Flags::ACTIVE);
f.status = Flags::SUBSTITUTED;
LOG ("substituted %d", abs (lit));
stats.all.substituted++;
stats.now.substituted++;
stats.inactive++;
assert (stats.active);
stats.active--;
assert (!active (lit));
assert (f.substituted ());
}
void Internal::mark_active (int lit) {
Flags & f = flags (lit);
assert (f.status == Flags::UNUSED);
f.status = Flags::ACTIVE;
LOG ("activate %d previously unused", abs (lit));
assert (stats.inactive);
stats.inactive--;
assert (stats.unused);
stats.unused--;
stats.active++;
assert (active (lit));
}
void Internal::reactivate (int lit) {
assert (!active (lit));
Flags & f = flags (lit);
assert (f.status != Flags::FIXED);
assert (f.status != Flags::UNUSED);
#ifdef LOGGING
const char * msg = 0;
#endif
switch (f.status) {
default:
case Flags::ELIMINATED:
assert (f.status == Flags::ELIMINATED);
assert (stats.now.eliminated > 0);
stats.now.eliminated--;
#ifdef LOGGING
msg = "eliminated";
#endif
break;
case Flags::SUBSTITUTED:
#ifdef LOGGING
msg = "substituted";
#endif
assert (stats.now.substituted > 0);
stats.now.substituted--;
break;
case Flags::PURE:
#ifdef LOGGING
msg = "pure literal";
#endif
assert (stats.now.pure > 0);
stats.now.pure--;
break;
}
#ifdef LOGGING
assert (msg);
LOG ("reactivate previously %s %d", msg, abs (lit));
#endif
f.status = Flags::ACTIVE;
assert (active (lit));
stats.reactivated++;
assert (stats.inactive > 0);
stats.inactive--;
stats.active++;
}
}

76
hCaD_V2/src/flags.hpp Normal file
View File

@ -0,0 +1,76 @@
#ifndef _flags_hpp_INCLUDED
#define _flags_hpp_INCLUDED
namespace CaDiCaL {
struct Flags { // Variable flags.
// The first set of flags is related to 'analyze' and 'minimize'.
//
bool seen : 1; // seen in generating first UIP clause in 'analyze'
bool keep : 1; // keep in learned clause in 'minimize'
bool poison : 1; // can not be removed in 'minimize'
bool removable : 1; // can be removed in 'minimize'
bool shrinkable : 1; // can be removed in 'shrink'
// These three variable flags are used to schedule clauses in subsumption
// ('subsume'), variables in bounded variable elimination ('elim') and in
// hyper ternary resolution ('ternary').
//
bool elim : 1; // removed since last 'elim' round (*)
bool subsume : 1; // added since last 'subsume' round (*)
bool ternary : 1; // added in ternary clause since last 'ternary' (*)
// These literal flags are used by blocked clause elimination ('block').
//
unsigned char block : 2; // removed since last 'block' round (*)
unsigned char skip : 2; // skip this literal as blocking literal
// Bits for handling assumptions.
//
unsigned char assumed : 2;
unsigned char failed : 2;
enum {
UNUSED = 0,
ACTIVE = 1,
FIXED = 2,
ELIMINATED = 3,
SUBSTITUTED = 4,
PURE = 5
};
unsigned char status : 3;
// Initialized explicitly in 'Internal::init' through this function.
//
Flags () {
seen = keep = poison = removable = shrinkable = false;
subsume = elim = ternary = true;
block = 3u;
skip = assumed = failed = 0;
status = UNUSED;
}
bool unused () const { return status == UNUSED; }
bool active () const { return status == ACTIVE; }
bool fixed () const { return status == FIXED; }
bool eliminated () const { return status == ELIMINATED; }
bool substituted () const { return status == SUBSTITUTED; }
bool pure () const { return status == PURE; }
// The flags marked with '(*)' are copied during 'External::copy_flags',
// which in essence means they are reset in the copy if they were clear.
// This avoids the effort of fruitless preprocessing the copy.
void copy (Flags & dst) const {
dst.elim = elim;
dst.subsume = subsume;
dst.ternary = ternary;
dst.block = block;
}
};
}
#endif

81
hCaD_V2/src/format.cpp Normal file
View File

@ -0,0 +1,81 @@
#include "internal.hpp"
namespace CaDiCaL {
void Format::enlarge () {
char * old = buffer;
buffer = new char[size = size ? 2*size : 1];
memcpy (buffer, old, count);
delete [] old;
}
inline void Format::push_char (char ch) {
if (size == count) enlarge ();
buffer[count++] = ch;
}
void Format::push_string (const char * s) {
char ch;
while ((ch = *s++)) push_char (ch);
}
void Format::push_int (int d) {
char tmp[12];
sprintf (tmp, "%d", d);
push_string (tmp);
}
void Format::push_uint64 (uint64_t u) {
char tmp[12];
sprintf (tmp, "%" PRIu64, u);
push_string (tmp);
}
static bool
match_format (const char * & str, const char * pattern)
{
assert (pattern);
const char * p = str;
const char * q = pattern;
while (*q)
if (*q++ != *p++)
return false;
str = p;
return true;
}
const char * Format::add (const char * fmt, va_list & ap) {
const char * p = fmt;
char ch;
while ((ch = *p++)) {
if (ch != '%') push_char (ch);
else if (*p == 'c') push_char (va_arg (ap, int)), p++;
else if (*p == 'd') push_int (va_arg (ap, int)), p++;
else if (*p == 's') push_string (va_arg (ap, const char*)), p++;
else if (match_format (p, PRIu64))
push_uint64 (va_arg (ap, uint64_t));
else { push_char ('%'); push_char (*p); break; } // unsupported
}
push_char (0);
count--; // thus automatic append in subsequent calls.
return buffer;
}
const char * Format::init (const char * fmt, ...) {
count = 0;
va_list ap;
va_start (ap, fmt);
const char * res = add (fmt, ap);
va_end (ap);
return res;
}
const char * Format::append (const char * fmt, ...) {
va_list ap;
va_start (ap, fmt);
const char * res = add (fmt, ap);
va_end (ap);
return res;
}
}

33
hCaD_V2/src/format.hpp Normal file
View File

@ -0,0 +1,33 @@
#ifndef _format_hpp_INCLUDED
#define _format_hpp_INCLUDED
#include <cstdarg>
namespace CaDiCaL {
// This class provides a 'printf' style formatting utility.
// Only '%c', '%d', '%s' are supported at this point.
// It is used to capture and save an error message.
class Format {
char * buffer;
int64_t count, size;
void enlarge ();
void push_char (char);
void push_string (const char *);
void push_int (int);
void push_uint64 (uint64_t);
const char * add (const char * fmt, va_list &);
public:
Format () : buffer (0), count (0), size (0) { }
~Format () { if (buffer) delete [] buffer; }
const char * init (const char * fmt, ...)
CADICAL_ATTRIBUTE_FORMAT (2, 3);
const char * append (const char * fmt, ...)
CADICAL_ATTRIBUTE_FORMAT (2, 3);
operator const char * () const { return count ? buffer : 0; }
};
}
#endif

527
hCaD_V2/src/gates.cpp Normal file
View File

@ -0,0 +1,527 @@
#include "internal.hpp"
namespace CaDiCaL {
/*------------------------------------------------------------------------*/
// As in our original SATeLite published at SAT'05 we are trying to find
// gates in order to restrict the number of resolutions that need to be
// tried. If there is such a gate, we only need to consider resolvents
// among gate and one non-gate clauses. Resolvents between definitions will
// be tautological anyhow and resolvents among non-gates can actually be
// shown to be redundant too.
/*------------------------------------------------------------------------*/
// The next function returns a non-zero if the clause 'c', which is assumed
// to contain the literal 'first', after removing falsified literals is a
// binary clause. Then the actual second literal is returned.
int
Internal::second_literal_in_binary_clause (Eliminator & eliminator,
Clause * c, int first)
{
assert (!c->garbage);
int second = 0;
for (const auto & lit : *c) {
if (lit == first) continue;
const signed char tmp = val (lit);
if (tmp < 0) continue;
if (tmp > 0) {
mark_garbage (c);
elim_update_removed_clause (eliminator, c);
return 0;
}
if (second) { second = INT_MIN; break; }
second = lit;
}
if (!second) return 0;
if (second == INT_MIN) return 0;
assert (active (second));
#ifdef LOGGING
if (c->size == 2) LOG (c, "found binary");
else LOG (c, "found actual binary %d %d", first, second);
#endif
return second;
}
/*------------------------------------------------------------------------*/
// Mark all other literals in binary clauses with 'first'. During this
// marking we might also detect hyper unary resolvents producing a unit.
// If such a unit is found we propagate it and return immediately.
void Internal::mark_binary_literals (Eliminator & eliminator, int first) {
if (unsat) return;
if (val (first)) return;
if (!eliminator.gates.empty ()) return;
assert (!marked (first));
assert (eliminator.marked.empty ());
const Occs & os = occs (first);
for (const auto & c : os) {
if (c->garbage) continue;
const int second =
second_literal_in_binary_clause (eliminator, c, first);
if (!second) continue;
const int tmp = marked (second);
if (tmp < 0) {
LOG ("found binary resolved unit %d", first);
assign_unit (first);
elim_propagate (eliminator, first);
return;
}
if (tmp > 0) {
LOG (c, "duplicated actual binary clause");
elim_update_removed_clause (eliminator, c);
mark_garbage (c);
continue;
}
eliminator.marked.push_back (second);
mark (second);
LOG ("marked second literal %d in binary clause %d %d",
second, first, second);
}
}
// Unmark all literals saved on the 'marked' stack.
void Internal::unmark_binary_literals (Eliminator & eliminator) {
LOG ("unmarking %zd literals", eliminator.marked.size ());
for (const auto & lit : eliminator.marked)
unmark (lit);
eliminator.marked.clear ();
}
/*------------------------------------------------------------------------*/
// Find equivalence for 'pivot'. Requires that all other literals in binary
// clauses with 'pivot' are marked (through 'mark_binary_literals');
void Internal::find_equivalence (Eliminator & eliminator, int pivot) {
if (!opts.elimequivs) return;
assert (opts.elimsubst);
if (unsat) return;
if (val (pivot)) return;
if (!eliminator.gates.empty ()) return;
mark_binary_literals (eliminator, pivot);
if (unsat || val (pivot)) goto DONE;
for (const auto & c : occs (-pivot)) {
if (c->garbage) continue;
const int second =
second_literal_in_binary_clause (eliminator, c, -pivot);
if (!second) continue;
const int tmp = marked (second);
if (tmp > 0) {
LOG ("found binary resolved unit %d", second);
assign_unit (second);
elim_propagate (eliminator, second);
if (val (pivot)) break;
if (unsat) break;
}
if (tmp >= 0) continue;
LOG ("found equivalence %d = %d", pivot, -second);
stats.elimequivs++;
stats.elimgates++;
LOG (c, "first gate clause");
assert (!c->gate);
c->gate = true;
eliminator.gates.push_back (c);
Clause * d = 0;
const Occs & ps = occs (pivot);
for (const auto & e : ps) {
if (e->garbage) continue;
const int other =
second_literal_in_binary_clause (eliminator, e, pivot);
if (other == -second) { d = e; break; }
}
assert (d);
LOG (d, "second gate clause");
assert (!d->gate);
d->gate = true;
eliminator.gates.push_back (d);
break;
}
DONE:
unmark_binary_literals (eliminator);
}
/*------------------------------------------------------------------------*/
// Find and gates for 'pivot' with a long clause, in which the pivot occurs
// positively. Requires that all other literals in binary clauses with
// 'pivot' are marked (through 'mark_binary_literals');
void Internal::find_and_gate (Eliminator & eliminator, int pivot) {
if (!opts.elimands) return;
assert (opts.elimsubst);
if (unsat) return;
if (val (pivot)) return;
if (!eliminator.gates.empty ()) return;
mark_binary_literals (eliminator, pivot);
if (unsat || val (pivot)) goto DONE;
for (const auto & c : occs (-pivot)) {
if (c->garbage) continue;
if (c->size < 3) continue;
bool all_literals_marked = true;
unsigned arity = 0;
for (const auto & lit : *c) {
if (lit == -pivot) continue;
assert (lit != pivot);
signed char tmp = val (lit);
if (tmp < 0) continue;
assert (!tmp);
tmp = marked (lit);
if (tmp < 0) { arity++; continue; }
all_literals_marked = false;
break;
}
if (!all_literals_marked) continue;
#ifdef LOGGING
if (opts.log) {
Logger::print_log_prefix (this);
tout.magenta ();
printf ("found arity %u AND gate %d = ", arity, -pivot);
bool first = true;
for (const auto & lit : *c) {
if (lit == -pivot) continue;
assert (lit != pivot);
if (!first) fputs (" & ", stdout);
printf ("%d", -lit);
first = false;
}
fputc ('\n', stdout);
tout.normal ();
fflush (stdout);
}
#endif
stats.elimands++;
stats.elimgates++;
(void) arity;
assert (!c->gate);
c->gate = true;
eliminator.gates.push_back (c);
for (const auto & lit : *c) {
if (lit == -pivot) continue;
assert (lit != pivot);
signed char tmp = val (lit);
if (tmp < 0) continue;
assert (!tmp);
assert (marked (lit) < 0);
marks [vidx (lit)] *= 2;
}
unsigned count = 0;
for (const auto & d : occs (pivot)) {
if (d->garbage) continue;
const int other =
second_literal_in_binary_clause (eliminator, d, pivot);
if (!other) continue;
const int tmp = marked (other);
if (tmp != 2) continue;
LOG (d, "AND gate binary side clause");
assert (!d->gate);
d->gate = true;
eliminator.gates.push_back (d);
count++;
}
assert (count >= arity);
(void) count;
break;
}
DONE:
unmark_binary_literals (eliminator);
}
/*------------------------------------------------------------------------*/
// Find and extract ternary clauses.
bool Internal::get_ternary_clause (Clause * d, int & a, int & b, int & c)
{
if (d->garbage) return false;
if (d->size < 3) return false;
int found = 0;
a = b = c = 0;
for (const auto & lit : *d) {
if (val (lit)) continue;
if (++found == 1) a = lit;
else if (found == 2) b = lit;
else if (found == 3) c = lit;
else return false;
}
return found == 3;
}
// This function checks whether 'd' exists as ternary clause.
bool Internal::match_ternary_clause (Clause * d, int a, int b, int c) {
if (d->garbage) return false;
int found = 0;
for (const auto & lit : *d) {
if (val (lit)) continue;
if (a != lit && b != lit && c != lit) return false;
found++;
}
return found == 3;
}
Clause *
Internal::find_ternary_clause (int a, int b, int c) {
if (occs (b).size () > occs (c).size ()) swap (b, c);
if (occs (a).size () > occs (b).size ()) swap (a, b);
for (auto d : occs (a))
if (match_ternary_clause (d, a, b, c))
return d;
return 0;
}
/*------------------------------------------------------------------------*/
// Find if-then-else gate.
void Internal::find_if_then_else (Eliminator & eliminator, int pivot) {
if (!opts.elimites) return;
assert (opts.elimsubst);
if (unsat) return;
if (val (pivot)) return;
if (!eliminator.gates.empty ()) return;
const Occs & os = occs (pivot);
const auto end = os.end ();
for (auto i = os.begin (); i != end; i++) {
Clause * di = *i;
int ai, bi, ci;
if (!get_ternary_clause (di, ai, bi, ci)) continue;
if (bi == pivot) swap (ai, bi);
if (ci == pivot) swap (ai, ci);
assert (ai == pivot);
for (auto j = i + 1; j != end; j++) {
Clause * dj = *j;
int aj, bj, cj;
if (!get_ternary_clause (dj, aj, bj, cj)) continue;
if (bj == pivot) swap (aj, bj);
if (cj == pivot) swap (aj, cj);
assert (aj == pivot);
if (abs (bi) == abs (cj)) swap (bj, cj);
if (abs (ci) == abs (cj)) continue;
if (bi != -bj) continue;
Clause * d1 = find_ternary_clause (-pivot, bi, -ci);
if (!d1) continue;
Clause * d2 = find_ternary_clause (-pivot, bj, -cj);
if (!d2) continue;
LOG (di, "1st if-then-else");
LOG (dj, "2nd if-then-else");
LOG (d1, "3rd if-then-else");
LOG (d2, "4th if-then-else");
LOG ("found ITE gate %d == (%d ? %d : %d)", pivot, -bi, -ci, -cj);
assert (!di->gate);
assert (!dj->gate);
assert (!d1->gate);
assert (!d2->gate);
di->gate = true;
dj->gate = true;
d1->gate = true;
d2->gate = true;
eliminator.gates.push_back (di);
eliminator.gates.push_back (dj);
eliminator.gates.push_back (d1);
eliminator.gates.push_back (d2);
stats.elimgates++;
stats.elimites++;
return;
}
}
}
/*------------------------------------------------------------------------*/
// Find and extract clause.
bool Internal::get_clause (Clause * c, vector<int> & l) {
if (c->garbage) return false;
l.clear ();
for (const auto & lit : *c) {
if (val (lit)) continue;
l.push_back (lit);
}
return true;
}
// Check whether 'c' contains only the literals in 'l'.
bool Internal::is_clause (Clause * c, const vector<int> & lits) {
if (c->garbage) return false;
int size = lits.size ();
if (c->size < size) return false;
int found = 0;
for (const auto & lit : *c) {
if (val (lit)) continue;
const auto it = find (lits.begin (), lits.end (), lit);
if (it == lits.end ()) return false;
if (++found > size) return false;
}
return found == size;
}
Clause * Internal::find_clause (const vector<int> & lits) {
int best = 0;
size_t len = 0;
for (const auto & lit : lits) {
size_t l = occs (lit).size ();
if (best && l >= len) continue;
len = l, best = lit;
}
for (auto c : occs (best))
if (is_clause (c, lits))
return c;
return 0;
}
void Internal::find_xor_gate (Eliminator & eliminator, int pivot) {
if (!opts.elimxors) return;
assert (opts.elimsubst);
if (unsat) return;
if (val (pivot)) return;
if (!eliminator.gates.empty ()) return;
vector<int> lits;
for (auto d : occs (pivot)) {
if (!get_clause (d, lits)) continue;
const int size = lits.size (); // clause size
const int arity = size - 1; // arity of XOR
if (size < 3) continue;
if (arity > opts.elimxorlim) continue;
assert (eliminator.gates.empty ());
unsigned needed = (1u << arity) - 1; // additional clauses
unsigned signs = 0; // literals to negate
do {
const unsigned prev = signs;
while (parity (++signs))
;
for (int j = 0; j < size; j++) {
const unsigned bit = 1u << j;
int lit = lits[j];
if ((prev & bit) != (signs & bit))
lits[j] = lit = -lit;
}
Clause * e = find_clause (lits);
if (!e) break;
eliminator.gates.push_back (e);
} while (--needed);
if (needed) { eliminator.gates.clear (); continue; }
eliminator.gates.push_back (d);
assert (eliminator.gates.size () == (1u << arity));
#ifdef LOGGING
if (opts.log) {
Logger::print_log_prefix (this);
tout.magenta ();
printf ("found arity %u XOR gate %d = ", arity, -pivot);
bool first = true;
for (const auto & lit : *d) {
if (lit == pivot) continue;
assert (lit != -pivot);
if (!first) fputs (" ^ ", stdout);
printf ("%d", lit);
first = false;
}
fputc ('\n', stdout);
tout.normal ();
fflush (stdout);
}
#endif
stats.elimgates++;
stats.elimxors++;
const auto end = eliminator.gates.end ();
auto j = eliminator.gates.begin ();
for (auto i = j; i != end; i++) {
Clause * e = *i;
if (e->gate) continue;
e->gate = true;
LOG (e, "contributing");
*j++ = e;
}
eliminator.gates.resize (j - eliminator.gates.begin ());
break;
}
}
/*------------------------------------------------------------------------*/
// Find a gate for 'pivot'. If such a gate is found, the gate clauses are
// marked and pushed on the stack of gates. Further hyper unary resolution
// might detect units, which are propagated. This might assign the pivot or
// even produce the empty clause.
void Internal::find_gate_clauses (Eliminator & eliminator, int pivot)
{
if (!opts.elimsubst) return;
if (unsat) return;
if (val (pivot)) return;
assert (eliminator.gates.empty ());
find_equivalence (eliminator, pivot);
find_and_gate (eliminator, pivot);
find_and_gate (eliminator, -pivot);
find_if_then_else (eliminator, pivot);
find_xor_gate (eliminator, pivot);
}
void Internal::unmark_gate_clauses (Eliminator & eliminator) {
LOG ("unmarking %zd gate clauses", eliminator.gates.size ());
for (const auto & c : eliminator.gates) {
assert (c->gate);
c->gate = false;
}
eliminator.gates.clear ();
}
/*------------------------------------------------------------------------*/
}

198
hCaD_V2/src/heap.hpp Normal file
View File

@ -0,0 +1,198 @@
#ifndef _heap_hpp_INCLUDED
#define _heap_hpp_INCLUDED
#include "util.hpp" // Alphabetically after 'heap.hpp'.
namespace CaDiCaL {
using namespace std;
// This is a priority queue with updates for unsigned integers implemented
// as binary heap. We need to map integer elements added (through
// 'push_back') to positions on the binary heap in 'array'. This map is
// stored in the 'pos' array. This approach is really wasteful (at least in
// terms of memory) if only few and a sparse set of integers is added. So
// it should not be used in this situation. A generic priority queue would
// implement the mapping externally provided by another template parameter.
// Since we use 'UINT_MAX' as 'not contained' flag, we can only have
// 'UINT_MAX - 1' elements in the heap.
const unsigned invalid_heap_position = UINT_MAX;
template<class C> class heap {
vector<unsigned> array; // actual binary heap
vector<unsigned> pos; // positions of elements in array
C less; // less-than for elements
// Map an element to its position entry in the 'pos' map.
//
unsigned & index (unsigned e) {
while ((size_t) e >= pos.size ()) pos.push_back (invalid_heap_position);
unsigned & res = pos[e];
assert (res == invalid_heap_position || (size_t) res < array.size ());
return res;
}
bool has_parent (unsigned e) { return index (e) > 0; }
bool has_left (unsigned e) { return (size_t) 2*index (e) + 1 < size (); }
bool has_right (unsigned e) { return (size_t) 2*index (e) + 2 < size (); }
unsigned parent (unsigned e) {
assert(has_parent (e));
return array[(index(e)-1)/2];
}
unsigned left (unsigned e) {
assert(has_left (e));
return array[2*index(e)+1];
}
unsigned right (unsigned e) {
assert(has_right (e));
return array[2*index(e)+2];
}
// Exchange elements 'a' and 'b' in 'array' and fix their positions.
//
void exchange (unsigned a, unsigned b) {
unsigned & i = index (a), & j = index (b);
swap (array[i], array[j]);
swap (i, j);
}
// Bubble up an element as far as necessary.
//
void up (unsigned e) {
unsigned p;
while (has_parent (e) && less ((p = parent (e)), e))
exchange (p, e);
}
// Bubble down an element as far as necessary.
//
void down (unsigned e) {
while (has_left (e)) {
unsigned c = left (e);
if (has_right (e)) {
unsigned r = right (e);
if (less (c, r)) c = r;
}
if (!less (e, c)) break;
exchange (e, c);
}
}
// Very expensive checker for the main 'heap' invariant. Can be enabled
// to find violations of antisymmetry in the client implementation of
// 'less' and as well of course bugs in this heap implementation. It
// should be enabled during testing applications of the heap.
//
void check () {
#if 0 // EXPENSIVE HEAP CHECKING IF ENABLED
#warning "expensive checking in heap enabled"
assert (array.size () <= invalid_heap_position);
for (size_t i = 0; i < array.size (); i++) {
size_t l = 2*i + 1, r = 2*i + 2;
if (l < array.size ()) assert (!less (array[i], array[l]));
if (r < array.size ()) assert (!less (array[i], array[r]));
assert (array[i] >= 0);
{
assert ((size_t) array[i] < pos.size ());
assert (i == (size_t) pos[array[i]]);
}
}
for (size_t i = 0; i < pos.size (); i++) {
if (pos[i] == invalid_heap_position) continue;
assert (pos[i] < array.size ());
assert (array[pos[i]] == (unsigned) i);
}
#endif
}
public:
heap (const C & c) : less (c) { }
// Number of elements in the heap.
//
size_t size () const { return array.size (); }
// Check if no more elements are in the heap.
//
bool empty () const { return array.empty (); }
// Check whether 'e' is already in the heap.
//
bool contains (unsigned e) const {
if ((size_t) e >= pos.size ()) return false;
return pos[e] != invalid_heap_position;
}
// Add a new (not contained) element 'e' to the heap.
//
void push_back (unsigned e) {
assert (!contains (e));
size_t i = array.size ();
assert (i < (size_t) invalid_heap_position);
array.push_back (e);
index (e) = (unsigned) i;
up (e);
down (e);
check ();
}
// Returns the maximum element in the heap.
//
unsigned front () const { assert (!empty ()); return array[0]; }
// Removes the maximum element in the heap.
//
unsigned pop_front () {
assert (!empty ());
unsigned res = array[0], last = array.back ();
if (size () > 1) exchange (res, last);
index (res) = invalid_heap_position;
array.pop_back ();
if (size () > 1) down (last);
check ();
return res;
}
// Notify the heap, that evaluation of 'less' has changed for 'e'.
//
void update (unsigned e) {
assert (contains (e));
up (e);
down (e);
check ();
}
void clear () {
array.clear ();
pos.clear ();
}
void erase () {
erase_vector (array);
erase_vector (pos);
}
void shrink () {
shrink_vector (array);
shrink_vector (pos);
}
// Standard iterators 'inherited' from 'vector'.
//
typedef typename vector<unsigned>::iterator iterator;
typedef typename vector<unsigned>::const_iterator const_iterator;
iterator begin () { return array.begin (); }
iterator end () { return array.end (); }
const_iterator begin () const { return array.begin (); }
const_iterator end () const { return array.end (); }
};
}
#endif

241
hCaD_V2/src/instantiate.cpp Normal file
View File

@ -0,0 +1,241 @@
#include "internal.hpp"
namespace CaDiCaL {
/*------------------------------------------------------------------------*/
// This provides an implementation of variable instantiation, a technique
// for removing literals with few occurrence (see also 'instantiate.hpp').
/*------------------------------------------------------------------------*/
// Triggered at the end of a variable elimination round ('elim_round').
void
Internal::collect_instantiation_candidates (Instantiator & instantiator) {
assert (occurring ());
for (auto idx : vars) {
if (frozen (idx)) continue;
if (!active (idx)) continue;
if (flags (idx).elim) continue; // BVE attempt pending
for (int sign = -1; sign <= 1; sign += 2) {
const int lit = sign * idx;
if (noccs (lit) > opts.instantiateocclim) continue;
Occs & os = occs (lit);
for (const auto & c : os) {
if (c->garbage) continue;
if (opts.instantiateonce && c->instantiated) continue;
if (c->size < opts.instantiateclslim) continue;
bool satisfied = false;
int unassigned = 0;
for (const auto & other : *c) {
const signed char tmp = val (other);
if (tmp > 0) satisfied = true;
if (!tmp) unassigned++;
}
if (satisfied) continue;
if (unassigned < 3) continue; // avoid learning units
size_t negoccs = occs (-lit).size ();
LOG (c,
"instantiation candidate literal %d "
"with %zu negative occurrences in", lit, negoccs);
instantiator.candidate (lit, c, c->size, negoccs);
}
}
}
}
/*------------------------------------------------------------------------*/
// Specialized propagation and assignment routines for instantiation.
inline void Internal::inst_assign (int lit) {
LOG ("instantiate assign %d", lit);
assert (!val (lit));
vals[lit] = 1;
vals[-lit] = -1;
trail.push_back (lit);
}
bool Internal::inst_propagate () { // Adapted from 'propagate'.
START (propagate);
int64_t before = propagated;
bool ok = true;
while (ok && propagated != trail.size ()) {
const int lit = -trail[propagated++];
LOG ("instantiate propagating %d", -lit);
Watches & ws = watches (lit);
const const_watch_iterator eow = ws.end ();
const_watch_iterator i = ws.begin ();
watch_iterator j = ws.begin ();
while (i != eow) {
const Watch w = *j++ = *i++;
const signed char b = val (w.blit);
if (b > 0) continue;
if (w.binary ()) {
if (b < 0) { ok = false; LOG (w.clause, "conflict"); break; }
else inst_assign (w.blit);
} else {
literal_iterator lits = w.clause->begin ();
const int other = lits[0]^lits[1]^lit;
lits[0] = other, lits[1] = lit;
const signed char u = val (other);
if (u > 0) j[-1].blit = other;
else {
const int size = w.clause->size;
const const_literal_iterator end = lits + size;
const literal_iterator middle = lits + w.clause->pos;
literal_iterator k = middle;
signed char v = -1;
int r = 0;
while (k != end && (v = val (r = *k)) < 0)
k++;
if (v < 0) {
k = lits + 2;
assert (w.clause->pos <= size);
while (k != middle && (v = val (r = *k)) < 0)
k++;
}
w.clause->pos = k - lits;
assert (lits + 2 <= k), assert (k <= w.clause->end ());
if (v > 0) {
j[-1].blit = r;
} else if (!v) {
LOG (w.clause, "unwatch %d in", r);
lits[1] = r;
*k = lit;
watch_literal (r, lit, w.clause);
j--;
} else if (!u) {
assert (v < 0);
inst_assign (other);
} else {
assert (u < 0);
assert (v < 0);
LOG (w.clause, "conflict");
ok = false;
break;
}
}
}
}
if (j != i) {
while (i != eow)
*j++ = *i++;
ws.resize (j - ws.begin ());
}
}
int64_t delta = propagated - before;
stats.propagations.instantiate += delta;
STOP (propagate);
return ok;
}
/*------------------------------------------------------------------------*/
// This is the actual instantiation attempt.
bool Internal::instantiate_candidate (int lit, Clause * c) {
stats.instried++;
if (c->garbage) return false;
assert (!level);
bool found = false, satisfied = false, inactive = false;
int unassigned = 0;
for (const auto & other : *c) {
if (other == lit) found = true;
const signed char tmp = val (other);
if (tmp > 0) { satisfied = true; break; }
if (!tmp && !active (other)) { inactive = true; break; }
if (!tmp) unassigned++;
}
if (!found) return false;
if (inactive) return false;
if (satisfied) return false;
if (unassigned < 3) return false;
size_t before = trail.size ();
assert (propagated == before);
assert (active (lit));
LOG (c, "trying to instantiate %d in", lit);
assert (!c->garbage);
c->instantiated = true;
level++;
inst_assign (lit); // Assume 'lit' to true.
for (const auto & other : *c) {
if (other == lit) continue;
const signed char tmp = val (other);
if (tmp) { assert (tmp < 0); continue; }
inst_assign (-other); // Assume other to false.
}
bool ok = inst_propagate (); // Propagate.
while (trail.size () > before) { // Backtrack.
const int other = trail.back ();
LOG ("instantiate unassign %d", other);
trail.pop_back ();
assert (val (other) > 0);
vals[other] = vals[-other] = 0;
}
propagated = before;
assert (level == 1);
level = 0;
if (ok) { LOG ("instantiation failed"); return false; }
unwatch_clause (c);
strengthen_clause (c, lit);
watch_clause (c);
assert (c->size > 1);
LOG ("instantiation succeeded");
stats.instantiated++;
return true;
}
/*------------------------------------------------------------------------*/
// Try to instantiate all candidates collected before through the
// 'collect_instantiation_candidates' routine.
void Internal::instantiate (Instantiator & instantiator) {
assert (opts.instantiate);
START (instantiate);
stats.instrounds++;
#ifndef QUIET
const int64_t candidates = instantiator.candidates.size ();
#endif
int64_t instantiated = 0, tried = 0;
init_watches ();
connect_watches ();
if (propagated < trail.size ()) {
if (!propagate ()) {
LOG ("propagation after connecting watches failed");
learn_empty_clause ();
assert (unsat);
}
}
PHASE ("instantiate", stats.instrounds,
"attempting to instantiate %" PRId64 " candidate literal clause pairs",
candidates);
while (!unsat &&
!terminated_asynchronously () &&
!instantiator.candidates.empty ()) {
Instantiator::Candidate cand = instantiator.candidates.back ();
instantiator.candidates.pop_back ();
tried++;
if (!active (cand.lit)) continue;
LOG (cand.clause,
"trying to instantiate %d with "
"%zd negative occurrences in", cand.lit, cand.negoccs);
if (!instantiate_candidate (cand.lit, cand.clause)) continue;
instantiated++;
VERBOSE (2, "instantiation %" PRId64 " (%.1f%%) succeeded "
"(%.1f%%) with %zd negative occurrences in size %d clause",
tried, percent (tried, candidates),
percent (instantiated, tried), cand.negoccs, cand.size);
}
PHASE ("instantiate", stats.instrounds,
"instantiated %" PRId64 " candidate successfully "
"out of %" PRId64 " tried %.1f%%",
instantiated, tried, percent (instantiated, tried));
report ('I', !instantiated);
reset_watches ();
STOP (instantiate);
}
}

View File

@ -0,0 +1,47 @@
#ifndef _instantiate_hpp_INCLUDED
#define _instantiate_hpp_INCLUDED
namespace CaDiCaL {
// We are trying to remove literals in clauses, which occur in few clauses
// and further restrict this removal to variables for which variable
// elimination failed. Thus if for instance we succeed in removing the
// single occurrence of a literal, pure literal elimination can
// eliminate the corresponding variable in the next variable elimination
// round. The set of such literal clause candidate pairs is collected at
// the end of a variable elimination round and tried before returning. The
// name of this technique is inspired by 'variable instantiation' as
// described in [AnderssonBjesseCookHanna-DAC'02] and apparently
// successfully used in the 'Oepir' SAT solver.
struct Clause;
struct Internal;
class Instantiator {
friend struct Internal;
struct Candidate {
int lit;
int size;
size_t negoccs;
Clause * clause;
Candidate (int l, Clause * c, int s, size_t n) :
lit (l), size (s), negoccs (n), clause (c)
{ }
};
vector<Candidate> candidates;
public:
void candidate (int l, Clause * c, int s, size_t n) {
candidates.push_back (Candidate (l, c, s, n));
}
operator bool () const { return !candidates.empty (); }
};
}
#endif

739
hCaD_V2/src/internal.cpp Normal file
View File

@ -0,0 +1,739 @@
#include "internal.hpp"
namespace CaDiCaL {
/*------------------------------------------------------------------------*/
Internal::Internal ()
:
mode (SEARCH),
unsat (false),
iterating (false),
localsearching (false),
lookingahead (false),
preprocessing (false),
protected_reasons (false),
force_saved_phase (false),
searching_lucky_phases (false),
stable (false),
reported (false),
rephased (0),
vsize (0),
max_var (0),
level (0),
vals (0),
score_inc (1.0),
pol_sc_inc(1.0),
scores (this),
conflict (0),
ignore (0),
propagated (0),
propagated2 (0),
best_assigned (0),
target_assigned (0),
no_conflict_until (0),
proof (0),
checker (0),
tracer (0),
opts (this),
#ifndef QUIET
profiles (this),
force_phase_messages (false),
#endif
arena (this),
prefix ("c "),
internal (this),
external (0),
termination_forced (false),
vars (this->max_var),
lits (this->max_var)
{
control.push_back (Level (0, 0));
}
Internal::~Internal () {
for (const auto & c : clauses)
delete_clause (c);
if (proof) delete proof;
if (tracer) delete tracer;
if (checker) delete checker;
if (vals) { vals -= vsize; delete [] vals; }
}
/*------------------------------------------------------------------------*/
// Values in 'vals' can be accessed in the range '[-max_var,max_var]' that
// is directly by a literal. This is crucial for performance. By shifting
// the start of 'vals' appropriately, we achieve that negative offsets from
// the start of 'vals' can be used. We also need to set both values at
// 'lit' and '-lit' during assignments. In MiniSAT integer literals are
// encoded, using the least significant bit as negation. This avoids taking
// the 'abs ()' (as in our solution) and thus also avoids a branch in the
// hot-spot of the solver (clause traversal in propagation). That solution
// requires another (branch less) negation of the values though and
// debugging is harder since literals occur only encoded in clauses.
// The main draw-back of our solution is that we have to shift the memory
// and access it through negative indices, which looks less clean (but still
// as far I can tell is properly defined C / C++). You might get a warning
// by static analyzers though. Clang with '--analyze' thought that this
// idiom would generate a memory leak thus we use the following dummy.
static signed char * ignore_clang_analyze_memory_leak_warning;
void Internal::enlarge_vals (size_t new_vsize) {
signed char * new_vals;
const size_t bytes = 2u * new_vsize;
new_vals = new signed char [ bytes ]; // g++-4.8 does not like ... { 0 };
memset (new_vals, 0, bytes);
ignore_clang_analyze_memory_leak_warning = new_vals;
new_vals += new_vsize;
if (vals) memcpy (new_vals - max_var, vals - max_var, 2u*max_var + 1u);
vals -= vsize;
delete [] vals;
vals = new_vals;
}
/*------------------------------------------------------------------------*/
template<class T>
static void enlarge_init (vector<T> & v, size_t N, const T & i) {
while (v.size () < N)
v.push_back (i);
}
template<class T>
static void enlarge_only (vector<T> & v, size_t N) {
while (v.size () < N)
v.push_back (T ());
}
template<class T>
static void enlarge_zero (vector<T> & v, size_t N) {
enlarge_init (v, N, (const T &) 0);
}
/*------------------------------------------------------------------------*/
void Internal::enlarge (int new_max_var) {
assert (!level);
size_t new_vsize = vsize ? 2*vsize : 1 + (size_t) new_max_var;
while (new_vsize <= (size_t) new_max_var) new_vsize *= 2;
LOG ("enlarge internal size from %zd to new size %zd", vsize, new_vsize);
// Ordered in the size of allocated memory (larger block first).
enlarge_only (wtab, 2*new_vsize);
enlarge_only (vtab, new_vsize);
enlarge_zero (parents, new_vsize);
enlarge_only (links, new_vsize);
enlarge_zero (btab, new_vsize);
enlarge_zero (gtab, new_vsize);
enlarge_zero (stab, new_vsize);
enlarge_init (ptab, 2*new_vsize, -1);
enlarge_only (ftab, new_vsize);
enlarge_vals (new_vsize);
enlarge_zero (frozentab, new_vsize);
const signed char val = opts.phase ? 1 : -1;
enlarge_init (phases.saved, new_vsize, val);
if(opts.psids) enlarge_zero (phases.act, 2*new_vsize);
enlarge_zero (phases.forced, new_vsize);
enlarge_zero (phases.target, new_vsize);
enlarge_zero (phases.best, new_vsize);
enlarge_zero (phases.prev, new_vsize);
enlarge_zero (phases.min, new_vsize);
enlarge_zero (marks, new_vsize);
vsize = new_vsize;
}
void Internal::init_vars (int new_max_var) {
if (new_max_var <= max_var) return;
if (level) backtrack ();
LOG ("initializing %d internal variables from %d to %d",
new_max_var - max_var, max_var + 1, new_max_var);
if ((size_t) new_max_var >= vsize) enlarge (new_max_var);
#ifndef NDEBUG
for (int64_t i = -new_max_var; i < -max_var; i++) assert (!vals[i]);
for (unsigned i = max_var + 1; i <= (unsigned) new_max_var; i++)
assert (!vals[i]), assert (!btab[i]), assert (!gtab[i]);
for (uint64_t i = 2*((uint64_t)max_var + 1);
i <= 2*(uint64_t)new_max_var + 1;
i++)
assert (ptab[i] == -1);
#endif
assert (!btab[0]);
int old_max_var = max_var;
max_var = new_max_var;
init_queue (old_max_var, new_max_var);
init_scores (old_max_var, new_max_var);
int initialized = new_max_var - old_max_var;
stats.vars += initialized;
stats.unused += initialized;
stats.inactive += initialized;
LOG ("finished initializing %d internal variables", initialized);
}
void Internal::add_original_lit (int lit) {
assert (abs (lit) <= max_var);
if (lit) {
original.push_back (lit);
} else {
if (proof) proof->add_original_clause (original);
add_new_original_clause ();
original.clear ();
}
}
/*------------------------------------------------------------------------*/
// This is the main CDCL loop with interleaved inprocessing.
int Internal::cdcl_loop_with_inprocessing () {
int res = 0;
START (search);
if (stable) { START (stable); report ('['); }
else { START (unstable); report ('{'); }
while (!res) {
if (unsat) res = 20;
else if (!propagate ()) analyze (); // propagate and analyze
else if (iterating) iterate (); // report learned unit
else if (satisfied ()) res = 10; // found model
else if (search_limits_hit ()) break; // decision or conflict limit
else if (terminated_asynchronously ()) // externally terminated
break;
else if (restarting ()) restart (); // restart by backtracking
else if (rephasing ()) rephase (); // reset variable phases
else if (reducing ()) reduce (); // collect useless clauses
else if (probing ()) probe (); // failed literal probing
else if (subsuming ()) subsume (); // subsumption algorithm
else if (eliminating ()) elim (); // variable elimination
else if (compacting ()) compact (); // collect variables
else if (conditioning ()) condition (); // globally blocked clauses
else res = decide (); // next decision
}
if (stable) { STOP (stable); report (']'); }
else { STOP (unstable); report ('}'); }
STOP (search);
return res;
}
/*------------------------------------------------------------------------*/
// Most of the limits are only initialized in the first 'solve' call and
// increased as in a stand-alone non-incremental SAT call except for those
// explicitly marked as being reset below.
void Internal::init_report_limits () {
reported = false;
lim.report = 0;
}
void Internal::init_preprocessing_limits () {
const bool incremental = lim.initialized;
if (incremental)
LOG ("reinitializing preprocessing limits incrementally");
else LOG ("initializing preprocessing limits and increments");
const char * mode = 0;
/*----------------------------------------------------------------------*/
if (incremental) mode = "keeping";
else {
lim.subsume = stats.conflicts + scale (opts.subsumeint);
mode = "initial";
}
(void) mode;
LOG ("%s subsume limit %" PRId64 " after %" PRId64 " conflicts",
mode, lim.subsume, lim.subsume - stats.conflicts);
/*----------------------------------------------------------------------*/
if (incremental) mode = "keeping";
else {
last.elim.marked = -1;
lim.elim = stats.conflicts + scale (opts.elimint);
mode = "initial";
}
(void) mode;
LOG ("%s elim limit %" PRId64 " after %" PRId64 " conflicts",
mode, lim.elim, lim.elim - stats.conflicts);
// Initialize and reset elimination bounds in any case.
lim.elimbound = opts.elimboundmin;
LOG ("elimination bound %" PRId64 "", lim.elimbound);
/*----------------------------------------------------------------------*/
if (!incremental) {
last.ternary.marked = -1; // TODO explain why this is necessary.
lim.compact = stats.conflicts + opts.compactint;
LOG ("initial compact limit %" PRId64 " increment %" PRId64 "",
lim.compact, lim.compact - stats.conflicts);
}
/*----------------------------------------------------------------------*/
if (incremental) mode = "keeping";
else {
lim.probe = stats.conflicts + opts.probeint;
mode = "initial";
}
(void) mode;
LOG ("%s probe limit %" PRId64 " after %" PRId64 " conflicts",
mode, lim.probe, lim.probe - stats.conflicts);
/*----------------------------------------------------------------------*/
if (incremental) mode = "keeping";
else {
lim.condition = stats.conflicts + opts.conditionint;
mode = "initial";
}
LOG ("%s condition limit %" PRId64 " increment %" PRId64,
mode, lim.condition, lim.condition - stats.conflicts);
/*----------------------------------------------------------------------*/
// Initial preprocessing rounds.
if (inc.preprocessing <= 0) {
lim.preprocessing = 0;
LOG ("no preprocessing");
} else {
lim.preprocessing = inc.preprocessing;
LOG ("limiting to %" PRId64 " preprocessing rounds", lim.preprocessing);
}
}
void Internal::init_search_limits () {
const bool incremental = lim.initialized;
if (incremental) LOG ("reinitializing search limits incrementally");
else LOG ("initializing search limits and increments");
const char * mode = 0;
/*----------------------------------------------------------------------*/
if (incremental) mode = "keeping";
else {
last.reduce.conflicts = -1;
lim.reduce = stats.conflicts + opts.reduceint;
mode = "initial";
}
(void) mode;
LOG ("%s reduce limit %" PRId64 " after %" PRId64 " conflicts",
mode, lim.reduce, lim.reduce - stats.conflicts);
/*----------------------------------------------------------------------*/
if (incremental) mode = "keeping";
else {
lim.flush = opts.flushint;
inc.flush = opts.flushint;
mode = "initial";
}
(void) mode;
LOG ("%s flush limit %" PRId64 " interval %" PRId64 "",
mode, lim.flush, inc.flush);
/*----------------------------------------------------------------------*/
// Initialize or reset 'rephase' limits in any case.
lim.rephase = stats.conflicts + opts.rephaseint;
lim.rephased[0] = lim.rephased[1] = 0;
LOG ("new rephase limit %" PRId64 " after %" PRId64 " conflicts",
lim.rephase, lim.rephase - stats.conflicts);
/*----------------------------------------------------------------------*/
// Initialize or reset 'restart' limits in any case.
lim.restart = stats.conflicts + opts.restartint;
LOG ("new restart limit %" PRId64 " increment %" PRId64 "",
lim.restart, lim.restart - stats.conflicts);
/*----------------------------------------------------------------------*/
if (!incremental) {
stable = opts.stabilize && opts.stabilizeonly;
if (stable) LOG ("starting in always forced stable phase");
else LOG ("starting in default non-stable phase");
init_averages ();
} else if (opts.stabilize && opts.stabilizeonly) {
LOG ("keeping always forced stable phase");
assert (stable);
} else if (stable) {
LOG ("switching back to default non-stable phase");
stable = false;
swap_averages ();
} else LOG ("keeping non-stable phase");
inc.stabilize = opts.stabilizeint;
lim.stabilize = stats.conflicts + inc.stabilize;
LOG ("new stabilize limit %" PRId64 " after %" PRId64 " conflicts",
lim.stabilize, inc.stabilize);
if (opts.stabilize && opts.reluctant) {
LOG ("new restart reluctant doubling sequence period %d",
opts.reluctant);
reluctant.enable (opts.reluctant, opts.reluctantmax);
} else reluctant.disable ();
/*----------------------------------------------------------------------*/
// Conflict and decision limits.
if (inc.conflicts < 0) {
lim.conflicts = -1;
LOG ("no limit on conflicts");
} else {
lim.conflicts = stats.conflicts + inc.conflicts;
LOG ("conflict limit after %" PRId64 " conflicts at %" PRId64 " conflicts",
inc.conflicts, lim.conflicts);
}
if (inc.decisions < 0) {
lim.decisions = -1;
LOG ("no limit on decisions");
} else {
lim.decisions = stats.decisions + inc.decisions;
LOG ("conflict limit after %" PRId64 " decisions at %" PRId64 " decisions",
inc.decisions, lim.decisions);
}
/*----------------------------------------------------------------------*/
// Initial preprocessing rounds.
if (inc.localsearch <= 0) {
lim.localsearch = 0;
LOG ("no local search");
} else {
lim.localsearch = inc.localsearch;
LOG ("limiting to %" PRId64 " local search rounds", lim.localsearch);
}
/*----------------------------------------------------------------------*/
lim.initialized = true;
}
/*------------------------------------------------------------------------*/
bool Internal::preprocess_round (int round) {
(void) round;
if (unsat) return false;
if (!max_var) return false;
START (preprocess);
struct { int64_t vars, clauses; } before, after;
before.vars = active ();
before.clauses = stats.current.irredundant;
stats.preprocessings++;
assert (!preprocessing);
preprocessing = true;
PHASE ("preprocessing", stats.preprocessings,
"starting round %d with %" PRId64 " variables and %" PRId64 " clauses",
round, before.vars, before.clauses);
int old_elimbound = lim.elimbound;
if (opts.probe) probe (false);
if (opts.elim) elim (false);
if (opts.condition) condition (false);
after.vars = active ();
after.clauses = stats.current.irredundant;
assert (preprocessing);
preprocessing = false;
PHASE ("preprocessing", stats.preprocessings,
"finished round %d with %" PRId64 " variables and %" PRId64 " clauses",
round, after.vars, after.clauses);
STOP (preprocess);
report ('P');
if (unsat) return false;
if (after.vars < before.vars) return true;
if (old_elimbound < lim.elimbound) return true;
return false;
}
int Internal::preprocess () {
for (int i = 0; i < lim.preprocessing; i++)
if (!preprocess_round (i))
break;
if (unsat) return 20;
return 0;
}
/*------------------------------------------------------------------------*/
int Internal::try_to_satisfy_formula_by_saved_phases () {
LOG ("satisfying formula by saved phases");
assert (!level);
assert (!force_saved_phase);
assert (propagated == trail.size ());
force_saved_phase = true;
int res = 0;
while (!res) {
if (satisfied ()) {
LOG ("formula indeed satisfied by saved phases");
res = 10;
} else if (decide ()) {
LOG ("inconsistent assumptions with redundant clauses and phases");
res = 20;
} else if (!propagate ()) {
LOG ("saved phases do not satisfy redundant clauses");
assert (level > 0);
backtrack ();
conflict = 0; // ignore conflict
assert (!res);
break;
}
}
assert (force_saved_phase);
force_saved_phase = false;
return res;
}
/*------------------------------------------------------------------------*/
void Internal::produce_failed_assumptions () {
LOG ("producing failed assumptions");
assert (!level);
assert (!assumptions.empty ());
while (!unsat) {
assert (!satisfied ());
if (decide ()) break;
while (!unsat && !propagate ())
analyze ();
}
if (unsat) LOG ("formula is actually unsatisfiable unconditionally");
else LOG ("assumptions indeed failing");
}
/*------------------------------------------------------------------------*/
int Internal::local_search_round (int round) {
assert (round > 0);
if (unsat) return false;
if (!max_var) return false;
START_OUTER_WALK ();
assert (!localsearching);
localsearching = true;
// Determine propagation limit quadratically scaled with rounds.
//
int64_t limit = opts.walkmineff;
limit *= round;
if (LONG_MAX / round > limit) limit *= round;
else limit = LONG_MAX;
int res = walk_round (limit, true);
assert (localsearching);
localsearching = false;
STOP_OUTER_WALK ();
report ('L');
return res;
}
int Internal::local_search () {
if (unsat) return 0;
if (!max_var) return 0;
if (!opts.walk) return 0;
int res = 0;
for (int i = 1; !res && i <= lim.localsearch; i++)
res = local_search_round (i);
if (res == 10) {
LOG ("local search determined formula to be satisfiable");
assert (!stats.walk.minimum);
res = try_to_satisfy_formula_by_saved_phases ();
} else if (res == 20) {
LOG ("local search determined assumptions to be inconsistent");
assert (!assumptions.empty ());
produce_failed_assumptions ();
}
return res;
}
/*------------------------------------------------------------------------*/
int Internal::solve (bool preprocess_only) {
assert (clause.empty ());
START (solve);
if (preprocess_only) LOG ("internal solving in preprocessing only mode");
else LOG ("internal solving in full mode");
init_report_limits ();
int res = already_solved ();
if (!res) res = restore_clauses ();
if (!res) {
init_preprocessing_limits ();
if (!preprocess_only) init_search_limits ();
}
if (!res) res = preprocess ();
if (!preprocess_only) {
if (!res) res = local_search ();
if (!res) res = lucky_phases ();
if (!res) res = cdcl_loop_with_inprocessing ();
}
reset_solving ();
report_solving (res);
STOP (solve);
return res;
}
int Internal::already_solved () {
int res = 0;
if (unsat) {
LOG ("already inconsistent");
res = 20;
} else {
if (level) backtrack ();
if (!propagate ()) {
LOG ("root level propagation produces conflict");
learn_empty_clause ();
res = 20;
}
if(max_var == 0 && res == 0)
res = 10;
}
return res;
}
void Internal::report_solving (int res) {
if (res == 10) report ('1');
else if (res == 20) report ('0');
else report ('?');
}
void Internal::reset_solving () {
if (termination_forced) {
// TODO this leads potentially to a data race if the external
// user is calling 'terminate' twice within one 'solve' call.
// A proper solution would be to guard / protect setting the
// 'termination_forced' flag and only allow it during solving and
// ignore it otherwise thus also the second time it is called during a
// 'solve' call. We could move resetting it also the start of
// 'solve'.
//
termination_forced = false;
LOG ("reset forced termination");
}
}
int Internal::restore_clauses () {
int res = 0;
if (opts.restoreall <= 1 &&
external->tainted.empty ()) {
LOG ("no tainted literals and nothing to restore");
report ('*');
} else {
report ('+');
external->restore_clauses ();
internal->report ('r');
if (!unsat && !propagate ()) {
LOG ("root level propagation after restore produces conflict");
learn_empty_clause ();
res = 20;
}
}
return res;
}
int Internal::lookahead () {
assert (clause.empty ());
START (lookahead);
assert (!lookingahead);
lookingahead = true;
int tmp = already_solved ();
if (!tmp) tmp = restore_clauses ();
int res = 0;
if (!tmp) res = lookahead_probing ();
if (res == INT_MIN) res = 0;
reset_solving ();
report_solving (tmp);
assert (lookingahead);
lookingahead = false;
STOP(lookahead);
return res;
}
/*------------------------------------------------------------------------*/
void Internal::print_statistics () {
stats.print (this);
if (checker) checker->print_stats ();
}
/*------------------------------------------------------------------------*/
// Only useful for debugging purposes.
void Internal::dump (Clause * c) {
for (const auto & lit : *c)
printf ("%d ", lit);
printf ("0\n");
}
void Internal::dump () {
int64_t m = assumptions.size ();
for (auto idx : vars)
if (fixed (idx)) m++;
for (const auto & c : clauses)
if (!c->garbage) m++;
printf ("p cnf %d %" PRId64 "\n", max_var, m);
for (auto idx : vars) {
const int tmp = fixed (idx);
if (tmp) printf ("%d 0\n", tmp < 0 ? -idx : idx);
}
for (const auto & c : clauses)
if (!c->garbage) dump (c);
for (const auto & lit : assumptions)
printf ("%d 0\n", lit);
fflush (stdout);
}
/*------------------------------------------------------------------------*/
bool Internal::traverse_clauses (ClauseIterator & it) {
vector<int> eclause;
if (unsat) return it.clause (eclause);
for (const auto & c : clauses) {
if (c->garbage) continue;
if (c->redundant) continue;
bool satisfied = false;
for (const auto & ilit : *c) {
const int tmp = fixed (ilit);
if (tmp > 0) { satisfied = true; break; }
if (tmp < 0) continue;
const int elit = externalize (ilit);
eclause.push_back (elit);
}
if (!satisfied && !it.clause (eclause))
return false;
eclause.clear ();
}
return true;
}
}

1351
hCaD_V2/src/internal.hpp Normal file

File diff suppressed because it is too large Load Diff

48
hCaD_V2/src/ipasir.cpp Normal file
View File

@ -0,0 +1,48 @@
#include "ipasir.h"
#include "ccadical.h"
extern "C" {
const char * ipasir_signature () {
return ccadical_signature ();
}
void * ipasir_init () {
return ccadical_init (); }
void ipasir_release (void * solver) {
ccadical_release ((CCaDiCaL*) solver);
}
void ipasir_add (void * solver, int lit) {
ccadical_add ((CCaDiCaL *) solver, lit);
}
void ipasir_assume (void * solver, int lit) {
ccadical_assume ((CCaDiCaL *) solver, lit);
}
int ipasir_solve (void * solver) {
return ccadical_solve ((CCaDiCaL *) solver);
}
int ipasir_val (void * solver, int lit) {
return ccadical_val ((CCaDiCaL *) solver, lit);
}
int ipasir_failed (void * solver, int lit) {
return ccadical_failed ((CCaDiCaL *) solver, lit);
}
void ipasir_set_terminate (void * solver,
void * state, int (*terminate)(void * state)) {
ccadical_set_terminate ((CCaDiCaL *) solver, state, terminate);
}
void ipasir_set_learn (void * solver,
void * state, int max_length,
void (*learn)(void * state, int * clause)) {
ccadical_set_learn ((CCaDiCaL*) solver, state, max_length, learn);
}
}

38
hCaD_V2/src/ipasir.h Normal file
View File

@ -0,0 +1,38 @@
#ifndef _ipasir_h_INCLUDED
#define _ipasir_h_INCLUDED
/*------------------------------------------------------------------------*/
#ifdef __cplusplus
extern "C" {
#endif
/*------------------------------------------------------------------------*/
// Here are the declarations for the actual IPASIR functions, which is the
// generic incremental reentrant SAT solver API used for instance in the SAT
// competition. The other 'C' API in 'ccadical.h' is (more) type safe and
// has additional functions only supported by the CaDiCaL library. Please
// also refer to our SAT Race 2015 article in the Journal of AI from 2016.
const char * ipasir_signature (void);
void * ipasir_init (void);
void ipasir_release (void * solver);
void ipasir_add (void * solver, int lit);
void ipasir_assume (void * solver, int lit);
int ipasir_solve (void * solver);
int ipasir_val (void * solver, int lit);
int ipasir_failed (void * solver, int lit);
void ipasir_set_terminate (void * solver,
void * state, int (*terminate)(void * state));
void ipasir_set_learn (void * solver,
void * state, int max_length,
void (*learn)(void * state, int * clause));
/*------------------------------------------------------------------------*/
#ifdef __cplusplus
}
#endif
/*------------------------------------------------------------------------*/
#endif

30
hCaD_V2/src/level.hpp Normal file
View File

@ -0,0 +1,30 @@
#ifndef _level_hpp_INCLUDED
#define _level_hpp_INCLUDED
#include <climits>
namespace CaDiCaL {
// For each new decision we increase the decision level and push a 'Level'
// on the 'control' stack. The information gathered here is used in
// 'reuse_trail' and for early aborts in clause minimization.
struct Level {
int decision; // decision literal of this level
int trail; // trail start of this level
struct {
int count; // how many variables seen during 'analyze'
int trail; // smallest trail position seen on this level
} seen;
void reset () { seen.count = 0; seen.trail = INT_MAX; }
Level (int d, int t) : decision (d), trail (t) { reset (); }
Level () { }
};
}
#endif

122
hCaD_V2/src/limit.cpp Normal file
View File

@ -0,0 +1,122 @@
#include "internal.hpp"
namespace CaDiCaL {
Limit::Limit () {
memset (this, 0, sizeof *this);
}
/*------------------------------------------------------------------------*/
double Internal::scale (double v) const {
const double ratio = clause_variable_ratio ();
const double factor = (ratio <= 2) ? 1.0 : log (ratio) / log (2);
double res = factor * v;
if (res < 1) res = 1;
return res;
}
/*------------------------------------------------------------------------*/
Last::Last () {
memset (this, 0, sizeof *this);
}
/*------------------------------------------------------------------------*/
Inc::Inc () {
memset (this, 0, sizeof *this);
decisions = conflicts = -1; // unlimited
}
void Internal::limit_terminate (int l) {
if (l <= 0 && !lim.terminate.forced) {
LOG ("keeping unbounded terminate limit");
} else if (l <= 0) {
LOG ("reset terminate limit to be unbounded");
lim.terminate.forced = 0;
} else {
lim.terminate.forced = l;
LOG ("new terminate limit of %d calls", l);
}
}
void Internal::limit_conflicts (int l) {
if (l < 0 && inc.conflicts < 0) {
LOG ("keeping unbounded conflict limit");
} else if (l < 0) {
LOG ("reset conflict limit to be unbounded");
inc.conflicts = -1;
} else {
inc.conflicts = l;
LOG ("new conflict limit of %d conflicts", l);
}
}
void Internal::limit_decisions (int l) {
if (l < 0 && inc.decisions < 0) {
LOG ("keeping unbounded decision limit");
} else if (l < 0) {
LOG ("reset decision limit to be unbounded");
inc.decisions = -1;
} else {
inc.decisions = l;
LOG ("new decision limit of %d decisions", l);
}
}
void Internal::limit_preprocessing (int l) {
if (l < 0) {
LOG ("ignoring invalid preprocessing limit %d", l);
} else if (!l) {
LOG ("reset preprocessing limit to no preprocessing");
inc.preprocessing = 0;
} else {
inc.preprocessing = l;
LOG ("new preprocessing limit of %d preprocessing rounds", l);
}
}
void Internal::limit_local_search (int l) {
if (l < 0) {
LOG ("ignoring invalid local search limit %d", l);
} else if (!l) {
LOG ("reset local search limit to no local search");
inc.localsearch = 0;
} else {
inc.localsearch = l;
LOG ("new local search limit of %d local search rounds", l);
}
}
bool Internal::is_valid_limit (const char * name) {
if (!strcmp (name, "terminate")) return true;
if (!strcmp (name, "conflicts")) return true;
if (!strcmp (name, "decisions")) return true;
if (!strcmp (name, "preprocessing")) return true;
if (!strcmp (name, "localsearch")) return true;
return false;
}
bool Internal::limit (const char * name, int l) {
bool res = true;
if (!strcmp (name, "terminate")) limit_terminate (l);
else if (!strcmp (name, "conflicts")) limit_conflicts (l);
else if (!strcmp (name, "decisions")) limit_decisions (l);
else if (!strcmp (name, "preprocessing")) limit_preprocessing (l);
else if (!strcmp (name, "localsearch")) limit_local_search (l);
else res = false;
return res;
}
void Internal::reset_limits () {
LOG ("reset limits");
limit_terminate (0);
limit_conflicts (-1);
limit_decisions (-1);
limit_preprocessing (0);
limit_local_search (0);
}
}

68
hCaD_V2/src/limit.hpp Normal file
View File

@ -0,0 +1,68 @@
#ifndef _limit_hpp_INCLUDED
#define _limit_hpp_INCLUDED
namespace CaDiCaL {
struct Limit {
bool initialized;
int64_t conflicts; // conflict limit if non-negative
int64_t decisions; // decision limit if non-negative
int64_t preprocessing; // limit on preprocessing rounds
int64_t localsearch; // limit on local search rounds
int64_t compact; // conflict limit for next 'compact'
int64_t condition; // conflict limit for next 'condition'
int64_t elim; // conflict limit for next 'elim'
int64_t flush; // conflict limit for next 'flush'
int64_t probe; // conflict limit for next 'probe'
int64_t reduce; // conflict limit for next 'reduce'
int64_t rephase; // conflict limit for next 'rephase'
int64_t report; // report limit for header
int64_t restart; // conflict limit for next 'restart'
int64_t stabilize; // conflict limit for next 'stabilize'
int64_t subsume; // conflict limit for next 'subsume'
int keptsize; // maximum kept size in 'reduce'
int keptglue; // maximum kept glue in 'reduce'
// How often rephased during (1) or out (0) of stabilization.
//
int64_t rephased[2];
// Current elimination bound per eliminated variable.
//
int64_t elimbound;
struct {
int check; // countdown to next terminator call
int forced; // forced termination for testing
} terminate;
Limit ();
};
struct Last {
struct { int64_t propagations; } transred, vivify;
struct { int64_t fixed, subsumephases, marked; } elim;
struct { int64_t propagations, reductions; } probe;
struct { int64_t conflicts; } reduce, rephase;
struct { int64_t marked; } ternary;
struct { int64_t fixed; } collect;
Last ();
};
struct Inc {
int64_t flush; // flushing interval in terms of conflicts
int64_t stabilize; // stabilization interval increment
int64_t conflicts; // next conflict limit if non-negative
int64_t decisions; // next decision limit if non-negative
int64_t preprocessing; // next preprocessing limit if non-negative
int64_t localsearch; // next local search limit if non-negative
Inc ();
};
}
#endif

133
hCaD_V2/src/logging.cpp Normal file
View File

@ -0,0 +1,133 @@
#ifdef LOGGING
#include "internal.hpp"
namespace CaDiCaL {
void Logger::print_log_prefix (Internal * internal) {
internal->print_prefix ();
tout.magenta ();
fputs ("LOG ", stdout);
tout.magenta (true);
printf ("%d ", internal->level);
tout.normal ();
}
void Logger::log_empty_line (Internal * internal) {
internal->print_prefix ();
tout.magenta ();
const int len = internal->prefix.size (), max = 78 - len;
for (int i = 0; i < max; i++)
fputc ('-', stdout);
fputc ('\n', stdout);
tout.normal ();
fflush (stdout);
}
void Logger::log (Internal * internal, const char * fmt, ...) {
print_log_prefix (internal);
tout.magenta ();
va_list ap;
va_start (ap, fmt);
vprintf (fmt, ap);
va_end (ap);
fputc ('\n', stdout);
tout.normal ();
fflush (stdout);
}
// It is hard to factor out the common part between the two clause loggers,
// since they are also used in slightly different contexts. Our attempt to
// do so were not more readable than the current version. See the header
// for an explanation of the difference between the following two functions.
void Logger::log (Internal * internal,
const Clause * c, const char *fmt, ...) {
print_log_prefix (internal);
tout.magenta ();
va_list ap;
va_start (ap, fmt);
vprintf (fmt, ap);
va_end (ap);
if (c) {
if (c->redundant) printf (" glue %d redundant", c->glue);
else printf (" irredundant");
printf (" size %d clause[%" PRId64 "]", c->size, c->id);
if (c->moved) printf (" ... (moved)");
else {
if (internal->opts.logsort) {
vector<int> s;
for (const auto & lit : *c)
s.push_back (lit);
sort (s.begin (), s.end (), clause_lit_less_than ());
for (const auto & lit : s)
printf (" %d", lit);
} else {
for (const auto & lit : *c)
printf (" %d", lit);
}
}
} else if (internal->level) printf (" decision");
else printf (" unit");
fputc ('\n', stdout);
tout.normal ();
fflush (stdout);
}
// Same as above, but for the global clause 'c' (which is not a reason).
void Logger::log (Internal * internal,
const vector<int> & c, const char *fmt, ...) {
print_log_prefix (internal);
tout.magenta ();
va_list ap;
va_start (ap, fmt);
vprintf (fmt, ap);
va_end (ap);
if (internal->opts.logsort) {
vector<int> s;
for (const auto & lit : c)
s.push_back (lit);
sort (s.begin (), s.end (), clause_lit_less_than ());
for (const auto & lit : s)
printf (" %d", lit);
} else {
for (const auto & lit : c)
printf (" %d", lit);
}
fputc ('\n', stdout);
tout.normal ();
fflush (stdout);
}
// Now for 'restore_clause' to avoid copying (without logging).
void Logger::log (Internal * internal,
const vector<int>::const_iterator & begin,
const vector<int>::const_iterator & end,
const char *fmt, ...) {
print_log_prefix (internal);
tout.magenta ();
va_list ap;
va_start (ap, fmt);
vprintf (fmt, ap);
va_end (ap);
if (internal->opts.logsort) {
vector<int> s;
for (auto p = begin; p != end; p++)
s.push_back (*p);
sort (s.begin (), s.end (), clause_lit_less_than ());
for (const auto & lit : s)
printf (" %d", lit);
} else {
for (auto p = begin; p != end; p++)
printf (" %d", *p);
}
fputc ('\n', stdout);
tout.normal ();
fflush (stdout);
}
}
#endif

79
hCaD_V2/src/logging.hpp Normal file
View File

@ -0,0 +1,79 @@
#ifndef _logging_hpp_INCLUDED
#define _logging_hpp_INCLUDED
/*------------------------------------------------------------------------*/
#ifdef LOGGING
/*------------------------------------------------------------------------*/
#include <vector>
namespace CaDiCaL {
// For debugging purposes and to help understanding what the solver is doing
// there is a logging facility which is compiled in by './configure -l'. It
// still has to be enabled at run-time though (again using the '-l' option
// in the stand-alone solver). It produces quite a bit of information.
using namespace std;
struct Clause;
struct Internal;
struct Logger {
static void print_log_prefix (Internal *);
// Simple logging of a C-style format string.
//
static void log (Internal *, const char * fmt, ...)
CADICAL_ATTRIBUTE_FORMAT (2, 3);
// Prints the format string (with its argument) and then the clause. The
// clause can also be a zero pointer and then is interpreted as a decision
// (current decision level > 0) or unit clause (zero decision level) and
// printed accordingly.
//
static void log (Internal *, const Clause *, const char *fmt, ...)
CADICAL_ATTRIBUTE_FORMAT (3, 4);
// Same as before, except that this is meant for the global 'clause' stack
// used for new clauses (and not for reasons).
//
static void log (Internal *, const vector<int> &, const char *fmt, ...)
CADICAL_ATTRIBUTE_FORMAT (3, 4);
// Another variant, to avoid copying (without logging).
//
static void log (Internal *,
const vector<int>::const_iterator & begin,
const vector<int>::const_iterator & end,
const char *fmt, ...)
CADICAL_ATTRIBUTE_FORMAT (4, 5);
static void log_empty_line (Internal *);
};
}
/*------------------------------------------------------------------------*/
// Make sure that 'logging' code is really not included (second case of the
// '#ifdef') if logging code is not included.
#define LOG(...) \
do { \
if (!internal->opts.log) break; \
Logger::log (internal, __VA_ARGS__); \
} while (0)
/*------------------------------------------------------------------------*/
#else // end of 'then' part of 'ifdef LOGGING'
/*------------------------------------------------------------------------*/
#define LOG(...) do { } while (0)
/*------------------------------------------------------------------------*/
#endif // end of 'else' part of 'ifdef LOGGING'
/*------------------------------------------------------------------------*/
#endif

504
hCaD_V2/src/lookahead.cpp Normal file
View File

@ -0,0 +1,504 @@
#include "internal.hpp"
namespace CaDiCaL {
struct literal_occ {
int lit;
int count;
bool operator<(const literal_occ &locc) const {
return (count > locc.count) || (count == locc.count && lit < locc.lit);
}
literal_occ operator++() {
++count;
return *this;
}
};
std::vector<int> Internal::lookahead_populate_locc() {
std::vector<literal_occ> loccs((std::size_t)max_var+1);
for (std::size_t lit = 0; lit < loccs.size(); ++lit) {
loccs[lit].lit = lit;
}
for (const auto &c : clauses)
if (!c->redundant)
for (const auto &lit : *c)
if (active(lit))
++loccs[std::abs(lit)];
std::sort(begin(loccs), end(loccs));
std::vector<int> locc_map;
locc_map.reserve(max_var);
for (const auto &locc : loccs)
locc_map.push_back(locc.lit);
return locc_map;
}
int Internal::lookahead_locc(const std::vector<int> &loccs) {
for (auto lit : loccs)
if (active(abs(lit)) && !assumed(lit) && !assumed(-lit) && !val(lit))
return lit;
return 0;
}
// This calculates the literal that appears the most often reusing the
// available datastructures and iterating over the clause set. This is too
// slow to be called iteratively. A faster (but inexact) version is
// lookahead_populate_loc and lookahead_loc.
int Internal::most_occurring_literal () {
init_noccs ();
for (const auto & c : clauses)
if (!c->redundant)
for (const auto & lit : *c)
if (active (lit))
noccs (lit)++;
int64_t max_noccs = 0;
int res = 0;
if(unsat)
return INT_MIN;
propagate();
for (int idx = 1; idx <= max_var; idx++) {
if (!active (idx) || assumed(idx) || assumed(-idx) || val(idx)) continue;
for (int sign = -1; sign <= 1; sign += 2) {
const int lit = sign * idx;
if (!active(lit))
continue;
int64_t tmp = noccs(lit);
if (tmp <= max_noccs)
continue;
max_noccs = tmp;
res = lit;
}
}
MSG ("maximum occurrence %" PRId64 " of literal %d", max_noccs, res);
reset_noccs ();
return res;
}
// We probe on literals first, which occur more often negated and thus we
// sort the 'probes' stack in such a way that literals which occur negated
// less frequently come first. Probes are taken from the back of the stack.
struct probe_negated_noccs_rank {
Internal * internal;
probe_negated_noccs_rank (Internal * i) : internal (i) { }
typedef size_t Type;
Type operator () (int a) const { return internal->noccs (-a); }
};
// Follow the ideas in 'generate_probes' but flush non root probes and
// reorder remaining probes.
void Internal::lookahead_flush_probes () {
assert (!probes.empty ());
init_noccs ();
for (const auto & c : clauses) {
int a, b;
if (!is_binary_clause (c, a, b)) continue;
noccs (a)++;
noccs (b)++;
}
const auto eop = probes.end ();
auto j = probes.begin ();
for (auto i = j; i != eop; i++) {
int lit = *i;
if (!active (lit)) continue;
const bool have_pos_bin_occs = noccs (lit) > 0;
const bool have_neg_bin_occs = noccs (-lit) > 0;
if (have_pos_bin_occs == have_neg_bin_occs) continue;
if (have_pos_bin_occs) lit = -lit;
assert (!noccs (lit)), assert (noccs (-lit) > 0);
if (propfixed (lit) >= stats.all.fixed) continue;
MSG ("keeping probe %d negated occs %" PRId64 "", lit, noccs (-lit));
*j++ = lit;
}
size_t remain = j - probes.begin ();
#ifndef QUIET
size_t flushed = probes.size () - remain;
#endif
probes.resize (remain);
rsort (probes.begin (), probes.end (), probe_negated_noccs_rank (this));
reset_noccs ();
shrink_vector (probes);
PHASE ("probe-round", stats.probingrounds,
"flushed %zd literals %.0f%% remaining %zd",
flushed, percent (flushed, remain + flushed), remain);
}
void Internal::lookahead_generate_probes () {
assert (probes.empty ());
// First determine all the literals which occur in binary clauses. It is
// way faster to go over the clauses once, instead of walking the watch
// lists for each literal.
//
init_noccs ();
for (const auto & c : clauses) {
int a, b;
if (!is_binary_clause (c, a, b)) continue;
noccs (a)++;
noccs (b)++;
}
for (int idx = 1; idx <= max_var; idx++) {
// Then focus on roots of the binary implication graph, which are
// literals occurring negatively in a binary clause, but not positively.
// If neither 'idx' nor '-idx' is a root it makes less sense to probe
// this variable.
// This argument requires that equivalent literal substitution through
// 'decompose' is performed, because otherwise there might be 'cyclic
// roots' which are not tried, i.e., -1 2 0, 1 -2 0, 1 2 3 0, 1 2 -3 0.
const bool have_pos_bin_occs = noccs (idx) > 0;
const bool have_neg_bin_occs = noccs (-idx) > 0;
// if (have_pos_bin_occs == have_neg_bin_occs) continue;
if (have_pos_bin_occs) {
int probe = -idx;
// See the discussion where 'propfixed' is used below.
//
if (propfixed (probe) >= stats.all.fixed) continue;
MSG ("scheduling probe %d negated occs %" PRId64 "", probe, noccs (-probe));
probes.push_back (probe);
}
if (have_neg_bin_occs) {
int probe = idx;
// See the discussion where 'propfixed' is used below.
//
if (propfixed (probe) >= stats.all.fixed) continue;
MSG ("scheduling probe %d negated occs %" PRId64 "",
probe, noccs (-probe));
probes.push_back (probe);
}
}
rsort (probes.begin (), probes.end (), probe_negated_noccs_rank (this));
reset_noccs ();
shrink_vector (probes);
PHASE ("probe-round", stats.probingrounds,
"scheduled %zd literals %.0f%%",
probes.size (), percent (probes.size (), 2*max_var));
}
int Internal::lookahead_next_probe () {
int generated = 0;
for (;;) {
if (probes.empty ()) {
if (generated++) return 0;
lookahead_generate_probes ();
}
while (!probes.empty ()) {
int probe = probes.back ();
probes.pop_back ();
// Eliminated or assigned.
//
if (!active (probe) || assumed(probe) || assumed (-probe)) continue;
// There is now new unit since the last time we propagated this probe,
// thus we propagated it before without obtaining a conflict and
// nothing changed since then. Thus there is no need to propagate it
// again. This observation was independently made by Partik Simons
// et.al. in the context of implementing 'smodels' (see for instance
// Alg. 4 in his JAIR article from 2002) and it has also been
// contributed to the thesis work of Yacine Boufkhad.
//
if (propfixed (probe) >= stats.all.fixed) continue;
return probe;
}
}
}
bool non_tautological_cube (std::vector<int> cube) {
std::sort(begin(cube), end(cube), clause_lit_less_than ());
for(size_t i = 0, j = 1; j < cube.size(); ++i, ++j)
if(cube[i] == cube[j])
return false;
else if (cube[i] == - cube[j])
return false;
else if (cube[i] == 0)
return false;
return true;
}
bool Internal::terminating_asked() {
if (external->terminator && external->terminator->terminate()) {
MSG("connected terminator forces termination");
return true;
}
if (termination_forced) {
MSG("termination forced");
return true;
}
return false;
}
// We run probing on all literals with some differences:
//
// * no limit on the number of propagations. We rely on terminating to stop()
// * we run only one round
//
// The run can be expensive, so we actually first run the cheaper
// occurrence version and only then run lookahead.
//
int Internal::lookahead_probing() {
if (!active ())
return 0;
MSG ("lookahead-probe-round %" PRId64
" without propagations limit and %zu assumptions",
stats.probingrounds, assumptions.size());
termination_forced = false;
#ifndef QUIET
int old_failed = stats.failed;
int64_t old_probed = stats.probed;
#endif
int64_t old_hbrs = stats.hbrs;
if (unsat) return INT_MIN;
if (level) backtrack ();
if (!propagate ()) {
MSG ("empty clause before probing");
learn_empty_clause ();
return INT_MIN;
}
if (terminating_asked())
return most_occurring_literal();
decompose ();
if (ternary ()) // If we derived a binary clause
decompose (); // then start another round of ELS.
// Remove duplicated binary clauses and perform in essence hyper unary
// resolution, i.e., derive the unit '2' from '1 2' and '-1 2'.
//
mark_duplicated_binary_clauses_as_garbage ();
lim.conflicts = -1;
if (!probes.empty ()) lookahead_flush_probes ();
// We reset 'propfixed' since there was at least another conflict thus
// a new learned clause, which might produce new propagations (and hyper
// binary resolvents). During 'generate_probes' we keep the old value.
//
for (int idx = 1; idx <= max_var; idx++)
propfixed (idx) = propfixed (-idx) = -1;
assert (unsat || propagated == trail.size ());
propagated = propagated2 = trail.size ();
int probe;
int res = most_occurring_literal();
int max_hbrs = -1;
set_mode (PROBE);
MSG("unsat = %d, terminating_asked () = %d ", unsat, terminating_asked ());
while (!unsat &&
!terminating_asked () &&
(probe = lookahead_next_probe ())) {
stats.probed++;
int hbrs;
probe_assign_decision (probe);
if (probe_propagate ())
hbrs = trail.size(), backtrack();
else hbrs = 0, failed_literal (probe);
if (max_hbrs < hbrs ||
(max_hbrs == hbrs &&
internal->bumped(probe) > internal->bumped(res))) {
res = probe;
max_hbrs = hbrs;
}
}
reset_mode (PROBE);
if (unsat) {
MSG ("probing derived empty clause");
res = INT_MIN;
}
else if (propagated < trail.size ()) {
MSG ("probing produced %zd units",
(size_t)(trail.size () - propagated));
if (!propagate ()) {
MSG ("propagating units after probing results in empty clause");
learn_empty_clause ();
res = INT_MIN;
} else sort_watches ();
}
#ifndef QUIET
int failed = stats.failed - old_failed;
int64_t probed = stats.probed - old_probed;
#endif
int64_t hbrs = stats.hbrs - old_hbrs;
MSG ("lookahead-probe-round %" PRId64 " probed %" PRId64
" and found %d failed literals",
stats.probingrounds,
probed, failed);
if (hbrs)
PHASE ("lookahead-probe-round", stats.probingrounds,
"found %" PRId64 " hyper binary resolvents", hbrs);
MSG ("lookahead literal %d with %d\n", res, max_hbrs);
return res;
}
CubesWithStatus Internal::generate_cubes(int depth, int min_depth) {
if (!active() || depth == 0) {
CubesWithStatus cubes;
cubes.cubes.push_back(std::vector<int>());
return cubes;
}
lookingahead = true;
START(lookahead);
MSG("Generating cubes of depth %i", depth);
// presimplify required due to assumptions
termination_forced = false;
int res = already_solved();
if (res == 0)
res = restore_clauses();
if(unsat)
res = 10;
if (res != 0)
res = solve(true);
if (res != 0) {
MSG("Solved during preprocessing");
CubesWithStatus cubes;
cubes.status = res;
cubes.status = 20;
lookingahead = false;
STOP(lookahead);
return cubes;
}
reset_limits();
MSG ("generate cubes with %zu assumptions\n", assumptions.size());
assert(ntab.empty());
std::vector<int> current_assumptions{assumptions};
std::vector<std::vector<int>> cubes {{assumptions}};
auto loccs{lookahead_populate_locc()};
LOG("loccs populated\n");
assert(ntab.empty());
for (int i = 0; i < depth; ++i) {
LOG("Probing at depth %i, currently %zu have been generated",
i, cubes.size());
std::vector<std::vector<int>> cubes2 {std::move(cubes)};
cubes.clear ();
for (size_t j = 0; j < cubes2.size(); ++j) {
assert(ntab.empty());
assert(!unsat);
reset_assumptions();
for (auto lit : cubes2[j])
assume(lit);
restore_clauses();
propagate();
// preprocess_round(0); //uncomment maybe
if (unsat) {
LOG("current cube is unsat; skipping");
unsat = false;
continue;
}
int res = terminating_asked() ? lookahead_locc(loccs) : lookahead_probing();
if(unsat) {
LOG("current cube is unsat; skipping");
unsat = false;
continue;
}
if (res == 0) {
LOG("no lit to split %i", res);
cubes.push_back(cubes2[j]);
continue;
}
assert(res != 0);
LOG("splitting on lit %i", res);
std::vector<int> cube1{cubes2[j]};
cube1.push_back(res);
std::vector<int> cube2{std::move(cubes2[j])};
cube2.push_back(-res);
cubes.push_back(cube1);
cubes.push_back(cube2);
}
if (terminating_asked() && i >= min_depth)
break;
}
assert(std::for_each(std::begin(cubes), std::end(cubes), [](std::vector<int> cube){return non_tautological_cube (cube);}));
reset_assumptions();
for(auto lit : current_assumptions)
assume(lit);
STOP(lookahead);
lookingahead = false;
if (unsat) {
LOG("Solved during preprocessing");
CubesWithStatus cubes;
cubes.status = 20;
cubes.status = res;
return cubes;
}
CubesWithStatus rcubes;
rcubes.status = 0;
rcubes.cubes = cubes;
return rcubes;
}
} // namespace CaDiCaL

307
hCaD_V2/src/lucky.cpp Normal file
View File

@ -0,0 +1,307 @@
#include "internal.hpp"
namespace CaDiCaL {
// It turns out that even in the competition there are formulas which are
// easy to satisfy by either setting all variables to the same truth value
// or by assigning variables to the same value and propagating it. In the
// latter situation this can be done either in the order of all variables
// (forward or backward) or in the order of all clauses. These lucky
// assignments can be tested initially in a kind of pre-solving step.
// This function factors out clean up code common among the 'lucky'
// functions for backtracking and resetting a potential conflict. One could
// also use exceptions here, but there are two different reasons for
// aborting early. The first kind of aborting is due to asynchronous
// termination and the second kind due to a situation in which it is clear
// that a particular function will not be successful (for instance a
// completely negative clause is found). The latter situation returns zero
// and will just abort the particular lucky function, while the former will
// abort all (by returning '-1').
int Internal::unlucky (int res) {
if (level > 0) backtrack ();
if (conflict) conflict = 0;
return res;
}
int Internal::trivially_false_satisfiable () {
LOG ("checking that all clauses contain a negative literal");
assert (!level);
assert (assumptions.empty ());
for (const auto & c : clauses) {
if (terminated_asynchronously (100)) return unlucky (-1);
if (c->garbage) continue;
if (c->redundant) continue;
bool satisfied = false, found_negative_literal = false;
for (const auto & lit : *c) {
const signed char tmp = val (lit);
if (tmp > 0) { satisfied = true; break; }
if (tmp < 0) continue;
if (lit > 0) continue;
found_negative_literal = true;
break;
}
if (satisfied || found_negative_literal) continue;
LOG (c, "found purely positively");
return unlucky (0);
}
VERBOSE (1, "all clauses contain a negative literal");
for (auto idx : vars) {
if (terminated_asynchronously (10)) return unlucky (-1);
if (val (idx)) continue;
search_assume_decision (-idx);
if (propagate ()) continue;
assert (level > 0);
LOG ("propagation failed including redundant clauses");
return unlucky (0);
}
stats.lucky.constant.zero++;
return 10;
}
int Internal::trivially_true_satisfiable () {
LOG ("checking that all clauses contain a positive literal");
assert (!level);
assert (assumptions.empty ());
for (const auto & c : clauses) {
if (terminated_asynchronously (100)) return unlucky (-1);
if (c->garbage) continue;
if (c->redundant) continue;
bool satisfied = false, found_positive_literal = false;
for (const auto & lit : *c) {
const signed char tmp = val (lit);
if (tmp > 0) { satisfied = true; break; }
if (tmp < 0) continue;
if (lit < 0) continue;
found_positive_literal = true;
break;
}
if (satisfied || found_positive_literal) continue;
LOG (c, "found purely negatively");
return unlucky (0);
}
VERBOSE (1, "all clauses contain a positive literal");
for (auto idx : vars) {
if (terminated_asynchronously (10)) return unlucky (-1);
if (val (idx)) continue;
search_assume_decision (idx);
if (propagate ()) continue;
assert (level > 0);
LOG ("propagation failed including redundant clauses");
return unlucky (0);
}
stats.lucky.constant.one++;
return 10;
}
/*------------------------------------------------------------------------*/
int Internal::forward_false_satisfiable () {
LOG ("checking increasing variable index false assignment");
assert (!unsat);
assert (!level);
assert (assumptions.empty ());
for (auto idx : vars) {
if (terminated_asynchronously (100)) return unlucky (-1);
if (val (idx)) continue;
search_assume_decision (-idx);
if (!propagate ()) return unlucky (0);
}
VERBOSE (1, "forward assuming variables false satisfies formula");
assert (satisfied ());
stats.lucky.forward.zero++;
return 10;
}
int Internal::forward_true_satisfiable () {
LOG ("checking increasing variable index true assignment");
assert (!unsat);
assert (!level);
assert (assumptions.empty ());
for (auto idx : vars) {
if (terminated_asynchronously (10)) return unlucky (-1);
if (val (idx)) continue;
search_assume_decision (idx);
if (!propagate ()) return unlucky (0);
}
VERBOSE (1, "forward assuming variables true satisfies formula");
assert (satisfied ());
stats.lucky.forward.one++;
return 10;
}
/*------------------------------------------------------------------------*/
int Internal::backward_false_satisfiable () {
LOG ("checking decreasing variable index false assignment");
assert (!unsat);
assert (!level);
assert (assumptions.empty ());
for (int idx = max_var; idx > 0; idx--) {
if (terminated_asynchronously (10)) return unlucky (-1);
if (val (idx)) continue;
search_assume_decision (-idx);
if (!propagate ()) return unlucky (0);
}
VERBOSE (1, "backward assuming variables false satisfies formula");
assert (satisfied ());
stats.lucky.backward.zero++;
return 10;
}
int Internal::backward_true_satisfiable () {
LOG ("checking decreasing variable index true assignment");
assert (!unsat);
assert (!level);
assert (assumptions.empty ());
for (int idx = max_var; idx > 0; idx--) {
if (terminated_asynchronously (10)) return unlucky (-1);
if (val (idx)) continue;
search_assume_decision (idx);
if (!propagate ()) return unlucky (0);
}
VERBOSE (1, "backward assuming variables true satisfies formula");
assert (satisfied ());
stats.lucky.backward.one++;
return 10;
}
/*------------------------------------------------------------------------*/
// The following two functions test if the formula is a satisfiable horn
// formula. Actually the test is slightly more general. It goes over all
// clauses and assigns the first positive literal to true and propagates.
// Already satisfied clauses are of course skipped. A reverse function
// is not implemented yet.
int Internal::positive_horn_satisfiable () {
LOG ("checking that all clauses are positive horn satisfiable");
assert (!level);
assert (assumptions.empty ());
for (const auto & c : clauses) {
if (terminated_asynchronously (10)) return unlucky (-1);
if (c->garbage) continue;
if (c->redundant) continue;
int positive_literal = 0;
bool satisfied = false;
for (const auto & lit : *c) {
const signed char tmp = val (lit);
if (tmp > 0) { satisfied = true; break; }
if (tmp < 0) continue;
if (lit < 0) continue;
positive_literal = lit;
break;
}
if (satisfied) continue;
if (!positive_literal) {
LOG (c, "no positive unassigned literal in");
return unlucky (0);
}
assert (positive_literal > 0);
LOG (c, "found positive literal %d in", positive_literal);
search_assume_decision (positive_literal);
if (propagate ()) continue;
LOG ("propagation of positive literal %d leads to conflict",
positive_literal);
return unlucky (0);
}
for (auto idx : vars) {
if (terminated_asynchronously (10)) return unlucky (-1);
if (val (idx)) continue;
search_assume_decision (-idx);
if (propagate ()) continue;
LOG ("propagation of remaining literal %d leads to conflict", -idx);
return unlucky (0);
}
VERBOSE (1, "clauses are positive horn satisfied");
assert (!conflict);
assert (satisfied ());
stats.lucky.horn.positive++;
return 10;
}
int Internal::negative_horn_satisfiable () {
LOG ("checking that all clauses are negative horn satisfiable");
assert (!level);
assert (assumptions.empty ());
for (const auto & c : clauses) {
if (terminated_asynchronously (10)) return unlucky (-1);
if (c->garbage) continue;
if (c->redundant) continue;
int negative_literal = 0;
bool satisfied = false;
for (const auto & lit : *c) {
const signed char tmp = val (lit);
if (tmp > 0) { satisfied = true; break; }
if (tmp < 0) continue;
if (lit > 0) continue;
negative_literal = lit;
break;
}
if (satisfied) continue;
if (!negative_literal) {
if (level > 0) backtrack ();
LOG (c, "no negative unassigned literal in");
return unlucky (0);
}
assert (negative_literal < 0);
LOG (c, "found negative literal %d in", negative_literal);
search_assume_decision (negative_literal);
if (propagate ()) continue;
LOG ("propagation of negative literal %d leads to conflict",
negative_literal);
return unlucky (0);
}
for (auto idx : vars) {
if (terminated_asynchronously (10)) return unlucky (-1);
if (val (idx)) continue;
search_assume_decision (idx);
if (propagate ()) continue;
LOG ("propagation of remaining literal %d leads to conflict", idx);
return unlucky (0);
}
VERBOSE (1, "clauses are negative horn satisfied");
assert (!conflict);
assert (satisfied ());
stats.lucky.horn.negative++;
return 10;
}
/*------------------------------------------------------------------------*/
int Internal::lucky_phases () {
assert (!level);
require_mode (SEARCH);
if (!opts.lucky) return 0;
// TODO: Some of the lucky assignments can also be found if there are
// assumptions, but this is not completely implemented nor tested yet.
//
if (!assumptions.empty ()) return 0;
START (search);
START (lucky);
assert (!searching_lucky_phases);
searching_lucky_phases = true;
stats.lucky.tried++;
int res = trivially_false_satisfiable ();
if (!res) res = trivially_true_satisfiable ();
if (!res) res = forward_true_satisfiable ();
if (!res) res = forward_false_satisfiable ();
if (!res) res = backward_false_satisfiable ();
if (!res) res = backward_true_satisfiable ();
if (!res) res = positive_horn_satisfiable ();
if (!res) res = negative_horn_satisfiable ();
if (res < 0) assert (termination_forced), res = 0;
if (res == 10) stats.lucky.succeeded++;
report ('l', !res);
assert (searching_lucky_phases);
searching_lucky_phases = false;
STOP (lucky);
STOP (search);
return res;
}
}

1
hCaD_V2/src/makefile Symbolic link
View File

@ -0,0 +1 @@
/home/chenzh/experiments/cec/equal/hCaD_V2/makefile

206
hCaD_V2/src/message.cpp Normal file
View File

@ -0,0 +1,206 @@
#include "internal.hpp"
namespace CaDiCaL {
/*------------------------------------------------------------------------*/
#ifndef QUIET
/*------------------------------------------------------------------------*/
void Internal::print_prefix () { fputs (prefix.c_str (), stdout); }
void Internal::vmessage (const char * fmt, va_list & ap) {
#ifdef LOGGING
if (!opts.log)
#endif
if (opts.quiet) return;
print_prefix ();
vprintf (fmt, ap);
fputc ('\n', stdout);
fflush (stdout);
}
void Internal::message (const char * fmt, ...) {
va_list ap;
va_start (ap, fmt);
vmessage (fmt, ap);
va_end (ap);
}
void Internal::message () {
#ifdef LOGGING
if (!opts.log)
#endif
if (opts.quiet) return;
print_prefix ();
fputc ('\n', stdout);
fflush (stdout);
}
/*------------------------------------------------------------------------*/
void Internal::vverbose (int level, const char * fmt, va_list & ap) {
#ifdef LOGGING
if (!opts.log)
#endif
if (opts.quiet || level > opts.verbose) return;
print_prefix ();
vprintf (fmt, ap);
fputc ('\n', stdout);
fflush (stdout);
}
void Internal::verbose (int level, const char * fmt, ...) {
va_list ap;
va_start (ap, fmt);
vverbose (level, fmt, ap);
va_end (ap);
}
void Internal::verbose (int level) {
#ifdef LOGGING
if (!opts.log)
#endif
if (opts.quiet || level > opts.verbose) return;
print_prefix ();
fputc ('\n', stdout);
fflush (stdout);
}
/*------------------------------------------------------------------------*/
void Internal::section (const char * title) {
#ifdef LOGGING
if (!opts.log)
#endif
if (opts.quiet) return;
if (stats.sections++) MSG ();
print_prefix ();
tout.blue ();
fputs ("--- [ ", stdout);
tout.blue (true);
fputs (title, stdout);
tout.blue ();
fputs (" ] ", stdout);
for (int i = strlen (title) + strlen (prefix.c_str ()) + 9; i < 78; i++)
fputc ('-' , stdout);
tout.normal ();
fputc ('\n', stdout);
MSG ();
}
/*------------------------------------------------------------------------*/
void Internal::phase (const char * phase, const char * fmt, ...) {
#ifdef LOGGING
if (!opts.log)
#endif
if (opts.quiet ||
(!force_phase_messages && opts.verbose < 2)) return;
print_prefix ();
printf ("[%s] ", phase);
va_list ap;
va_start (ap, fmt);
vprintf (fmt, ap);
va_end (ap);
fputc ('\n', stdout);
fflush (stdout);
}
void Internal::phase (const char * phase,
int64_t count, const char * fmt, ...) {
#ifdef LOGGING
if (!opts.log)
#endif
if (opts.quiet ||
(!force_phase_messages && opts.verbose < 2)) return;
print_prefix ();
printf ("[%s-%" PRId64 "] ", phase, count);
va_list ap;
va_start (ap, fmt);
vprintf (fmt, ap);
va_end (ap);
fputc ('\n', stdout);
fflush (stdout);
}
/*------------------------------------------------------------------------*/
#endif // ifndef QUIET
/*------------------------------------------------------------------------*/
void Internal::warning (const char *fmt, ...) {
fflush (stdout);
terr.bold ();
fputs ("cadical: ", stderr);
terr.red (1);
fputs ("warning:", stderr);
terr.normal ();
fputc (' ', stderr);
va_list ap;
va_start (ap, fmt);
vfprintf (stderr, fmt, ap);
va_end (ap);
fputc ('\n', stderr);
fflush (stderr);
}
/*------------------------------------------------------------------------*/
void Internal::error_message_start () {
fflush (stdout);
terr.bold ();
fputs ("cadical: ", stderr);
terr.red (1);
fputs ("error:", stderr);
terr.normal ();
fputc (' ', stderr);
}
void Internal::error_message_end () {
fputc ('\n', stderr);
fflush (stderr);
// TODO add possibility to use call back instead.
exit (1);
}
void Internal::verror (const char *fmt, va_list & ap) {
error_message_start ();
vfprintf (stderr, fmt, ap);
error_message_end ();
}
void Internal::error (const char *fmt, ...) {
va_list ap;
va_start (ap, fmt);
verror (fmt, ap);
va_end (ap); // unreachable
}
/*------------------------------------------------------------------------*/
void fatal_message_start () {
fflush (stdout);
terr.bold ();
fputs ("cadical: ", stderr);
terr.red (1);
fputs ("fatal error:", stderr);
terr.normal ();
fputc (' ', stderr);
}
void fatal_message_end () {
fputc ('\n', stderr);
fflush (stderr);
abort ();
}
void fatal (const char *fmt, ...) {
fatal_message_start ();
va_list ap;
va_start (ap, fmt);
vfprintf (stderr, fmt, ap);
va_end (ap);
fatal_message_end ();
abort ();
}
}

40
hCaD_V2/src/message.hpp Normal file
View File

@ -0,0 +1,40 @@
#ifndef _message_h_INCLUDED
#define _message_h_INCLUDED
/*------------------------------------------------------------------------*/
// Macros for compact message code.
#ifndef QUIET
#define LINE() \
do { if (internal) internal->message (); } while (0)
#define MSG(...) \
do { if (internal) internal->message (__VA_ARGS__); } while (0)
#define PHASE(...) \
do { if (internal) internal->phase (__VA_ARGS__); } while (0)
#define SECTION(...) \
do { if (internal) internal->section (__VA_ARGS__); } while (0)
#define VERBOSE(...) \
do { if (internal) internal->verbose (__VA_ARGS__); } while (0)
#else
#define LINE() do { } while (0)
#define MSG(...) do { } while (0)
#define PHASE(...) do { } while (0)
#define SECTION(...) do { } while (0)
#define VERBOSE(...) do { } while (0)
#endif
#define FATAL fatal
#define WARNING(...) internal->warning (__VA_ARGS__)
/*------------------------------------------------------------------------*/
#endif // ifndef _message_h_INCLUDED

104
hCaD_V2/src/minimize.cpp Normal file
View File

@ -0,0 +1,104 @@
#include "internal.hpp"
namespace CaDiCaL {
// Functions for learned clause minimization. We only have the recursive
// version, which actually really is implemented recursively. We also
// played with a derecursified version, which however was more complex and
// slower. The trick to keep potential stack exhausting recursion under
// guards is to explicitly limit the recursion depth.
// Instead of signatures as in the original implementation in MiniSAT and
// our corresponding paper, we use the 'poison' idea of Allen Van Gelder to
// mark unsuccessful removal attempts, then Donald Knuth's idea to abort
// minimization if only one literal was seen on the level and a new idea of
// also aborting if the earliest seen literal was assigned afterwards.
bool Internal::minimize_literal (int lit, int depth) {
LOG("attempt to minimize lit %d at depth %d", lit, depth);
assert(val(lit) > 0);
Flags & f = flags (lit);
Var & v = var (lit);
if (!v.level || f.removable || f.keep) return true;
if (!v.reason || f.poison || v.level == level) return false;
const Level & l = control[v.level];
if (!depth && l.seen.count < 2) return false; // Don Knuth's idea
if (v.trail <= l.seen.trail) return false; // new early abort
if (depth > opts.minimizedepth) return false;
bool res = true;
assert (v.reason);
const const_literal_iterator end = v.reason->end ();
const_literal_iterator i;
for (i = v.reason->begin (); res && i != end; i++) {
const int other = *i;
if (other == lit) continue;
res = minimize_literal (-other, depth + 1);
}
if (res) f.removable = true; else f.poison = true;
minimized.push_back (lit);
if (!depth) LOG ("minimizing %d %s", lit, res ? "succeeded" : "failed");
return res;
}
// Sorting the clause before minimization with respect to the trail order
// (literals with smaller trail height first) is necessary but natural and
// might help to minimize the required recursion depth too.
struct minimize_trail_positive_rank {
Internal * internal;
minimize_trail_positive_rank (Internal * s) : internal (s) { }
typedef int Type;
Type operator () (const int & a) const {
assert (internal->val (a));
return internal->var (a).trail;
}
};
struct minimize_trail_smaller {
Internal * internal;
minimize_trail_smaller (Internal * s) : internal (s) { }
bool operator () (const int & a, const int & b) const {
return internal->var (a).trail < internal->var (b).trail;
}
};
void Internal::minimize_clause () {
START (minimize);
LOG (clause, "minimizing first UIP clause");
external->check_learned_clause (); // check 1st UIP learned clause first
minimize_sort_clause();
assert (minimized.empty ());
const auto end = clause.end ();
auto j = clause.begin (), i = j;
for (; i != end; i++)
if (minimize_literal (-*i)) stats.minimized++;
else flags (*j++ = *i).keep = true;
LOG ("minimized %zd literals", (size_t)(clause.end () - j));
if (j != end) clause.resize (j - clause.begin ());
clear_minimized_literals ();
STOP (minimize);
}
// Sort the literals in reverse assignment order (thus trail order) to
// establish the base case of the recursive minimization algorithm in the
// positive case (where a literal with 'keep' true is hit).
//
void Internal::minimize_sort_clause () {
MSORT(opts.radixsortlim, clause.begin(), clause.end(),
minimize_trail_positive_rank(this), minimize_trail_smaller(this));
}
void Internal::clear_minimized_literals () {
LOG ("clearing %zd minimized literals", minimized.size ());
for (const auto & lit : minimized) {
Flags & f = flags (lit);
f.poison = f.removable = f.shrinkable = false;
}
for (const auto & lit : clause)
assert(!flags(lit).shrinkable), flags(lit).keep = flags(lit).shrinkable = false;
minimized.clear ();
}
}

3082
hCaD_V2/src/mobical.cpp Normal file

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More