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

org.chocosolver.solver.learn.ExplanationForSignedClause 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.solver.learn;

import org.chocosolver.solver.ICause;
import org.chocosolver.solver.Model;
import org.chocosolver.solver.constraints.Propagator;
import org.chocosolver.solver.constraints.nary.clauses.ClauseBuilder;
import org.chocosolver.solver.constraints.nary.clauses.ClauseStore;
import org.chocosolver.solver.constraints.nary.clauses.PropSignedClause;
import org.chocosolver.solver.exception.ContradictionException;
import org.chocosolver.solver.exception.SolverException;
import org.chocosolver.solver.search.strategy.assignments.DecisionOperatorFactory;
import org.chocosolver.solver.search.strategy.decision.DecisionPath;
import org.chocosolver.solver.search.strategy.decision.IntDecision;
import org.chocosolver.solver.variables.BoolVar;
import org.chocosolver.solver.variables.IntVar;
import org.chocosolver.solver.variables.events.IntEventType;
import org.chocosolver.util.PoolManager;
import org.chocosolver.util.objects.ValueSortedMap;
import org.chocosolver.util.objects.setDataStructures.iterable.IntIterableRangeSet;

import java.util.HashSet;

/**
 * An implementation of {@link IExplanation} dedicated to learn signed clauses
 * 

* *

* Project: choco-solver. * * @author Charles Prud'homme * @since 27/01/2017. */ public class ExplanationForSignedClause extends IExplanation { /** * Conflicting nodes */ private final ValueSortedMap front; /** * Literals that explains the conflict */ private final HashSet literals; /** * The decision to refute (ie, point to jump to wrt the current decision path). * * @implSpec 0 represents the ROOT node, * any value greater than the decision path is ignored, * otherwise it represents the decision to refute in the decision path. */ private int assertLevel = 0; /** * The implication graph */ private final Implications mIG; private final PoolManager manager; public ExplanationForSignedClause(Implications ig) { front = new ValueSortedMap<>(); literals = new HashSet<>(); manager = new PoolManager<>(); mIG = ig; } /** * @implSpec The */ @Override public void extractConstraint(Model mModel, ClauseStore ngstore) { ClauseBuilder ngb = mModel.getClauseBuilder(); literals.forEach(v -> ngb.put(v, v.getLit().export())); // TODO : improve ngb.buildNogood(mModel); } @Override public void recycle() { front.clear(); literals.forEach(IntVar::flushLit); literals.clear(); assertLevel = Integer.MAX_VALUE; } public void learnSolution(DecisionPath path) { recycle(); if (path.size() > 1) { // skip solution at ROOT node int i = path.size() - 1; IntDecision dec = (IntDecision) path.getDecision(i); // skip refuted bottom decisions while (i > 1 /*0 is ROOT */ && !dec.hasNext() && dec.getArity() > 1) { dec = (IntDecision) path.getDecision(--i); } // build a 'fake' explanation that is able to refute the right decision for (; i > 0 /*0 is ROOT */ ; i--) { dec = (IntDecision) path.getDecision(i); IntIterableRangeSet dom = null; IntVar var = dec.getDecisionVariable(); if (dec.getDecOp().equals(DecisionOperatorFactory.makeIntEq())) { if (dec.hasNext() || dec.getArity() == 1) { dom = universe(); dom.remove(dec.getDecisionValue()); } else { dom = empty(); dom.add(dec.getDecisionValue()); } } else if (dec.getDecOp().equals(DecisionOperatorFactory.makeIntNeq())) { if (dec.hasNext() || dec.getArity() == 1) { dom = empty(); dom.add(dec.getDecisionValue()); } else { dom = universe(); dom.remove(dec.getDecisionValue()); } } else if (dec.getDecOp().equals(DecisionOperatorFactory.makeIntSplit())) { // <= dom = universe(); if (dec.hasNext() || dec.getArity() == 1) { dom.retainBetween(dec.getDecisionValue() + 1, IntIterableRangeSet.MAX); } else { dom.retainBetween(IntIterableRangeSet.MIN, dec.getDecisionValue()); } } else if (dec.getDecOp().equals(DecisionOperatorFactory.makeIntReverseSplit())) { // >= dom = universe(); if (dec.hasNext() || dec.getArity() == 1) { dom.retainBetween(IntIterableRangeSet.MIN, dec.getDecisionValue() - 1); } else { dom.retainBetween(dec.getDecisionValue(), IntIterableRangeSet.MAX); } } var.unionLit(dom, this); } } } /** * From a given conflict, defined by cex and the current implication graph mIG, * this method will compute the signed clause inferred from the conflict. * A call to {@link #extractConstraint(Model, ClauseStore)} will return the computed result. * * @param cex the conflict */ public void learnSignedClause(ContradictionException cex) { recycle(); if (XParameters.PROOF) System.out.print("<-----"); initFront(cex); loop(); if (XParameters.PROOF) System.out.print(">\n"); } private void initFront(ContradictionException cex) { mIG.collectNodesFromConflict(cex, front); // deal with global conflict if (cex.v == null) { if (Propagator.class.isAssignableFrom(cex.c.getClass())) { if (XParameters.PROOF) { System.out.printf("\nCstr: %s\n", cex.c); System.out.print("Pivot: none\n"); } explain(cex.c, -1); } else { throw new UnsupportedOperationException(); } } } private void loop() { int current; do { current = front.pollLastValue(); mIG.predecessorsOf(current, front); if (XParameters.PROOF) { System.out.printf("\nCstr: %s\n", mIG.getCauseAt(current)); System.out.printf("Pivot: %s = %s\n", mIG.getIntVarAt(current).getName(), mIG.getDomainAt(current)); } explain(mIG.getCauseAt(current), current); if (XParameters.PROOF) { System.out.print("Expl: {"); literals.stream() //.sorted(Comparator.comparingInt(Identity::getId)) .forEach(v -> System.out.printf("%s ∈ %s,", v, v.getLit())); System.out.print("}\n-----"); } // filter irrelevant nodes relax(); } while (!stop()); } private void explain(ICause cause, int p) { if (p == -1 || XParameters.DEFAULT_X && Propagator.class.isAssignableFrom(cause.getClass()) && !PropSignedClause.class.isAssignableFrom(cause.getClass()) && !ClauseStore.SignedClause.class.isAssignableFrom(cause.getClass()) ) { Propagator propagator = (Propagator) cause; Propagator.defaultExplain(propagator, p, this); } else { cause.explain(p, this); } // check reification checkReification(cause, p); } private void checkReification(ICause cause, int p) { if (Propagator.class.isAssignableFrom(cause.getClass())) { Propagator propagator = (Propagator) cause; if (propagator.isReified()) { BoolVar b = propagator.reifiedWith(); assert !propagator.isReifiedAndSilent(); mIG.findPredecessor(front, b, p == -1 ? mIG.size() : p); if (b.isInstantiated()) { if (XParameters.FINE_PROOF) System.out.print("Reif: "); b.unionLit(1 - b.getValue(), this); } else { throw new UnsupportedOperationException("Oh nooo!"); } } } } private void relax() { int l, k = -1; while (!front.isEmpty() && (l = front.getLastValue()) != k) { // remove variable in 'front' but not in literals // achieved lazily by only evaluating the right-most one if (!literals.contains(mIG.getIntVarAt(l))) { front.pollLastValue(); } else { IntVar var = mIG.getIntVarAt(l); // cpru deal with : if(VariableUtils.isView(var))? int p = mIG.getPredecessorOf(l); // todo improve // go left as long as the right-most variable in 'front' contradicts 'literals' if (p < l /* to avoid going "before" root */ && var.getLit().disjoint(mIG.getDomainAt(p))) { front.replace(var, p); } } k = l; } } /** * Estimate if conflict analysis can stop: *

    *
  • the rightmost node in conflict is a decision
  • *
  • or it is above the first decision
  • *
* * @return true if the conflict analysis can stop */ private boolean stop() { int max; if (front.isEmpty() || IntEventType.VOID.getMask() == mIG.getEventMaskAt(max = front.getLastValue()) || mIG.getDecisionLevelAt(max) == 1) { if (XParameters.PROOF) System.out.print("\nbacktrack to ROOT\n-----"); assertLevel = mIG.getIntVarAt(0) .getModel() .getSolver() .getDecisionPath() .getDecision(0) .getPosition(); } else /*// check UIP // WARNING: the following code does not work. It cannot be applied stricto-senso from SAT // Since a variable may have been modified more than once in a decision level, unlike SAT { int prev = front.getLowerValue(max); int dl = mIG.getDecisionLevelAt(max); if (prev == -1 || mIG.getDecisionLevelAt(prev) != dl) { // find backtrack point while (max > 0 && !IntDecision.class.isAssignableFrom(mIG.getCauseAt(max).getClass())) { max--; } //assert mIG.getDecisionLevelAt(max) != dl; assert IntDecision.class.isAssignableFrom(mIG.getCauseAt(max).getClass()); if (PROOF) System.out.printf("\nbacktrack to %s\n-----", mIG.getCauseAt(max)); if (ASSERT_NO_LEFT_BRANCH && !((IntDecision) mIG.getCauseAt(max)).hasNext()) { throw new SolverException("Weak explanation found. Try to backjump to :" + mIG.getCauseAt(max) + "\n" + literals); } assertLevel = ((IntDecision) mIG.getCauseAt(max)).getPosition(); } /*/if (IntDecision.class.isAssignableFrom(mIG.getCauseAt(max).getClass())) { if (XParameters.PROOF) System.out.printf("\nbacktrack to %s\n-----", mIG.getCauseAt(max)); if (XParameters.ASSERT_NO_LEFT_BRANCH && !((IntDecision) mIG.getCauseAt(max)).hasNext()) { throw new SolverException("Weak explanation found. Try to backjump to :" + mIG.getCauseAt(max) + "\n" + literals); } assertLevel = ((IntDecision) mIG.getCauseAt(max)).getPosition(); //*/ } return assertLevel != Integer.MAX_VALUE; } /** * Remove {@code var} from {@link #literals} and {@link #front} * * @param var a variable */ public void removeLit(IntVar var) { literals.remove(var); front.remove(var); } /** * Add {@code var} to {@link #literals} * * @param var a variable */ public void addLit(IntVar var) { literals.add(var); } /** * Check if {@code var} is in {@link #literals} * * @param var a variable */ public boolean contains(IntVar var) { return literals.contains(var); } /** * @return the number of literals in this explanation */ public int getCardinality() { return literals.size(); } /** * @return the decision to refute (ie, point to jump to wrt the current decision path). */ public int getAssertingLevel() { return assertLevel; } /** * Return an empty set available (created and returned) or create a new one * * @return a free set */ public IntIterableRangeSet empty() { IntIterableRangeSet set = manager.getE(); if (set == null) { return new IntIterableRangeSet(); } else { set.unlock(); } return set; } public void returnSet(IntIterableRangeSet set) { set.clear(); set.lock(); manager.returnE(set); } /** * @param var a variable * @return a set which contains a copy of the domain of var at its front position */ public IntIterableRangeSet domain(IntVar var) { IntIterableRangeSet set = empty(); set.copyFrom(readDom(var)); return set; } /** * @param var a variable * @return a set which contains a copy of the complement domain of var at its front position * wrt to its root domain */ public IntIterableRangeSet complement(IntVar var) { IntIterableRangeSet set = root(var); set.removeAll(readDom(var)); return set; } /** * @param val a value * @return a set which contains all values after val and val */ public IntIterableRangeSet setDiffVal(int val) { IntIterableRangeSet set = universe(); set.remove(val); return set; } /** * Return (-∞,+∞) set (created and returned). * * @return a full set */ public IntIterableRangeSet universe() { IntIterableRangeSet set = empty(); set.addBetween(IntIterableRangeSet.MIN, IntIterableRangeSet.MAX); return set; } /** * @param var a variable * @return a copy of the root domain of var */ public IntIterableRangeSet root(IntVar var) { IntIterableRangeSet set = empty(); set.copyFrom(mIG.getRootDomain(var)); return set; } public ValueSortedMap getFront() { return front; } public Implications getImplicationGraph() { return mIG; } /** * Return the variable stored in {@link #mIG} at positon {@code p}. * * @param p position of the node to read. * @return the variable at position {@code p} in {@link #mIG}. */ public IntVar readVar(int p) { return mIG.getIntVarAt(p); } /** * Return the event mask stored in {@link #mIG} at positon {@code p}. * * @param p position of the node to read. * @return the event mask at position {@code p} in {@link #mIG} */ public int readMask(int p) { return mIG.getEventMaskAt(p); } /** * Return the value stored in {@link #mIG} at positon {@code p}. * * @param p position of the node to read. * @return the value at position {@code p} in {@link #mIG} */ public int readValue(int p) { return mIG.getValueAt(p); } /** * Return the domain stored in {@link #mIG} at positon {@code p}. * * @param p position of the node to read. * @return the domain at position {@code p} in {@link #mIG} * @implNote read-only method. * Object returned by this method is not intended to be modified. */ public IntIterableRangeSet readDom(int p) { return mIG.getDomainAt(p); } /** * Return the domain stored in {@link #mIG} at positon {@code p}. * * @param var variable to read * @return the domain at position {@code p} in {@link #mIG} * @implNote read-only method. * Object returned by this method is not intended to be modified. * @implSpec position of {@code var} in {@link #mIG} is retrieved * through {@link #front} */ public IntIterableRangeSet readDom(IntVar var) { return mIG.getDomainAt(front.getValue(var)); } public HashSet getLiterals() { return literals; } @Override public String toString() { StringBuilder st = new StringBuilder(); st.append('{'); literals.stream() //.sorted(Comparator.comparingInt(Identity::getId)) .forEach(v -> st.append(v.getName()).append('\u2208').append(v.getLit()).append(',')); st.append('}'); return st.toString(); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy