org.chocosolver.solver.variables.IntVar Maven / Gradle / Ivy
/*
* This file is part of choco-solver, http://choco-solver.org/
*
* Copyright (c) 2022, 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.variables;
import org.chocosolver.solver.ICause;
import org.chocosolver.solver.exception.ContradictionException;
import org.chocosolver.solver.expression.discrete.arithmetic.ArExpression;
import org.chocosolver.solver.learn.ExplanationForSignedClause;
import org.chocosolver.solver.learn.XParameters;
import org.chocosolver.solver.variables.delta.IIntDeltaMonitor;
import org.chocosolver.solver.variables.events.IEventType;
import org.chocosolver.solver.variables.impl.siglit.SignedLiteral;
import org.chocosolver.util.iterators.DisposableRangeIterator;
import org.chocosolver.util.iterators.DisposableValueIterator;
import org.chocosolver.util.objects.setDataStructures.iterable.IntIterableRangeSet;
import org.chocosolver.util.objects.setDataStructures.iterable.IntIterableSet;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.function.Consumer;
import java.util.function.IntConsumer;
import java.util.stream.IntStream;
import java.util.stream.StreamSupport;
/**
* Interface for integer variables. Provides every required services.
* The domain is explicitly represented but is not (and should not be) accessible from outside.
*
*
* CPRU r544: remove default implementation
*
* @author Charles Prud'homme
* @since 18 nov. 2010
*/
public interface IntVar extends ICause, Variable, Iterable, ArExpression {
/**
* Provide a minimum value for integer variable lower bound.
* Do not prevent from underflow, but may avoid it, somehow.
*/
int MIN_INT_BOUND = Integer.MIN_VALUE / 100;
/**
* Provide a minimum value for integer variable lower bound.
* Do not prevent from overflow, but may avoid it, somehow.
*/
int MAX_INT_BOUND = Integer.MAX_VALUE / 100;
/**
* Removes value
from the domain of this
. The instruction comes from propagator
.
*
* - If
value
is out of the domain, nothing is done and the return value is false
,
* - if removing
value
leads to a dead-end (domain wipe-out),
* a ContradictionException
is thrown,
* - otherwise, if removing
value
from the domain can be done safely,
* the event type is created (the original event can be promoted) and observers are notified
* and the return value is true
*
*
* @param value value to remove from the domain (int)
* @param cause removal releaser
* @return true if the value has been removed, false otherwise
* @throws ContradictionException if the domain become empty due to this action
*/
boolean removeValue(int value, ICause cause) throws ContradictionException;
/**
* Removes value
from the domain of this
. The instruction comes from propagator
.
*
* This method deals with value
as long.
* If such a long can be safely cast to an int, this falls back to regular case (int).
* Otherwise, it can either trivially do nothing or fail.
*
*
* - If
value
is out of the domain, nothing is done and the return value is false
,
* - if removing
value
leads to a dead-end (domain wipe-out),
* a ContradictionException
is thrown,
* - otherwise, if removing
value
from the domain can be done safely,
* the event type is created (the original event can be promoted) and observers are notified
* and the return value is true
*
*
* @param value value to remove from the domain (int)
* @param cause removal releaser
* @return true if the value has been removed, false otherwise
* @throws ContradictionException if the domain become empty due to this action
*/
default boolean removeValue(long value, ICause cause) throws ContradictionException{
if ((int) value != value) { // cannot be cast to an int
return false;
} else {
return removeValue((int) value, cause);
}
}
/**
* Removes the value in values
from the domain of this
. The instruction comes from propagator
.
*
* - If all values are out of the domain, nothing is done and the return value is
false
,
* - if removing a value leads to a dead-end (domain wipe-out),
* a
ContradictionException
is thrown,
* - otherwise, if removing the
values
from the domain can be done safely,
* the event type is created (the original event can be promoted) and observers are notified
* and the return value is true
*
*
* @param values set of ordered values to remove
* @param cause removal release
* @return true if at least a value has been removed, false otherwise
* @throws ContradictionException if the domain become empty due to this action
*/
boolean removeValues(IntIterableSet values, ICause cause) throws ContradictionException;
/**
* Removes all values from the domain of this
except those in values
. The instruction comes from propagator
.
*
* - If all values are out of the domain,
* a
ContradictionException
is thrown,
* - if the domain is a subset of values,
* nothing is done and the return value is
false
,
* - otherwise, if removing all values but
values
from the domain can be done safely,
* the event type is created (the original event can be promoted) and observers are notified
* and the return value is true
*
*
* @param values set of ordered values to keep in the domain
* @param cause removal release
* @return true if a at least a value has been removed, false otherwise
* @throws ContradictionException if the domain become empty due to this action
*/
boolean removeAllValuesBut(IntIterableSet values, ICause cause) throws ContradictionException;
/**
* Removes values between [from, to
] from the domain of this
. The instruction comes from propagator
.
*
* - If union between values and the current domain is empty, nothing is done and the return value is
false
,
* - if removing a
value
leads to a dead-end (domain wipe-out),
* a ContradictionException
is thrown,
* - otherwise, if removing at least a
value
from the domain can be done safely,
* the event type is created (the original event can be promoted) and observers are notified
* and the return value is true
*
*
* @param from lower bound of the interval to remove (int)
* @param to upper bound of the interval to remove(int)
* @param cause removal releaser
* @return true if the value has been removed, false otherwise
* @throws ContradictionException if the domain become empty due to this action
*/
boolean removeInterval(int from, int to, ICause cause) throws ContradictionException;
/**
* Instantiates the domain of this
to value
. The instruction comes from propagator
.
*
* - If the domain of
this
is already instantiated to value
,
* nothing is done and the return value is false
,
* - If the domain of
this
is already instantiated to another value,
* then a ContradictionException
is thrown,
* - Otherwise, the domain of
this
is restricted to value
and the observers are notified
* and the return value is true
.
*
*
* @param value instantiation value (int)
* @param cause instantiation releaser
* @return true if the instantiation is done, false otherwise
* @throws ContradictionException if the domain become empty due to this action
*/
boolean instantiateTo(int value, ICause cause) throws ContradictionException;
/**
* Instantiates the domain of this
to value
. The instruction comes from propagator
.
*
* This method deals with value
as long.
* If such a long can be safely cast to an int, this falls back to regular case (int).
* Otherwise, it can either trivially do nothing or fail.
*
*
* - If the domain of
this
is already instantiated to value
,
* nothing is done and the return value is false
,
* - If the domain of
this
is already instantiated to another value,
* then a ContradictionException
is thrown,
* - Otherwise, the domain of
this
is restricted to value
and the observers are notified
* and the return value is true
.
*
*
* @param value instantiation value (int)
* @param cause instantiation releaser
* @return true if the instantiation is done, false otherwise
* @throws ContradictionException if the domain become empty due to this action
*/
default boolean instantiateTo(long value, ICause cause) throws ContradictionException{
if ((int) value != value) { // cannot be cast to an int
return instantiateTo(value < getLB() ? getLB() - 1 : getUB() + 1, cause);
} else {
return instantiateTo((int) value, cause);
}
}
/**
* Updates the lower bound of the domain of this
to value
.
* The instruction comes from propagator
.
*
* - If
value
is smaller than the lower bound of the domain, nothing is done and the return value is false
,
* - if updating the lower bound to
value
leads to a dead-end (domain wipe-out),
* a ContradictionException
is thrown,
* - otherwise, if updating the lower bound to
value
can be done safely,
* the event type is created (the original event can be promoted) and observers are notified
* and the return value is true
*
*
* @param value new lower bound (included)
* @param cause updating releaser
* @return true if the lower bound has been updated, false otherwise
* @throws ContradictionException if the domain become empty due to this action
*/
boolean updateLowerBound(int value, ICause cause) throws ContradictionException;
/**
* Updates the lower bound of the domain of this
to value
.
* The instruction comes from propagator
.
*
* This method deals with value
as long.
* If such a long can be safely cast to an int, this falls back to regular case (int).
* Otherwise, it can either trivially do nothing or fail.
*
*
* - If
value
is smaller than the lower bound of the domain, nothing is done and the return value is false
,
* - if updating the lower bound to
value
leads to a dead-end (domain wipe-out),
* a ContradictionException
is thrown,
* - otherwise, if updating the lower bound to
value
can be done safely,
* the event type is created (the original event can be promoted) and observers are notified
* and the return value is true
*
*
* @param value new lower bound (included)
* @param cause updating releaser
* @return true if the lower bound has been updated, false otherwise
* @throws ContradictionException if the domain become empty due to this action
*/
default boolean updateLowerBound(long value, ICause cause) throws ContradictionException {
if ((int) value != value) { // cannot be cast to an int
if (value < getLB()) {
return false;
} else { // then value >> getLB, this fails
return updateLowerBound(getUB() + 1, cause);
}
} else {
return updateLowerBound((int) value, cause);
}
}
/**
* Updates the upper bound of the domain of this
to value
.
* The instruction comes from propagator
.
*
* - If
value
is greater than the upper bound of the domain, nothing is done and the return value is false
,
* - if updating the upper bound to
value
leads to a dead-end (domain wipe-out),
* a ContradictionException
is thrown,
* - otherwise, if updating the upper bound to
value
can be done safely,
* the event type is created (the original event can be promoted) and observers are notified
* and the return value is true
*
*
* @param value new upper bound (included)
* @param cause update releaser
* @return true if the upper bound has been updated, false otherwise
* @throws ContradictionException if the domain become empty due to this action
*/
boolean updateUpperBound(int value, ICause cause) throws ContradictionException;
/**
* Updates the upper bound of the domain of this
to value
.
* The instruction comes from propagator
.
*
* This method deals with value
as long.
* If such a long can be safely cast to an int, this falls back to regular case (int).
* Otherwise, it can either trivially do nothing or fail.
*
*
* - If
value
is greater than the upper bound of the domain, nothing is done and the return value is false
,
* - if updating the upper bound to
value
leads to a dead-end (domain wipe-out),
* a ContradictionException
is thrown,
* - otherwise, if updating the upper bound to
value
can be done safely,
* the event type is created (the original event can be promoted) and observers are notified
* and the return value is true
*
*
* @param value new upper bound (included)
* @param cause update releaser
* @return true if the upper bound has been updated, false otherwise
* @throws ContradictionException if the domain become empty due to this action
*/
default boolean updateUpperBound(long value, ICause cause) throws ContradictionException {
if ((int) value != value) { // cannot be cast to an int
if (value > getUB()) {
return false;
} else { // then value << getUB, this fails
return updateUpperBound(getLB() - 1, cause);
}
} else {
return updateUpperBound((int) value, cause);
}
}
/**
* Updates the lower bound and the upper bound of the domain of this
to, resp. lb
and ub
.
* The instruction comes from propagator
.
*
*
* - If
lb
is smaller than the lower bound of the domain
* and ub
is greater than the upper bound of the domain,
*
* nothing is done and the return value is false
,
* - if updating the lower bound to
lb
, or updating the upper bound to ub
leads to a dead-end (domain wipe-out),
* or if lb
is strictly greater than ub
,
* a ContradictionException
is thrown,
* - otherwise, if updating the lower bound to
lb
and/or the upper bound to ub
* can be done safely can be done safely,
* the event type is created (the original event can be promoted) and observers are notified
* and the return value is true
*
*
* @param lb new lower bound (included)
* @param ub new upper bound (included)
* @param cause update releaser
* @return true if the upper bound has been updated, false otherwise
* @throws ContradictionException if the domain become empty due to this action
*/
boolean updateBounds(int lb, int ub, ICause cause) throws ContradictionException;
/**
* Checks if a value v
belongs to the domain of this
*
* @param value int
* @return true
if the value belongs to the domain of this
, false
otherwise.
*/
boolean contains(int value);
/**
* Checks wether this
is instantiated to val
*
* @param value int
* @return true if this
is instantiated to val
, false otherwise
*/
boolean isInstantiatedTo(int value);
/**
* Retrieves the current value of the variable if instantiated, otherwier the lower bound.
*
* @return the current value (or lower bound if not yet instantiated).
*/
int getValue();
/**
* Retrieves the lower bound of the variable
*
* @return the lower bound
*/
int getLB();
/**
* Retrieves the upper bound of the variable
*
* @return the upper bound
*/
int getUB();
/**
* Returns the range of this domain, that is, the difference between the upper bound and the lower bound.
*
* @return the range of this domain
*/
int getRange();
/**
* Returns the first value just after v in this
which is in the domain.
* If no such value exists, returns Integer.MAX_VALUE;
*
* To iterate over the values in a IntVar
,
* use the following loop:
*
*
* int ub = iv.getUB();
* for (int i = iv.getLB(); i <= ub; i = iv.nextValue(i)) {
* // operate on value i here
* }
*
* @param v the value to start checking (exclusive)
* @return the next value in the domain
*/
int nextValue(int v);
/**
* Returns the first value just after v in this
which is out of the domain.
* If v is less than or equal to {@link #getLB()}-2, returns v + 1,
* if v is greater than or equal to {@link #getUB()}, returns v + 1.
*
* @param v the value to start checking (exclusive)
* @return the next value out of the domain
*/
int nextValueOut(int v);
/**
* Returns the previous value just before v in this
.
* If no such value exists, returns Integer.MIN_VALUE;
*
* To iterate over the values in a IntVar
,
* use the following loop:
*
*
* int lb = iv.getLB();
* for (int i = iv.getUB(); i >= lb; i = iv.previousValue(i)) {
* // operate on value i here
* }
*
* @param v the value to start checking (exclusive)
* @return the previous value in the domain
*/
int previousValue(int v);
/**
* Returns the first value just before v in this
which is out of the domain.
* If v is greater than or equal to {@link #getUB()}+2, returns v - 1,
* if v is less than or equal to {@link #getLB()}, returns v - 1.
*
* @param v the value to start checking (exclusive)
* @return the previous value out of the domain
*/
int previousValueOut(int v);
/**
* Retrieves an iterator over values of this
.
*
* The values can be iterated in a bottom-up way or top-down way.
*
* To bottom-up iterate over the values in a IntVar
,
* use the following loop:
*
*
* DisposableValueIterator vit = var.getValueIterator(true);
* while(vit.hasNext()){
* int v = vit.next();
* // operate on value v here
* }
* vit.dispose();
*
*
* To top-down iterate over the values in a IntVar
,
* use the following loop:
*
*
* DisposableValueIterator vit = var.getValueIterator(false);
* while(vit.hasPrevious()){
* int v = vit.previous();
* // operate on value v here
* }
* vit.dispose();
*
* Using both previous and next can lead to unexpected behaviour.
*
* @param bottomUp way to iterate over values. true
means from lower bound to upper bound,
* false
means from upper bound to lower bound.
* @return a disposable iterator over values of this
.
*/
DisposableValueIterator getValueIterator(boolean bottomUp);
/**
* Retrieves an iterator over ranges (or intervals) of this
.
*
* The ranges can be iterated in a bottom-up way or top-down way.
*
* To bottom-up iterate over the values in a IntVar
,
* use the following loop:
*
*
* DisposableRangeIterator rit = var.getRangeIterator(true);
* while (rit.hasNext()) {
* int from = rit.min();
* int to = rit.max();
* // operate on range [from,to] here
* rit.next();
* }
* rit.dispose();
*
* To top-down iterate over the values in a IntVar
,
* use the following loop:
*
*
* DisposableRangeIterator rit = var.getRangeIterator(false);
* while (rit.hasPrevious()) {
* int from = rit.min();
* int to = rit.max();
* // operate on range [from,to] here
* rit.previous();
* }
* rit.dispose();
*
* Using both previous and next can lead to unexpected behaviour.
*
* @param bottomUp way to iterate over ranges. true
means from lower bound to upper bound,
* false
means from upper bound to lower bound.
* @return a disposable iterator over ranges of this
.
*/
DisposableRangeIterator getRangeIterator(boolean bottomUp);
/**
* Indicates wether (or not) this
has an enumerated domain (represented in extension)
* or not (only bounds)
*
* @return true
if the domain is enumerated, false
otherwise.
*/
boolean hasEnumeratedDomain();
/**
* Allow to monitor removed values of this
.
*
* @param propagator the cause that requires to monitor delta
* @return a delta monitor
*/
IIntDeltaMonitor monitorDelta(ICause propagator);
/**
* @return true iff the variable has a binary domain
*/
boolean isBool();
@Override
default void forEachIntVar(Consumer action) {
action.accept(this);
}
/**
* @param evt original event
* @return transforms the original event wrt this IntVar
*/
default IEventType transformEvent(IEventType evt) {
return evt;
}
@Override
default IntVar intVar() {
return this;
}
@Override
default int getNoChild() {
return 1;
}
@Override
default boolean isExpressionLeaf() {
return true;
}
/**
* Create the signed literal.
* @param rootDomain the domain at root node
*/
void createLit(IntIterableRangeSet rootDomain);
/**
* @return the current signed literal
* @implSpec a call to {@link #createLit(IntIterableRangeSet)} is required
*/
SignedLiteral getLit();
/**
* Flush the current signed literal
*/
default void flushLit() {
this.getLit().clear();
}
/**
* Perform the union of this internal signed literal and {@code set}:
* {@code lit} = {@code set} ∪ {@code lit}
*
* @param set set of ints to join this signed literal with
* @param explanation the explanation
* @implNote {@code set} is considered as read-only and is not intended to modified.
* Before this methods ends, {@code set} is recycled and must not be used.
* @apiNote This method is supposed to be called on non-pivot variables only.
* It can be called many times on the same variable while explaning a cause
* since it applies a union operation on signed literal.
*/
default void unionLit(IntIterableRangeSet set, ExplanationForSignedClause explanation) {
if (XParameters.FINE_PROOF) {
System.out.printf("%s: %s ∪ %s\n", getName(), getLit(), set);
}
this.getLit().addAll(set);
if (this.getLit().isEmpty()) {
if (XParameters.FINE_PROOF) {
System.out.print("skip\n");
}
} else {
if (XParameters.FINE_PROOF) {
System.out.print("\n");
}
explanation.addLit(this);
}
explanation.returnSet(set);
}
/**
* Perform the union of this internal signed literal and the range [{@code l}, {@code u}]:
*
{@code lit} = [{@code l}, {@code u}] ∪ {@code lit}
*
* @param l inclusive lower bound
* @param u inclusive upper bound
* @param explanation the explanation
* @apiNote This method is supposed to be called on non-pivot variables only.
* It can be called many times on the same variable while explaning a cause
* since it applies a union operation on signed literal.
*/
default void unionLit(int l, int u, ExplanationForSignedClause explanation) {
if (XParameters.FINE_PROOF) {
System.out.printf("%s: %s ∪ [%d, %d]", getName(), getLit(), l, u);
}
this.getLit().addBetween(l, u);
if (this.getLit().isEmpty()) {
if (XParameters.FINE_PROOF) {
System.out.print("-- skip\n");
}
} else {
if (XParameters.FINE_PROOF) {
System.out.print("\n");
}
explanation.addLit(this);
}
}
/**
* Perform the union of this internal signed literal and {{@code v}}:
*
{@code lit} = {{@code v}} ∪ {@code lit}
*
* @param v int value
* @param explanation the explanation
* @apiNote This method is supposed to be called on non-pivot variables only.
* It can be called many times on the same variable while explaning a cause
* since it applies a union operation on signed literal.
*/
default void unionLit(int v, ExplanationForSignedClause explanation) {
if (XParameters.FINE_PROOF) {
System.out.printf("%s: %s ∪ {%d}", getName(), getLit(), v);
}
this.getLit().add(v);
if (this.getLit().isEmpty()) {
if (XParameters.FINE_PROOF) {
System.out.print("skip\n");
}
} else {
if (XParameters.FINE_PROOF) {
System.out.print("\n");
}
explanation.addLit(this);
}
}
/**
* Perform the intersection of this internal signed literal and {@code set}:
*
{@code lit} = {@code set} ∩ {@code lit}
*
* @param set set of ints to cross this signed literal with.
* @param explanation the explanation
* @implNote {@code set} is considered as read-only and is not intended to modified.
* Before this methods ends, {@code set} is recycled and must no be used.
* @apiNote This method is supposed to be called on pivot variables only.
* It can be called only once on the pivot variable while explaining a cause
* since it applies an intersection operation on signed literal.
*/
default void intersectLit(IntIterableRangeSet set, ExplanationForSignedClause explanation) {
if (explanation.contains(this)) {
if (XParameters.FINE_PROOF) {
System.out.printf("%s: %s ∩ %s", getName(), getLit(), set);
}
this.getLit().retainAll(set);
if (this.getLit().isEmpty()) {
if (XParameters.FINE_PROOF) {
System.out.print(" -- remove");
}
explanation.removeLit(this);
}
if (XParameters.FINE_PROOF) {
System.out.print("\n");
}
explanation.returnSet(set);
} else {
// this is the first occurrence of the variable during explanation
this.unionLit(set, explanation);
}
}
/**
* Perform the intersection of this internal signed literal and the range [{@code l}, {@code u}]:
*
{@code lit} = [{@code l}, {@code u}] ∩ {@code lit}
*
* @param l inclusive lower bound
* @param u inclusive upper bound
* @param explanation the explanation
* @apiNote This method is supposed to be called on pivot variables only.
* It can be called only once on the pivot variable while explaining a cause
* since it applies an intersection operation on signed literal.
*/
default void intersectLit(int l, int u, ExplanationForSignedClause explanation) {
if (explanation.contains(this)) {
if (XParameters.FINE_PROOF) {
System.out.printf("%s: %s ∩ [%d,%d]", getName(), getLit(), l, u);
}
this.getLit().retainBetween(l, u);
if (this.getLit().isEmpty()) {
if (XParameters.FINE_PROOF) {
System.out.print(" -- remove");
}
explanation.removeLit(this);
}
if (XParameters.FINE_PROOF) {
System.out.print("\n");
}
} else {
// this is the first occurrence of the variable during explanation
this.unionLit(l, u, explanation);
}
}
/**
* Perform the intersection of this internal signed literal and {{@code v}}:
*
{@code lit} = {{@code v}} ∩ {@code lit}
*
* @param v int value
* @param explanation the explanation
* @apiNote This method is supposed to be called on pivot variables only.
* It can be called only once on the pivot variable while explaining a cause
* since it applies an intersection operation on signed literal.
*/
default void intersectLit(int v, ExplanationForSignedClause explanation) {
if (explanation.contains(this)) {
if (XParameters.FINE_PROOF) {
System.out.printf("%s: %s ∩ {%d}", getName(), getLit(), v);
}
this.getLit().retain(v);
if (this.getLit().isEmpty()) {
if (XParameters.FINE_PROOF) {
System.out.print(" -- remove");
}
explanation.removeLit(this);
}
if (XParameters.FINE_PROOF) {
System.out.print("\n");
}
} else {
// this is the first occurrence of the variable during explanation
this.unionLit(v, explanation);
}
}
default IntStream stream() {
Spliterators.AbstractIntSpliterator it = new Spliterators.AbstractIntSpliterator(IntVar.this.getDomainSize(),
Spliterator.ORDERED | Spliterator.DISTINCT | Spliterator.NONNULL) {
final int[] val = {IntVar.this.getLB() - 1};
@Override
public boolean tryAdvance(IntConsumer action) {
if ((val[0] = IntVar.this.nextValue(val[0])) < Integer.MAX_VALUE) {
action.accept(val[0]);
return true;
}
return false;
}
};
return StreamSupport.intStream(it, false);
}
}