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

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

There is a newer version: 4.10.16
Show newest version
/**
 * This file is part of choco-solver, http://choco-solver.org/
 *
 * Copyright (c) 2018, 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 gnu.trove.map.hash.TLongIntHashMap;
import org.chocosolver.memory.IStateInt;
import org.chocosolver.sat.SatSolver.Clause;
import org.chocosolver.solver.Model;
import org.chocosolver.solver.constraints.Propagator;
import org.chocosolver.solver.constraints.PropagatorPriority;
import org.chocosolver.solver.exception.ContradictionException;
import org.chocosolver.solver.variables.BoolVar;
import org.chocosolver.solver.variables.IntVar;
import org.chocosolver.solver.variables.Variable;
import org.chocosolver.util.ESat;

import java.util.*;

import static org.chocosolver.sat.SatSolver.*;


/**
 * A propagator to store and propagate no-goods.
 *
 * Created by cprudhom on 20/01/15.
 * Project: choco.
 * @author Charles Prud'homme
 */
public class PropNogoods extends Propagator {

    /**
     * No entry value for {@link #lit2val}, {@link #lit2pos} and {@link #var2pos}.
     */
    private static final int NO_ENTRY = Integer.MAX_VALUE;

    /**
     * Mask to signed value.
     * The 33^th bit is set to 0 for "= value" and to 1 for "<= value".
     */
    private static final long BITOP = 1L << 33L;
    /**
     * Frontier between "= value" (below) and "<= value" (above)
     */
    private static final long FRONTIER = BITOP - Integer.MAX_VALUE;
    /**
     * The underlying SAT solver
     */
    private SatSolver sat_;

    /**
     * Binds couple (variable-value) to a unique literal
     */
    private TLongIntHashMap[] vv2lit;

    /**
     * Binds variable ({@link Variable#getId()} to a unique position
     */
    private int[] var2pos;

    /**
     * Binds literal to variable
     */
    private int[] lit2pos;
    /**
     * Binds literal to value.
     * A value is typically a signed int.
     * The 32^th bit is set to 0 for "= value" and to 1 for "<= value".
     */
    private long[] lit2val;

    /**
     * For comparison with SAT solver trail, to deal properly with backtrack
     */
    private IStateInt sat_trail_;

    /**
     * List of early deduction literals
     */
    private TIntList early_deductions_;

    /**
     * Local-like parameter.
     * To reduce learnt no-goods.
     */
    private BitSet test_eq;

    /**
     * Since there is no domain-clause, a fix point may not be reached by SatSolver itself.
     * Stores all modified variable to make sure a fix point is reached.
     */
    private Deque fp;

    /**
     * Local-like parameter, for #why() method only, lazily initialized.
     */
    private TIntObjectHashMap> inClauses;

    /**
     * Store new added variables when {@link #initialized} is false
     */
    private ArrayList add_var;

    /**
     * Indicates if this is initialized or not
     */
    private boolean initialized = false;

    /**
     * Create a (unique) propagator for no-goods recording and propagation.
     *
     * @param model the model that declares the propagator
     */
    public PropNogoods(Model model) {
        super(new BoolVar[]{model.boolVar(true)}, PropagatorPriority.VERY_SLOW, true);
        this.vars = new IntVar[0];// erase model.ONE from the variable scope

        int k = 16;
        this.vv2lit = new TLongIntHashMap[k];//new TIntObjectHashMap<>(16, .5f, NO_ENTRY);
        this.lit2val = new long[k];//new TIntIntHashMap(16, .5f, NO_ENTRY, NO_ENTRY);
        Arrays.fill(lit2val, NO_ENTRY);
        this.lit2pos = new int[k];//new TIntIntHashMap(16, .5f, NO_ENTRY, NO_ENTRY);
        Arrays.fill(lit2pos, NO_ENTRY);
        this.var2pos = new int[k];//new TIntIntHashMap(16, .5f, NO_ENTRY, NO_ENTRY);
        Arrays.fill(var2pos, NO_ENTRY);
        //TODO: one satsolver per model...
        sat_ = new SatSolver();
        early_deductions_ = new TIntArrayList();
        sat_trail_ = model.getEnvironment().makeInt();
        test_eq = new BitSet();
        fp = new ArrayDeque<>();
        add_var = new ArrayList<>(16);
    }

    @Override
    public void propagate(int evtmask) throws ContradictionException {
        initialize();
        if (!sat_.ok_) fails();
        fp.clear();
        sat_.cancelUntil(0); // to deal with learnt clauses, only called on coarse grain propagation
        storeEarlyDeductions();
        applyEarlyDeductions();
        for (int i = 0; i < vars.length; ++i) {
            doVariableBound(vars[i]);
        }
        while (fp.size() > 0) {
            doVariableBound(fp.pollFirst());
        }
    }

    @Override
    public void propagate(int idxVarInProp, int mask) throws ContradictionException {
        fp.clear();
        doVariableBound(vars[idxVarInProp]);
        while (fp.size() > 0) {
            doVariableBound(fp.pollFirst());
        }
    }

    private void doVariableBound(IntVar var) throws ContradictionException {
        TLongIntHashMap map;
        for (long k : (map = vv2lit[var.getId()]).keys()) {
            int value = ivalue(k);
            if (iseq(k)) {
                if (var.contains(value)) {
                    if (var.isInstantiated()) {
                        VariableBound(map.get(k), true);
                    }
                } else {
                    VariableBound(map.get(k), false);
                }
            } else {
                if (var.getUB() <= value) {
                    VariableBound(map.get(k), true);
                } else if (var.getLB() > value) {
                    VariableBound(map.get(k), false);
                }
            }
        }
    }

    @Override
    public ESat isEntailed() {
        if (vars.length == 0) return ESat.TRUE;
        if (isCompletelyInstantiated()) {
            boolean OK = true;
            int var, val;
            long value;
            boolean sign, eq;
            for (int k : sat_.implies_.keys()) {
                sign = sign(negated(k));
                var = var(k);
                IntVar ivar = vars[lit2pos[var]];
                value = lit2val[var];
                eq = iseq(value);
                val = ivalue(value);
                if ((eq && sign != ivar.contains(val))
                        || (!eq && sign != ivar.getUB() <= val)) {
                    OK &= impliesEntailed(sat_.implies_.get(k));
                }
            }
            OK &= clauseEntailed(sat_.clauses);
            OK &= clauseEntailed(sat_.learnts);
            return ESat.eval(OK);
        }
        return ESat.UNDEFINED;
    }

    private boolean impliesEntailed(TIntList lits) {
        int var;
        long value;
        boolean sign;
        IntVar ivar;
        for (int l : lits.toArray()) {
            sign = sign(l);
            var = var(l);
            ivar = vars[lit2pos[var]];
            value = lit2val[var];
            if (iseq(value)) {
                if (sign != ivar.contains(ivalue(value))) {
                    return false;
                }
            } else {
                if (sign && ivar.getLB() > ivalue(value)) {
                    return false;
                } else if (!sign && ivar.getUB() <= ivalue(value)) {
                    return false;
                }
            }
        }
        return true;
    }

    private boolean clauseEntailed(ArrayList clauses) {
        int lit, var;
        long value;
        boolean sign;
        IntVar ivar;
        for (Clause c : clauses) {
            int cnt = 0;
            for (int i = 0; i < c.size(); i++) {
                lit = c._g(i);
                sign = sign(lit);
                var = var(lit);
                ivar = vars[lit2pos[var]];
                value = lit2val[var];
                if (iseq(value)) {
                    if (sign != ivar.contains(ivalue(value))) {
                        cnt++;
                    } else break;
                } else {
                    if (sign && ivar.getLB() > ivalue(value)) {
                        cnt++;
                    } else if (!sign && ivar.getUB() <= ivalue(value)) {
                        cnt++;
                    } else break;
                }
            }
            if (cnt == c.size()) return false;
        }
        return true;
    }


    /**
     * @param v a value
     * @return true if the value encodes '=', false if it encodes '≤'.
     */
    static boolean iseq(long v) {
        return v < FRONTIER;
    }

    /**
     * @param v a value
     * @return v with `≤' information encoded into it
     */
    static long leq(int v) {
        return v + BITOP;
    }

    /**
     * @param v a value
     * @return the value without the '=' or '≤' information.
     */
    static int ivalue(long v) {
        return (int)(iseq(v)?v:v - BITOP);
    }


    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    /**
     * Initializes this propagator
     */
    public void initialize() {
        if (!initialized) {
            if (add_var.size() > 0) {
                addVariable(add_var.toArray(new IntVar[0]));
            }
            add_var.clear();
            this.initialized = true;
        }
    }

    /**
     * Creates or returns if already existing, the literal corresponding to :
     * 

* ivar (eq?"=":"<=") value *

* where "=" is selected if eq is true, "<=" otherwise. *

* The negation of the literal is managed outside. * * @param ivar an integer variable * @param value a value * @param eq set to true to select "=", to false to select "<=". * @return the literal corresponding to ivar (eq?"=":"<=") value */ public int Literal(IntVar ivar, int value, boolean eq) { // TODO: deal with BoolVar int vid = ivar.getId(); int var; TLongIntHashMap map; if (vid >= vv2lit.length) { TLongIntHashMap[] tmp = vv2lit; vv2lit = new TLongIntHashMap[vid + 1]; System.arraycopy(tmp, 0, vv2lit, 0, tmp.length); int[] tmpi = var2pos; var2pos = new int[vid + 1]; System.arraycopy(tmpi, 0, var2pos, 0, tmpi.length); Arrays.fill(var2pos, tmpi.length, vid + 1, NO_ENTRY); } if ((map = vv2lit[vid]) == null) { map = new TLongIntHashMap(16, .5f, NO_ENTRY, NO_ENTRY); vv2lit[vid] = map; } int pos; if ((pos = var2pos[vid]) == NO_ENTRY) { if(initialized) { addVariable(ivar); pos = vars.length - 1; }else { add_var.add(ivar); pos = add_var.size() - 1; } var2pos[vid] = pos; } long lvalue = eq ? value : leq(value); if ((var = map.get(lvalue)) == NO_ENTRY) { var = sat_.newVariable(); map.put(lvalue, var); if (var >= lit2pos.length) { int[] itmp = lit2pos; lit2pos = new int[var + 1]; System.arraycopy(itmp, 0, lit2pos, 0, itmp.length); Arrays.fill(lit2pos, itmp.length, var + 1, NO_ENTRY); long[] ltmp = lit2val; lit2val = new long[var + 1]; System.arraycopy(ltmp, 0, lit2val, 0, ltmp.length); Arrays.fill(lit2val, ltmp.length, var + 1, NO_ENTRY); } lit2pos[var] = pos; lit2val[var] = lvalue; } return makeLiteral(var, true); } /** * var points a clause variable whom value is now to be val. * * @param index a clause var * @param sign the sign of the lit * @throws ContradictionException if inconsistency is detected */ void VariableBound(int index, boolean sign) throws ContradictionException { try { if (sat_trail_.get() < sat_.trailMarker()) { sat_.cancelUntil(sat_trail_.get()); assert (sat_trail_.get() == sat_.trailMarker()); } int lit = makeLiteral(index, sign); if (!sat_.propagateOneLiteral(lit)) { // force failure by removing the last value: flip the sign // explanations require doing the failure doReduce(negated(lit)); } else { sat_trail_.set(sat_.trailMarker()); for (int i = 0; i < sat_.touched_variables_.size(); ++i) { lit = sat_.touched_variables_.get(i); doReduce(lit); } } } finally { sat_.touched_variables_.resetQuick(); // issue#327 } } /** * Reduce the variable. * * @param lit literal to assign * @throws ContradictionException if reduction leads to failure */ void doReduce(int lit) throws ContradictionException { int var = var(lit); long value = lit2val[var]; IntVar ivar = vars[lit2pos[var]]; if (iseq(value)) { if (sign(lit)) { ivar.instantiateTo(ivalue(value), this); } else { if (ivar.removeValue(ivalue(value), this)) { fp.push(ivar); } } } else { if (sign(lit)) { if (ivar.updateUpperBound(ivalue(value), this)) { fp.push(ivar); } } else if (ivar.updateLowerBound(ivalue(value) + 1, this)) { fp.push(ivar); } } } /** * Add clauses to ensure domain consistency, that is: *

    *
  1. * [ x ≤ d ] ⇒ [ x ≤ d +1 ] *
  2. *
  3. * [ x = d ] ⇔ ([ x ≤ d ] ∧ ¬[ x ≤ d + 1]) *
  4. *
* @param var an integer variable * @return if clauses have been successfully added to the store. */ boolean declareDomainNogood(IntVar var) { int size = var.getDomainSize(); int[] lits = new int[size * 2]; // 1. generate lits int a = var.getLB(); int ub = var.getUB(); int i = 0; while (a <= ub) { lits[i] = Literal(var, a, true); lits[i + size] = Literal(var, a, false); i++; a = var.nextValue(a); } TIntList clauses = new TIntArrayList(); boolean add = false; // 2. add clauses // 2a. [ x <= d ] => [ x <= d +1 ] for (int j = size; j < 2 * size - 1; j++) { clauses.add(negated(lits[j])); clauses.add(lits[j + 1]); add |= sat_.addClause(clauses); clauses.clear(); } // 2b. [ x = d ] <=> [ x <= d ] and not[ x <= d +1 ] for (int k = 0; k < size - 1; k++) { // [ x = d ] or not[ x <= d ] or [ x <= d +1 ] clauses.add(lits[k]); clauses.add(negated(lits[size + k])); clauses.add(lits[size + k + 1]); add |= sat_.addClause(clauses); clauses.clear(); // not [ x = d ] or [ x <= d ] clauses.add(negated(lits[k])); clauses.add(lits[size + k]); add |= sat_.addClause(clauses); clauses.clear(); // not [ x = d ] or not[ x <= d +1 ] clauses.add(negated(lits[k])); clauses.add(negated(lits[size + k + 1])); add |= sat_.addClause(clauses); clauses.clear(); } storeEarlyDeductions(); return add; } /** * Add unit clause to no-goods store * * @param p unit clause * @return false if failure is detected */ @SuppressWarnings("unused") public boolean addNogood(int p) { boolean result = sat_.addClause(p); storeEarlyDeductions(); return result; } /** * Add unit clause to no-goods store * * @param lits clause * @return false if failure is detected */ @SuppressWarnings("unused") public boolean addNogood(TIntList lits) { boolean result = sat_.addClause(lits); storeEarlyDeductions(); return result; } /** * Add learnt clause to no-goods store * * @param lits clause */ public void addLearnt(int... lits) { sat_.learnClause(lits); // early deductions of learnt clause may lead to incorrect behavior on backtrack // since early deduction is not backtrackable. forcePropagationOnBacktrack(); // issue#327 // compare the current clauses with the previous stored one, // just in case the current one dominates the previous none if (sat_.nLearnt() > 1) { Clause last = sat_.learnts.get(sat_.learnts.size() - 1); test_eq.clear(); for (int i = last.size() - 1; i >= 0; i--) { test_eq.set(last._g(i)); } for (int c = sat_.learnts.size() - 2; c >= 0; c--) { int s = test_eq.cardinality(); Clause prev = sat_.learnts.get(c); if (last.size() > 1 && last.size() < prev.size()) { for (int i = prev.size() - 1; i >= 0; i--) { s -= test_eq.get(prev._g(i)) ? 1 : 0; } if (s == 0) { // then last dominates prev sat_.detachLearnt(c); } } } } } private void storeEarlyDeductions() { for (int i = 0; i < sat_.touched_variables_.size(); ++i) { int lit = sat_.touched_variables_.get(i); early_deductions_.add(lit); } sat_.touched_variables_.resetQuick(); } /** * Apply early deduction * * @throws ContradictionException if it fails */ private void applyEarlyDeductions() throws ContradictionException { for (int i = 0; i < early_deductions_.size(); ++i) { int lit = early_deductions_.get(i); doReduce(lit); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy