
org.chocosolver.sat.SatSolver Maven / Gradle / Ivy
Show all versions of choco-sat Show documentation
/*
* This file is part of choco-sat, http://choco-solver.org/
*
* Copyright (c) 2020, IMT Atlantique. All rights reserved.
*
* Licensed under the BSD 4-clause license.
*
* See LICENSE file in the project root for full license information.
*/
package org.chocosolver.sat;
import gnu.trove.list.TIntList;
import gnu.trove.list.array.TIntArrayList;
import gnu.trove.map.hash.TIntObjectHashMap;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
/**
* A MiniSat solver.
*
* (or-tools, booleans.cc, ty L. Perron).
*
*
* @author Charles Prud'homme
* @since 12/07/13
*/
public class SatSolver implements SatFactory {
/**
* static const Literal kUndefinedLiteral = Literal(-2);
*/
private static final int kUndefinedLiteral = -2;
// If false, the constraints are already unsatisfiable. No part of
// the solver state may be used!
public boolean ok_;
// List of problem addClauses.
public ArrayList clauses;
// List of learnt addClauses.
public ArrayList learnts;
// 'watches_[lit]' is a list of constraints watching 'lit'(will go
// there if literal becomes true).
private TIntObjectHashMap> watches_;
// implies_[lit] is a list of literals to set to true if 'lit' becomes true.
public TIntObjectHashMap implies_;
// The current assignments.
TIntObjectHashMap assignment_;
// Assignment stack; stores all assigments made in the order they
// were made.
TIntArrayList trail_;
// Separator indices for different decision levels in 'trail_'.
TIntArrayList trail_markers_;
// Head of queue(as index into the trail_.
int qhead_;
// Number of variables
int num_vars_;
private TIntArrayList temporary_add_vector_;
public TIntArrayList touched_variables_;
public SatSolver() {
this.ok_ = true;
this.qhead_ = 0;
num_vars_ = 0;
this.clauses = new ArrayList<>();
this.learnts = new ArrayList<>();
this.watches_ = new TIntObjectHashMap<>();
this.implies_ = new TIntObjectHashMap<>();
this.assignment_ = new TIntObjectHashMap<>();
this.trail_ = new TIntArrayList();
this.trail_markers_ = new TIntArrayList();
this.temporary_add_vector_ = new TIntArrayList();
this.touched_variables_ = new TIntArrayList();
}
@Override
public SatSolver _me() {
return this;
}
// Add a new variable.
public int newVariable() {
int v = incrementVariableCounter();
// watches_.resize(2 * v + 2);
// implies_.resize(2 * v.value() + 2);
assignment_.put(v, Boolean.kUndefined);
return v;
}
// Add a clause to the solver.
public boolean addClause(TIntList ps) {
assert 0 == trailMarker();
if (!ok_) return false;
// Check if clause is satisfied and remove false/duplicated literals:
ps.sort();
int lit = kUndefinedLiteral;
int j = 0;
for (int i = 0; i < ps.size(); i++) {
if (valueLit(ps.get(i)) == Boolean.kTrue || ps.get(i) == negated(lit)) {
return true;
} else if (valueLit(ps.get(i)) != Boolean.kFalse && ps.get(i) != lit) {
lit = ps.get(i);
ps.set(j++, lit);
}
}
if (j < ps.size()) {
ps.remove(j, ps.size() - j);
}
switch (ps.size()) {
case 0:
return (ok_ = false);
case 1:
uncheckedEnqueue(ps.get(0));
return (ok_ = propagate());
case 2:
int l0 = ps.get(0);
int l1 = ps.get(1);
TIntArrayList i0 = implies_.get(negated(l0));
if (i0 == null) {
i0 = new TIntArrayList();
implies_.put(negated(l0), i0);
}
i0.add(l1);
TIntArrayList i1 = implies_.get(negated(l1));
if (i1 == null) {
i1 = new TIntArrayList();
implies_.put(negated(l1), i1);
}
i1.add(l0);
break;
default:
Clause cr = new Clause(ps.toArray());
clauses.add(cr);
attachClause(cr);
break;
}
return true;
}
/**
* Add a clause during resolution
* @param ps clause to add
* @return true if clause is added
*/
public boolean learnClause(int... ps) {
Arrays.sort(ps);
switch (ps.length) {
case 0:
return (ok_ = false);
case 1:
dynUncheckedEnqueue(ps[0]);
return (ok_ = propagate());
default:
Clause cr = new Clause(ps);
learnts.add(cr);
attachClause(cr);
break;
}
return true;
}
// Add the empty clause, making the solver contradictory.
boolean addEmptyClause() {
temporary_add_vector_.resetQuick();
return addClause(temporary_add_vector_);
}
// Add a unit clause to the solver.
public boolean addClause(int l) {
temporary_add_vector_.resetQuick();
temporary_add_vector_.add(l);
return addClause(temporary_add_vector_);
}
// Add a binary clause to the solver.
boolean addClause(int p, int q) {
temporary_add_vector_.resetQuick();
temporary_add_vector_.add(p);
temporary_add_vector_.add(q);
return addClause(temporary_add_vector_);
}
// Add a ternary clause to the solver.
boolean addClause(int p, int q, int r) {
temporary_add_vector_.resetQuick();
temporary_add_vector_.add(p);
temporary_add_vector_.add(q);
temporary_add_vector_.add(r);
return addClause(temporary_add_vector_);
}
// Incremental propagation.
boolean initPropagator() {
touched_variables_.resetQuick();
return !ok_;
}
// Backtrack until a certain level.
public void cancelUntil(int level) {
if (trailMarker() > level) {
for (int c = trail_.size() - 1; c >= trail_markers_.get(level); c--) {
int x = var(trail_.get(c));
assignment_.put(x, Boolean.kUndefined);
}
qhead_ = trail_markers_.get(level);
trail_.remove(trail_markers_.get(level), trail_.size() - trail_markers_.get(level));
trail_markers_.remove(level, trail_markers_.size() - level);
}
}
// Gives the current decisionlevel.
public int trailMarker() {
return trail_markers_.size();
}
// The current value of a variable.
Boolean valueVar(int x) {
return assignment_.get(x);
}
// The current value of a literal.
Boolean valueLit(int l) {
Boolean b = assignment_.get(var(l));
return b == Boolean.kUndefined ? Boolean.kUndefined : xor(b, sign(l));
}
// The current number of original clauses.
int nClauses() {
return clauses.size();
}
// The current number of original clauses.
public int nLearnt() {
return learnts.size();
}
// Propagates one literal, returns true if successful, false in case
// of failure.
public boolean propagateOneLiteral(int lit) {
assert ok_;
touched_variables_.resetQuick();
if (!propagate()) {
return false;
}
if (valueLit(lit) == Boolean.kTrue) {
// Dummy decision level:
pushTrailMarker();
return true;
} else if (valueLit(lit) == Boolean.kFalse) {
return false;
}
pushTrailMarker();
// Unchecked enqueue
assert valueLit(lit) == Boolean.kUndefined;
assignment_.put(var(lit), makeBoolean(!sign(lit)));
trail_.add(lit);
return propagate();
}
private int incrementVariableCounter() {
return num_vars_++;
}
// Begins a new decision level.
private void pushTrailMarker() {
trail_markers_.add(trail_.size());
}
// Enqueue a literal. Assumes value of literal is undefined.
void uncheckedEnqueue(int l) {
assert valueLit(l) == Boolean.kUndefined;
if (assignment_.get(var(l)) == Boolean.kUndefined) {
touched_variables_.add(l);
}
assignment_.put(var(l), sign(l) ? Boolean.kFalse : Boolean.kTrue);
trail_.add(l);
}
private void dynUncheckedEnqueue(int l) {
touched_variables_.add(l);
}
// Test if fact 'p' contradicts current state, Enqueue otherwise.
private boolean enqueue(int l) {
if (valueLit(l) != Boolean.kUndefined) {
return valueLit(l) != Boolean.kFalse;
} else {
uncheckedEnqueue(l);
return true;
}
}
// Attach a clause to watcher lists.
private void attachClause(Clause cr) {
assert cr.size() > 1;
ArrayList l0 = watches_.get(negated(cr._g(0)));
if (l0 == null) {
l0 = new ArrayList<>();
watches_.put(negated(cr._g(0)), l0);
}
ArrayList l1 = watches_.get(negated(cr._g(1)));
if (l1 == null) {
l1 = new ArrayList<>();
watches_.put(negated(cr._g(1)), l1);
}
l0.add(new Watcher(cr, cr._g(1)));
l1.add(new Watcher(cr, cr._g(0)));
}
public void detachLearnt(int ci) {
Clause cr = learnts.get(ci);
learnts.remove(ci);
ArrayList ws = watches_.get(negated(cr._g(0)));
int i = ws.size() - 1;
while (i >= 0 && ws.get(i).clause != cr) {
i--;
}
assert i > -1;
ws.remove(i);
ws = watches_.get(negated(cr._g(1)));
i = ws.size() - 1;
while (i >= 0 && ws.get(i).clause != cr) {
i--;
}
assert i > -1;
ws.remove(i);
}
// Perform unit propagation. returns true upon success.
boolean propagate() {
boolean result = true;
while (qhead_ < trail_.size()) {
int p = trail_.get(qhead_++);
// Propagate the implies first.
if(!propagateImplies(p)){
return false;
}
result &= propagateClauses(p);
}
return result;
}
private boolean propagateClauses(int p) {
boolean result = true;
// 'p' is enqueued fact to propagate.
ArrayList ws = watches_.get(p);
int i = 0;
int j = 0;
while (ws != null && i < ws.size()) {
// Try to avoid inspecting the clause:
int blocker = ws.get(i).blocker;
if (valueLit(blocker) == Boolean.kTrue) {
ws.set(j++, ws.get(i++));
continue;
}
// Make sure the false literal is data[1]:
Clause cr = ws.get(i).clause;
final int false_lit = negated(p);
if (cr._g(0) == false_lit) {
cr._s(0, cr._g(1));
cr._s(1, false_lit);
}
assert (cr._g(1) == false_lit);
i++;
// If 0th watch is true, then clause is already satisfied.
final int first = cr._g(0);
Watcher w = new Watcher(cr, first);
if (first != blocker && valueLit(first) == Boolean.kTrue) {
ws.set(j++, w);
continue;
}
// Look for new watch:
boolean cont = false;
for (int k = 2; k < cr.size(); k++) {
if (valueLit(cr._g(k)) != Boolean.kFalse) {
cr._s(1, cr._g(k));
cr._s(k, false_lit);
ArrayList lw = watches_.get(negated(cr._g(1)));
if (lw == null) {
lw = new ArrayList<>();
watches_.put(negated(cr._g(1)), lw);
}
lw.add(w);
cont = true;
break;
}
}
// Did not find watch -- clause is unit under assignment:
if (!cont) {
ws.set(j++, w);
if (valueLit(first) == Boolean.kFalse) {
result = false;
qhead_ = trail_.size();
// Copy the remaining watches_:
while (i < ws.size()) {
ws.set(j++, ws.get(i++));
}
touched_variables_.add(first);
} else {
uncheckedEnqueue(first);
}
}
}
if (ws != null) {
if (ws.size() > j) {
ws.subList(j, ws.size()).clear();
}
}
return result;
}
private boolean propagateImplies(int p) {
TIntList to_add = implies_.get(p);
if (to_add != null) {
for (int i = 0; i < to_add.size(); ++i) {
if (!enqueue(to_add.get(i))) {
touched_variables_.add(to_add.get(i));
return false;
}
}
}
return true;
}
/**
* inline Literal MakeLiteral(Variable var, bool sign) {
* return Literal(2 * var.value() + static_cast(sign));
* int(true) is always 1. And int(false) is always 0
* }
*/
public static int makeLiteral(int var, boolean sign) {
return (2 * var + (sign ? 1 : 0));
}
/**
* inline Literal Negated(Literal p) { return Literal(p.value() ^ 1); }
*/
public static int negated(int l) {
return (l ^ 1);
}
/**
* @param l a literal
* @return true if l is odd (false literal),
* false if l is even (true literal)
*
*/
public static boolean sign(int l) {
return (l & 1) != 0;
}
/**
* inline Variable Var(Literal p) { return Variable(p.value() >> 1); }
*/
public static int var(int l) {
return (l >> 1);
}
/**
* inline Boolean MakeBoolean(bool x) { return Boolean(!x); }
*/
private static Boolean makeBoolean(boolean b) {
return (b ? Boolean.kTrue : Boolean.kFalse);
}
/**
* inline Boolean Xor(Boolean a, bool b) {
* return Boolean((uint8)(a.value() ^ (uint8) b));
* }
*/
private static Boolean xor(Boolean a, boolean b) {
return Boolean.make((byte) (a.value() ^ (b ? 1 : 0)));
}
/**
* Clause -- a simple class for representing a clause
*
*
* @author Charles Prud'homme, Laurent Perron
* @since 12/07/13
*/
public static class Clause {
private int[] literals_;
Clause(int[] ps) {
literals_ = ps.clone();
}
public int size() {
return literals_.length;
}
public int _g(int i) {
return literals_[i];
}
int _s(int pos, int l) {
return literals_[pos] = l;
}
int pos(int l) {
int i = literals_.length - 1;
while (i >= 0 && literals_[i] != l) {
i--;
}
return i;
}
public String toString() {
return Arrays.toString(literals_);
}
}
/**
* // A watcher represent a clause attached to a literal.
*
* (or-tools, booleans.cc, ty L. Perron).
*
* @author Charles Prud'homme
* @since 12/07/13
*/
static class Watcher {
Clause clause;
int blocker;
Watcher(final Clause cr, int l) {
this.clause = cr;
this.blocker = l;
}
}
/**
*
* (or-tools, booleans.cc, ty L. Perron).
*
* @author Charles Prud'homme, Laurent Perron
* @since 12/07/13
*/
enum Boolean {
kTrue((byte) 0),
kFalse((byte) 1),
kUndefined((byte) 2);
byte value;
Boolean(byte value) {
this.value = value;
}
public byte value() {
return value;
}
public static Boolean make(byte b) {
if (b == 0) return kTrue;
else if (b == 1) return kFalse;
else return kUndefined;
}
}
public void copyFrom(SatSolver o) {
// Then, copy all data structures:
this.ok_ = o.ok_;
this.qhead_ = o.qhead_;
this.num_vars_ = o.num_vars_;
this.trail_.resetQuick();
this.trail_.addAll(o.trail_);
this.trail_markers_.resetQuick();
this.trail_markers_.addAll(o.trail_markers_);
this.touched_variables_.resetQuick();
this.touched_variables_.addAll(o.touched_variables_);
this.temporary_add_vector_.resetQuick();
this.temporary_add_vector_.addAll(o.temporary_add_vector_);
for (int k : o.assignment_.keys()) {
this.assignment_.putIfAbsent(k, o.assignment_.get(k));
}
for (int k : o.implies_.keys()) {
TIntArrayList tl = this.implies_.get(k);
if(tl == null){
tl = new TIntArrayList();
this.implies_.put(k, tl);
}else{
tl.resetQuick();
}
tl.addAll(o.implies_.get(k));
}
final HashMap map = new HashMap<>();
this.clauses.clear();
for (Clause cl : o.clauses) {
Clause _cl = new Clause(cl.literals_);
map.put(cl, _cl);
this.clauses.add(_cl);
}
this.learnts.clear();
for (Clause cl : o.learnts) {
Clause _cl = new Clause(cl.literals_);
map.put(cl, _cl);
this.learnts.add(_cl);
}
this.watches_.clear();
for (int k : o.watches_.keys()) {
ArrayList ws = o.watches_.get(k);
ArrayList _ws = new ArrayList<>(ws.size());
for (Watcher w : ws) {
_ws.add(new Watcher(map.get(w.clause), w.blocker));
}
this.watches_.put(k, _ws);
}
}
public long nbclauses() {
return clauses.size() + learnts.size() + implies_.size() / 2;
}
public long numvars() {
return num_vars_;
}
}