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

org.chocosolver.solver.explanations.RuleStore Maven / Gradle / Ivy

There is a newer version: 4.10.16
Show newest version
/**
 * Copyright (c) 2016, Ecole des Mines de Nantes
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *    This product includes software developed by the .
 * 4. Neither the name of the  nor the
 *    names of its contributors may be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY  ''AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL  BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package org.chocosolver.solver.explanations;

import gnu.trove.set.TIntSet;
import org.chocosolver.solver.ICause;
import org.chocosolver.solver.constraints.Propagator;
import org.chocosolver.solver.exception.SolverException;
import org.chocosolver.solver.search.strategy.decision.Decision;
import org.chocosolver.solver.variables.IntVar;
import org.chocosolver.solver.variables.Variable;
import org.chocosolver.solver.variables.events.IEventType;
import org.chocosolver.solver.variables.events.IntEventType;

import static org.chocosolver.solver.variables.events.PropagatorEventType.FULL_PROPAGATION;

/**
 * A RuleStore is a central object in the Asynchronous, Reverse, Low-Intrusive and Lazy explanation engine.
 * It stores a set of rules which enables to compute the explanation of a situation (for instance a conflict) by
 * scanning the events generated on the current branch.
 * The set of rules is dynamically maintained.
 * 

* Created by cprudhom on 09/12/14. * Project: choco. * @author Charles Prud'homme * @since 09/12/14 */ public class RuleStore { /** * No entry value for variable mask */ private static final int NO_ENTRY = Integer.MIN_VALUE; /** * Mask for integer variable modification */ protected static final int DM = 15; /** * Mask for integer variable bounds modification */ protected static final int BD = 7; /** * Mask for integer variable upper bound modification */ protected static final int UB = 5; /** * Mask for integer variable lower bound modification */ protected static final int LB = 3; /** * Mask for integer variable value removal */ protected static final int RM = 1; /** * Set of modification rules */ private Rules cRules; /** * Stores explanation of refuted decisions */ private Explanation[] decRefut; /** * Set to true when user feedback is needed, ie to keep trace of the propagator (in conflict) */ private final boolean saveCauses; /** * Set to true to enable stopping the explanation computation to the first involved decision. * Faster but weaker. */ private final boolean enablePartialExplanation; /** * When conditions are favorable, early iteration stop can be considered */ private boolean preemptedStop; /** * Local-like parameter. * Reference to the last variable popped from the event store. */ private IntVar lastVar; /** * Local-like parameter. * Reference to the last event popped from the event store. */ private IEventType lastEvt; /** * Local-like parameter. * Reference to the last value popped from the event store. */ private int lastValue; /** * Instantiate a rule store to compute explanations * @param saveCauses does it keep trace of the constraints in conflict ? * @param enablePartialExplanation do explanations need to be complete (for DBT or nogood extraction) ? */ public RuleStore(boolean saveCauses, boolean enablePartialExplanation) { this.saveCauses = saveCauses; this.enablePartialExplanation = enablePartialExplanation; decRefut = new Explanation[16]; } /** * Initialize the rulestore for a new explanation * @param expl an explanation */ public void init(Explanation expl) { this.cRules = expl.getRules(); preemptedStop = false; } /** * when conditions are favorable, a preempted stop can be considered: not all events have to be analyzed. * @return return true if early stop occurs. */ public boolean isPreemptedStop() { return preemptedStop; } /** * Return true if the event represented by matches one of the active rules. * * @param idx index in {@code eventStore} of the event to evaluate * @param eventStore set of events * @throws org.chocosolver.solver.exception.SolverException when the type of the variable is neither {@link Variable#BOOL} or {@link Variable#INT}. * @return true if the event in position {@code idx} in {@code eventStore} matches a rule */ public boolean match(final int idx, final ArrayEventStore eventStore) { lastVar = eventStore.getVariable(idx); lastValue = eventStore.getFirstValue(idx); // either the propagator ID, or a value related to the variable event (eg, instantiated value) lastEvt = eventStore.getEventType(idx); if (lastEvt != FULL_PROPAGATION) { // the event is a variable modification int lastVid = lastVar.getId(); int lastMask = cRules.getVmRules(lastVid); if (lastMask == DM) { // only to speed up the entire process return true; } else if (lastMask != NO_ENTRY) { IntEventType ievt = (IntEventType) lastEvt; return matchDomain(lastMask, lastVar, ievt, lastValue, eventStore.getSecondValue(idx), eventStore.getThirdValue(idx)); } return false; } else { // Does it match a propagator activation known rule? return cRules.getPaRules(lastValue); } } /** * Check whether a variable domain matches a rule * * @param ruleMask the current rule mask * @param ivar the integer variable * @param evt the event * @param i1 either instantiated value (IN) or new lb (LB) or new ub (UB) or removed value (RM) * @param i2 either old lb (IN, LB) or old ub (UB) or -1 (RM) * @param i3 either old ub (IN), or -1 (LB, UB, RM) * @return true the variable state matches a rule */ public boolean matchDomain(int ruleMask, IntVar ivar, IntEventType evt, int i1, int i2, int i3) { int vid = ivar.getId(); switch (ruleMask) { case DM: return true; case BD: switch (evt) { case INSTANTIATE: case DECUPP: case INCLOW: return true; case REMOVE: return (i1 < ivar.getLB() || i1 > ivar.getUB()); } case UB: switch (evt) { case INSTANTIATE: return i1 < i3; case DECUPP: return true; case INCLOW: return false; case REMOVE: return i1 > ivar.getUB(); } case LB: switch (evt) { case INSTANTIATE: return i1 > i2; case DECUPP: return false; case INCLOW: return true; case REMOVE: return i1 < ivar.getLB(); } case RM: if (ivar.hasEnumeratedDomain()) { switch (evt) { case INSTANTIATE: return cRules.intersect(i2, i3, vid); case DECUPP: return cRules.intersect(i1, i2, vid); case INCLOW: return cRules.intersect(i2, i1, vid); case REMOVE: return cRules.getVmRemval(vid).contains(i1); } } default:throw new SolverException("Unknown event"); } } /** * Update the rule store, and the explanation, wrt a given event * * @param idx index of the event * @param eventStore the event store * @param explanation the explanation to compute */ @SuppressWarnings({"PointlessBooleanExpression", "ConstantConditions"}) public void update(final int idx, final ArrayEventStore eventStore, Explanation explanation) { assert lastVar == eventStore.getVariable(idx) : "Wrong variable loaded"; assert lastEvt == eventStore.getEventType(idx) : "Wrong event loaded"; if (!lastEvt.equals(FULL_PROPAGATION)) { // Get the cause ICause lastCause = eventStore.getCause(idx); // If the cause is a decision if (lastCause instanceof Decision) { Decision decision = (Decision) lastCause; // If it is a LEFT decision, simply add it if (decision.hasNext() || decision.getArity() == 1) { explanation.addDecision(decision); // if partial explanation is enabled, finding the first decision in conflict is enough if (decision.getArity() > 1 && (preemptedStop |= enablePartialExplanation)) { explanation.setEvtstrIdx(idx); } } else if (decision.getArity() > 1) { // to deal with unary decision (once = true) // Otherwise, get the explanation of the refutation Explanation drr = getDecisionRefutation(decision); assert drr != null : "No explanation for decision refutation :" + decision.toString(); explanation.addCausesAndDecisions(drr); //update decisions and causes into the current explanation explanation.addRules(drr.getRules()); } // if no user feedback is required, ie, no conflict constraints are needed, then.. if (!saveCauses) { // if all decisions, from the current refuted one to ROOT, explained the conflict, // we can stop prematurely the algorithm preemptedStop |= explanation.getDecisions().previousClearBit(decision.getPosition()) == 0; // we do not need to copy the rules, since the explanation is complete, in term of decisions. } } else { assert lastValue == eventStore.getFirstValue(idx) : "Wrong value loaded"; // The cause is not a decision, that is, certainly a propagator // add the cause to the explanation explanation.addCause(lastCause); // then add new rules to the rule store to explain the cause application lastCause.why(this, lastVar, lastEvt, lastValue); } } else { // the event was a propagator activation // 1. add a new rule: explanation of the variable instantiation addFullDomainRule(lastVar); // 2. remove the propagator activation rule, now we know it depends on the variable cRules.paRulesClear(lastValue); } } /** * Add a value removal rule, that is, the event which remove the value needs to be retained. * * @param var the variable to add rule on * @param value the removed value * @return true if a new rule has been added (false = already existing rule) * @throws org.chocosolver.solver.exception.SolverException when the domain is not enumerated */ public boolean addRemovalRule(IntVar var, int value) { if (var.hasEnumeratedDomain()) { int vid = var.getId(); cRules.putMask(vid, RM); TIntSet remvals = cRules.getVmRemval(vid); return remvals.add(value); } else { if (value <= var.getLB()) { // Only value strictly lesser than the current LB are eligible. // One exception, though, when the event tested is the one that fails, then it can be equal to. return addLowerBoundRule(var); } else if (value >= var.getUB()) { // Only value strictly greater than the current UB are eligible. // One exception, though, when the event tested is the one that fails, then it can be equal to. return addUpperBoundRule(var); } else { throw new SolverException("Cannot add REMOVE rule for bounded variable"); } } } /** * Add a full domain rule, that is, any events involving the variable needs to be retained. * * @param var the variable to add rule on * @return true if a new rule has been added (false = already existing rule) */ public boolean addFullDomainRule(IntVar var) { return cRules.putMask(var.getId(), DM); } /** * Add a lower bound rule, that is, any event on the lower bound of the variable needs to be retained * * @param var the variable to add rule on * @return true if a new rule has been added (false = already existing rule) */ public boolean addLowerBoundRule(IntVar var) { return cRules.putMask(var.getId(), LB); } /** * Add a upper bound rule, that is, any event on the upper bound of the variable needs to be retained * * @param var the variable to add rule on * @return true if a new rule has been added (false = already existing rule) */ public boolean addUpperBoundRule(IntVar var) { return cRules.putMask(var.getId(), UB); } /** * Add an upper bound rule and a lower bound rule, that is, any event on the upper bound or the lower bound of the variable needs to be retained * * @param var the variable to add rule on * @return true if a new rule has been added (false = already existing rule) */ public boolean addBoundsRule(IntVar var) { return cRules.putMask(var.getId(), BD); } /** * Return the current rule mask associated to the variable vid * * @param var a variable * @return the current mask or NO_ENTRY */ public int getMask(Variable var) { return cRules.getVmRules(var.getId()); } /** * Add a propagator activation rule * * @param propagator activated propagator * @return true if a new rule has been adde */ public boolean addPropagatorActivationRule(Propagator propagator) { cRules.addPaRules(propagator.getId()); return false; } /** * Store a decision refutation, for future reasoning. * * @param decision refuted decision * @param explanation the explanation of the refutation */ public void storeDecisionRefutation(Decision decision, Explanation explanation) { int w = decision.getPosition(); if (w >= decRefut.length) { Explanation[] tmp = decRefut; decRefut = new Explanation[w + 10]; System.arraycopy(tmp, 0, decRefut, 0, tmp.length); } assert explanation == null || w >= explanation.getDecisions().length(); decRefut[w] = explanation; } /** * Move a decision refutation to the 'to' index * * @param decision a refuted decision * @param to the new index */ public void moveDecisionRefutation(Decision decision, int to) { assert to <= decision.getPosition(); if (to < decision.getPosition()) { decRefut[to] = decRefut[decision.getPosition()]; decRefut[decision.getPosition()] = null; } } /** * Free the explanation related to the decision (for efficiency purpose only) * * @param decision the decision which is going to be forgotten */ public void freeDecisionExplanation(Decision decision) { int w = decision.getPosition(); // when dealing with parallel portfolio, the cut given by another worker // may lead to free a decision which was not explained yet if (w < decRefut.length) { if (decRefut[w] != null) { decRefut[w].recycle(); decRefut[w] = null; } } } /** * Get the explanation associated with a decision refutation * * @param decision a RIGHT branch decision * @return an explanation */ public Explanation getDecisionRefutation(Decision decision) { assert decision.triesLeft() < 2 : decision.toString() + "is not explained yet"; return decRefut[decision.getPosition()]; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy