All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.chocosolver.sat.MiniSat Maven / Gradle / Ivy

There is a newer version: 4.10.17
Show newest version
/*
 * This file is part of choco-solver, http://choco-solver.org/
 *
 * Copyright (c) 2023, 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.TDoubleArrayList;
import gnu.trove.list.array.TIntArrayList;
import gnu.trove.map.hash.TIntObjectHashMap;
import org.chocosolver.util.ESat;
import org.chocosolver.util.objects.IntHeap;

import java.util.*;

/**
 * 

A MiniSat solver.

*

This is a transposition in Java of MiniSat.

*

MiniSat is a minimalistic, open-source SAT solver, * developed to help researchers and developers alike to get started on SAT. * It is released under the MIT licence.

*

*

 * MiniSat sat = new MiniSat();
 * int a = sat.newVariable();
 * int b = sat.newVariable();
 * sat.addClause(a, b);
 * sat.solve();
 * 
 * 

* * @author Charles Prud'homme * @since 12/07/13 */ public class MiniSat implements SatFactory, Dimacs { // Value of an undefined variable private static final int varUndef = -1; // value of an undefined literal private static final int litUndef = -2; // undefined clause static final Clause CR_Undef = new Clause(new int[0]); static final VarData VD_Undef = new VarData(CR_Undef, -1); // If false, the constraints are already unsatisfiable. No part of // the solver state may be used! public boolean ok_; // List of problem addClauses. public final ArrayList clauses = new ArrayList<>(); // List of learnt addClauses. private final ArrayList learnts = new ArrayList<>(); // 'watches_[lit]' is a list of constraints watching 'lit'(will go // there if literal becomes true). private final TIntObjectHashMap> watches_ = new TIntObjectHashMap<>(); // The current assignments. //TIntObjectHashMap assignment_ = new TIntObjectHashMap<>(); List assignment_ = new ArrayList<>(); // Assignment stack; stores all assignments made in the order they // were made. TIntArrayList trail_ = new TIntArrayList(); // Separator indices for different decision levels in 'trail_'. TIntArrayList trail_markers_ = new TIntArrayList(); // Head of queue(as index into the trail_). int qhead_; // Number of variables int num_vars_; int ccmin_mode = 1; // Controls conflict clause minimization (0=none, 1=basic, 2=deep) int phase_saving = 2; // Controls the level of phase saving (0=none, 1=limited, 2=full) double cla_inc = 1; double var_inc = 1; double var_decay = 0.95; double clause_decay = 0.999; double random_var_freq = 0; int restart_inc = 2; boolean rnd_init_act = true; boolean luby_restart = true; int restart_first = 100; int random_seed = 7; double learntsize_adjust_confl = 100; int learntsize_adjust_cnt = 100; double learntsize_adjust_inc = 1.5; double learntsize_inc = 1.1; double learntsize_factor = 1 / 3d; boolean rnd_pol; int conflict_budget = -1; int propagation_budget = -1; int propagations; int rnd_decisions; boolean asynch_interrupt = false; ArrayList model = new ArrayList<>(); TIntArrayList conflict = new TIntArrayList(); ArrayList vardata = new ArrayList<>(); int conflicts; int decisions; int max_literals; int tot_literals; int dec_vars; int clauses_literals; int learnts_literals; double max_learnts; BitSet seen = new BitSet(); BitSet decision = new BitSet(); BitSet polarity = new BitSet(); TIntArrayList analyze_toclear = new TIntArrayList(); TDoubleArrayList activity = new TDoubleArrayList(); IntHeap order_heap = new IntHeap((a, b) -> activity.get(a) > activity.get(b)); Random rand; private final TIntArrayList temporary_add_vector_ = new TIntArrayList(); public TIntArrayList touched_variables_ = new TIntArrayList(); /** * Create a new instance of MiniSat solver. */ protected MiniSat() { this.ok_ = true; this.qhead_ = 0; num_vars_ = 0; rand = new Random(random_seed); assignment_.add(Boolean.lUndef); // required because variable are numbered from 1 } @Override public MiniSat _me() { return this; } /** * Create and return a new variable * * @return a variable */ public int newVariable() { int v = incrementVariableCounter(); assignment_.add(v, Boolean.lUndef); vardata.add(new VarData(CR_Undef, 0)); //activity .push(0); activity.add(rnd_init_act ? rand.nextDouble() * 0.00001 : 0); seen.clear(v); polarity.set(v); if (!decision.get(v)) dec_vars++; decision.set(v); insertVarOrder(v); return v; } private void insertVarOrder(int v) { if (!order_heap.contains(v) && decision.get(v)) { order_heap.insert(v); } } /** * Add a clause to the solver. * * @param ps a list of literals * @return {@code false} if the Boolean formula is unsatisfiable. */ 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 = litUndef; int j = 0; for (int i = 0; i < ps.size(); i++) { if (valueLit(ps.get(i)) == Boolean.lTrue || ps.get(i) == neg(lit)) { return true; } else if (valueLit(ps.get(i)) != Boolean.lFalse && 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() == CR_Undef); default: Clause cr = new Clause(ps.toArray()); clauses.add(cr); attachClause(cr); break; } return true; } /** * Add a unit clause to the solver. * * @param l a literal * @return {@code false} if the Boolean formula is unsatisfiable. */ 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. * * @param p a literal * @param q a literal * @return {@code false} if the Boolean formula is unsatisfiable. */ public 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. * * @param p a literal * @param q a literal * @return {@code false} if the Boolean formula is unsatisfiable. */ public 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. 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_.set(x, Boolean.lUndef); if (phase_saving > 1 || (phase_saving == 1) && c > trail_markers_.get(trail_markers_.size() - 1)) polarity.set(x, sgn(trail_.get(c))); insertVarOrder(x); } 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. 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.lUndef ? Boolean.lUndef : xor(b, sgn(l)); } // The current number of original clauses. int nClauses() { return clauses.size(); } private int incrementVariableCounter() { return num_vars_++; } int nVars() { return num_vars_; } // Begins a new decision level. void pushTrailMarker() { trail_markers_.add(trail_.size()); } // Enqueue a literal. Assumes value of literal is undefined. void uncheckedEnqueue(int l, Clause from) { assert valueLit(l) == Boolean.lUndef; if (assignment_.get(var(l)) == Boolean.lUndef) { touched_variables_.add(l); } assignment_.set(var(l), makeBoolean(sgn(l))); //vardata.ensureCapacity(var(l)); while (vardata.size() < var(l)) { vardata.add(VD_Undef); } vardata.set(var(l), new VarData(from, trailMarker())); trail_.add(l); } void uncheckedEnqueue(int l) { uncheckedEnqueue(l, CR_Undef); } // Attach a clause to watcher lists. void attachClause(Clause cr) { assert cr.size() > 1; ArrayList l0 = watches_.get(neg(cr._g(0))); if (l0 == null) { l0 = new ArrayList<>(); watches_.put(neg(cr._g(0)), l0); } ArrayList l1 = watches_.get(neg(cr._g(1))); if (l1 == null) { l1 = new ArrayList<>(); watches_.put(neg(cr._g(1)), l1); } l0.add(new Watcher(cr, cr._g(1))); l1.add(new Watcher(cr, cr._g(0))); if (cr.learnt()) learnts_literals += cr.size(); else clauses_literals += cr.size(); } void detachClause(Clause cr) { ArrayList ws = watches_.get(neg(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(neg(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. Clause propagate() { Clause confl = CR_Undef; int num_props = 0; while (qhead_ < trail_.size()) { int p = trail_.get(qhead_++); // 'p' is enqueued fact to propagate. ArrayList ws = watches_.get(p); num_props++; 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.lTrue) { 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 = neg(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.lTrue) { 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.lFalse) { cr._s(1, cr._g(k)); cr._s(k, false_lit); ArrayList lw = watches_.get(neg(cr._g(1))); if (lw == null) { lw = new ArrayList<>(); watches_.put(neg(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.lFalse) { confl = cr; qhead_ = trail_.size(); // Copy the remaining watches_: while (i < ws.size()) { ws.set(j++, ws.get(i++)); } touched_variables_.add(first); } else { uncheckedEnqueue(first, cr); } } } if (ws != null) { if (ws.size() > j) { ws.subList(j, ws.size()).clear(); } } } propagations += num_props; return confl; } /** * A call to this method will attempt to find * an interpretation that satisfies the Boolean formula declared in this. * * @return {@code ESat.TRUE} if such an interpretation is found, * {@code ESat.FALSE} if no interpretation exists, * {@code ESat.UNDEFINED} if a limit was reached. */ public ESat solve() { model.clear(); conflict.clear(); if (!ok_) return ESat.FALSE; max_learnts = nClauses() * learntsize_factor; learntsize_adjust_confl = 100; learntsize_adjust_cnt = (int) learntsize_adjust_confl; ESat status = ESat.UNDEFINED; // Search: int curr_restarts = 0; while (status == ESat.UNDEFINED) { double rest_base = luby_restart ? luby(restart_inc, curr_restarts) : Math.pow(restart_inc, curr_restarts); status = search((int) (rest_base * restart_first)); if (!withinBudget()) break; curr_restarts++; } if (status == ESat.TRUE) { // Extend & copy model: model.ensureCapacity(nVars()); for (int i = 0; i < nVars(); i++) { model.add(valueLit(i)); } } else if (status == ESat.FALSE && conflict.size() == 0) ok_ = false; cancelUntil(0); if (status == ESat.TRUE) { System.out.print("SAT\n"); for (int i = 0; i < nVars(); i++) if (model.get(i) != Boolean.lUndef) System.out.printf("%s%s%d", (i == 0) ? "" : " ", (model.get(i) == Boolean.lTrue) ? "" : "-", i + 1); System.out.print(" 0\n"); } else if (status == ESat.FALSE) System.out.print("UNSAT\n"); else System.out.print("INDET\n"); return status; } /** * Search for a model the specified number of conflicts. * * @param nof_conflicts limit over the number of conflicts * @implNote Use negative value for 'nof_conflicts' indicate infinity. */ ESat search(int nof_conflicts) { assert ok_; int backtrack_level; int conflictC = 0; TIntList learnt_clause = new TIntArrayList(); for (; ; ) { Clause confl = propagate(); if (confl != CR_Undef) { // CONFLICT conflicts++; conflictC++; if (trailMarker() == 0) return ESat.FALSE; learnt_clause.clear(); backtrack_level = analyze(confl, learnt_clause); cancelUntil(backtrack_level); for (int v = 0; v < nVars(); v++) { assert valueVar(v) != Boolean.lUndef || order_heap.contains(v) : v + " not heaped"; } if (learnt_clause.size() == 1) { uncheckedEnqueue(learnt_clause.get(0)); } else { Clause cr = new Clause(learnt_clause.toArray(), true); learnts.add(cr); attachClause(cr); claBumpActivity(cr); uncheckedEnqueue(learnt_clause.get(0), cr); } varDecayActivity(); claDecayActivity(); if (--learntsize_adjust_cnt == 0) { learntsize_adjust_confl *= learntsize_adjust_inc; learntsize_adjust_cnt = (int) learntsize_adjust_confl; max_learnts *= learntsize_inc; } } else { // NO CONFLICT if (nof_conflicts >= 0 && conflictC >= nof_conflicts || !withinBudget()) { // Reached bound on number of conflicts: cancelUntil(0); return ESat.UNDEFINED; } // Simplify the set of problem clauses: if (trailMarker() == 0 && !simplify()) return ESat.FALSE; if (learnts.size() - trail_.size() >= max_learnts) // Reduce the set of learnt clauses: reduceDB(); // New variable decision: decisions++; int next = pickBranchLit(); if (next == litUndef) // Model found: return ESat.TRUE; // Increase decision level and enqueue 'next' pushTrailMarker(); uncheckedEnqueue(next, CR_Undef); } } } int pickBranchLit() { int next = varUndef; // Random decision: if (rand.nextDouble() < random_var_freq && !order_heap.isEmpty()) { next = order_heap.get(rand.nextInt(order_heap.size())); if (valueVar(next) == Boolean.lUndef && decision.get(next)) rnd_decisions++; } // Activity based decision: while (next == varUndef || valueVar(next) != Boolean.lUndef || !decision.get(next)) if (order_heap.isEmpty()) { next = varUndef; break; } else { next = order_heap.removeMin(); } return next == varUndef ? litUndef : makeLiteral(next, rnd_pol ? rand.nextDouble() < 0.5 : polarity.get(next)); } int analyze(Clause confl, TIntList out_learnt) { int pathC = 0; int p = litUndef; // Generate conflict clause: // out_learnt.add(litUndef); // (leave room for the asserting literal) int index = trail_.size() - 1; do { assert (confl != CR_Undef); // (otherwise should be UIP) Clause c = confl; if (c.learnt()) claBumpActivity(c); for (int j = (p == litUndef) ? 0 : 1; j < c.size(); j++) { int q = c._g(j); if (!seen.get(var(q)) && level(var(q)) > 0) { varBumpActivity(var(q)); seen.set(var(q)); if (level(var(q)) >= trailMarker()) pathC++; else out_learnt.add(q); } } // Select next clause to look at: //noinspection StatementWithEmptyBody while (!seen.get(var(trail_.get(index--)))) ; p = trail_.get(index + 1); confl = reason(var(p)); seen.clear(var(p)); pathC--; } while (pathC > 0); out_learnt.set(0, neg(p)); // Simplify conflict clause: // int i, j; analyze_toclear.clear(); analyze_toclear.addAll(out_learnt); /*if (ccmin_mode == 2) { uint32_t abstract_level = 0; for (i = 1; i < out_learnt.size(); i++) abstract_level |= abstractLevel(var(out_learnt.get(i))); // (maintain an abstraction of levels involved in conflict) for (i = j = 1; i < out_learnt.size(); i++) if (reason(var(out_learnt.get(i))) == CR_Undef || !litRedundant(out_learnt.get(i), abstract_level)) out_learnt.set(j++, out_learnt.get(i)); } else */ if (ccmin_mode == 1) { for (i = j = 1; i < out_learnt.size(); i++) { int x = var(out_learnt.get(i)); if (reason(x) == CR_Undef) out_learnt.set(j++, out_learnt.get(i)); else { Clause c = reason(var(out_learnt.get(i))); for (int k = 1; k < c.size(); k++) if (!seen.get(var(c._g(k))) && level(var(c._g(k))) > 0) { out_learnt.set(j++, out_learnt.get(i)); break; } } } } else j = out_learnt.size(); max_literals += out_learnt.size(); out_learnt.subList(j, out_learnt.size()).clear(); tot_literals += out_learnt.size(); // Find correct backtrack level: // int out_btlevel; if (out_learnt.size() == 1) out_btlevel = 0; else { int max_i = 1; // Find the first literal assigned at the next-highest level: for (i = 2; i < out_learnt.size(); i++) if (level(var(out_learnt.get(i))) > level(var(out_learnt.get(max_i)))) max_i = i; // Swap-in this literal at index 1: p = out_learnt.get(max_i); out_learnt.set(max_i, out_learnt.get(1)); out_learnt.set(1, p); out_btlevel = level(var(p)); } for (j = 0; j < analyze_toclear.size(); j++) seen.clear(var(analyze_toclear.get(j))); // ('seen[]' is now cleared) return out_btlevel; } boolean simplify() { assert (trailMarker() == 0); if (!ok_ || propagate() != CR_Undef) return ok_ = false; //System.err.println("TODO : simplify() "); // TODO /*if (nAssigns() == simpDB_assigns || (simpDB_props > 0)) return true; // Remove satisfied clauses: removeSatisfied(learnts); if (remove_satisfied) // Can be turned off. removeSatisfied(clauses); */ rebuildOrderHeap(); /*simpDB_assigns = nAssigns(); simpDB_props = clauses_literals + learnts_literals; // (shouldn't depend on stats really, but it will do for now) */ return true; } private void rebuildOrderHeap() { TIntList vs = new TIntArrayList(); for (int v = 0; v < nVars(); v++) if (decision.get(v) && valueVar(v) == Boolean.lUndef) vs.add(v); order_heap.build(vs); } void reduceDB() { int i, j; double extra_lim = cla_inc / learnts.size(); // Remove any clause below this activity learnts.sort(Comparator.comparingDouble(c -> c.activity)); // Don't delete binary or locked clauses. From the rest, delete clauses from the first half // and clauses with activity smaller than 'extra_lim': for (i = j = 0; i < learnts.size(); i++) { Clause c = learnts.get(i); if (c.size() > 2 && !locked(c) && (i < learnts.size() / 2 || c.activity < extra_lim)) removeClause(learnts.get(i)); else learnts.set(j++, learnts.get(i)); } learnts.subList(j, learnts.size()).clear(); } boolean withinBudget() { return !asynch_interrupt && (conflict_budget < 0 || conflicts < conflict_budget) && (propagation_budget < 0 || propagations < propagation_budget); } Clause reason(int x) { return vardata.get(x).cr; } int level(int x) { return vardata.get(x).level; } boolean locked(Clause c) { return valueLit(c._g(0)) == Boolean.lTrue && reason(var(c._g(0))) != CR_Undef && reason(var(c._g(0))) == c; } void removeClause(Clause cr) { detachClause(cr); // Don't leave pointers to free'd memory! if (locked(cr)) { vardata.get(var(cr._g(0))).cr = CR_Undef; } } void claBumpActivity(Clause c) { if ((c.activity += cla_inc) > 1e20d) { // Rescale: for (int i = 0; i < learnts.size(); i++) { learnts.get(i).activity *= 1e-20d; } cla_inc *= 1e-20d; } } void varBumpActivity(int v) { varBumpActivity(v, var_inc); } void varBumpActivity(int v, double inc) { activity.ensureCapacity(nVars()); double a = activity.get(v); activity.setQuick(v, a + inc); if (a + inc > 1e100) { // Rescale: for (int i = 0; i < nVars(); i++) activity.transformValues(value -> value * 1e-100); var_inc *= 1e-100; } // Update order_heap with respect to new activity: if (order_heap.contains(v)) order_heap.decrease(v); } void varDecayActivity() { var_inc *= (1 / var_decay); } void claDecayActivity() { cla_inc *= (1 / clause_decay); } private static double luby(double y, int x) { // Find the finite subsequence that contains index 'x', and the // size of that subsequence: int size = 1; int seq = 0; while (size < x + 1) { seq++; size = 2 * size + 1; } while (size - 1 != x) { size = (size - 1) >> 1; seq--; x = x % size; } return Math.pow(y, seq); } /////// /** * Make a literal from a variable and a sign * * @param var a variable * @param sign the required sign of the literal * @return a literal */ public static int makeLiteral(int var, boolean sign) { return (2 * var + (sign ? 1 : 0)); } /** * Make a positive literal from a variable * * @param var a variable * @return a positive literal * @implNote Equivalent to call {@code makeLiteral(var, true)}. */ public static int makeLiteral(int var) { return makeLiteral(var, true); } /** * Negate the literal given as a parameter * * @param l a literal * @return its negation */ public static int neg(int l) { return (l ^ 1); } /** * Returns the sign of a given literal * * @param l a literal * @return true if l is odd (false literal), * false if l is even (true literal) */ public static boolean sgn(int l) { // 1 is true, 0 is false return (l & 1) != 0; } /** * Returns the variable of a given literal * * @param l a literal * @return its variable */ public static int var(int l) { return (l >> 1); } static Boolean makeBoolean(boolean b) { return (b ? Boolean.lTrue : Boolean.lFalse); } 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 final int[] literals_; private final boolean learnt; private double activity; Clause(int[] ps, boolean learnt) { literals_ = ps.clone(); this.learnt = learnt; } Clause(int[] ps) { this(ps, false); } public int size() { return literals_.length; } public boolean learnt() { return learnt; } public int _g(int i) { return literals_[i]; } void _s(int pos, int l) { literals_[pos] = l; } 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 */ public enum Boolean { lTrue((byte) 0), lFalse((byte) 1), lUndef((byte) 2); byte value; Boolean(byte value) { this.value = value; } public byte value() { return value; } public static Boolean make(byte b) { if (b == 1) return lTrue; else if (b == 0) return lFalse; else return lUndef; } } static class VarData { Clause cr; int level; public VarData(Clause cr, int level) { this.cr = cr; this.level = level; } } }