
org.chocosolver.solver.variables.view.integer.IntAffineView Maven / Gradle / Ivy
The newest version!
/*
* This file is part of choco-solver, http://choco-solver.org/
*
* Copyright (c) 2025, 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.view.integer;
import org.chocosolver.sat.Reason;
import org.chocosolver.solver.ICause;
import org.chocosolver.solver.constraints.Explained;
import org.chocosolver.solver.exception.ContradictionException;
import org.chocosolver.solver.exception.SolverException;
import org.chocosolver.solver.variables.IntVar;
import org.chocosolver.solver.variables.delta.IIntDeltaMonitor;
import org.chocosolver.solver.variables.delta.NoDelta;
import org.chocosolver.solver.variables.events.IEventType;
import org.chocosolver.solver.variables.events.IntEventType;
import org.chocosolver.solver.variables.impl.scheduler.IntEvtScheduler;
import org.chocosolver.solver.variables.view.IntView;
import org.chocosolver.solver.variables.view.ViewDeltaMonitor;
import org.chocosolver.util.iterators.DisposableRangeIterator;
import org.chocosolver.util.iterators.DisposableValueIterator;
import org.chocosolver.util.iterators.EvtScheduler;
import static org.chocosolver.solver.variables.events.IntEventType.DECUPP;
import static org.chocosolver.solver.variables.events.IntEventType.INCLOW;
/**
*
*
* @author Charles Prud'homme
* @since 05/10/2023
*/
@Explained
public final class IntAffineView extends IntView {
public final boolean p; // positive
public final int a;
public final int b;
/**
* y is an affine view of x: y = a*x + b.
*
* @param var a integer variable
* @param a a coefficient
* @param b a constant
*/
public IntAffineView(final I var, int a, int b) {
super(a + ".(" + var.getName() + ") + " + b, var);
this.p = a >= 0;
this.a = Math.abs(a);
this.b = b;
}
@Override
public IIntDeltaMonitor monitorDelta(ICause propagator) {
var.createDelta();
if (var.getDelta() == NoDelta.singleton) {
return IIntDeltaMonitor.Default.NONE;
}
return new ViewDeltaMonitor(var.monitorDelta(propagator)) {
@Override
protected int transform(int value) {
return (p ? value : -value) * a + b;
}
};
}
@Override
public boolean removeValue(int value, ICause cause, Reason reason) throws ContradictionException {
assert cause != null;
int inf = getLB();
int sup = getUB();
if (inf > value || value > sup) return false;
value -= b;
if (a > 1) {
if ((value % a) != 0) {
return false;
}
value = value / a;
}
if (!p) {
value = -value;
}
if (var.removeValue(value, this, reason)) {// todo channel??
IntEventType e = IntEventType.REMOVE;
if (value == inf) {
e = IntEventType.INCLOW;
} else if (value == sup) {
e = IntEventType.DECUPP;
}
if (this.isInstantiated()) {
e = IntEventType.INSTANTIATE;
}
this.notifyPropagators(e, cause);
return true;
}
return false;
}
@Override
public boolean instantiateTo(int value, ICause cause, Reason reason) throws ContradictionException {
assert cause != null;
value -= b;
if (a > 1) {
if ((value % a) != 0) {
if (getModel().getSolver().isLCG()) {
getModel().getSolver().getSat().cEnqueue(0, reason);
}
this.contradiction(this, MSG_INST);
}
value /= a;
}
if (!p) {
value = -value;
}
if (var.instantiateTo(value, this, reason)) { // todo channel??
notifyPropagators(IntEventType.INSTANTIATE, cause);
return true;
}
return false;
}
@Override
public boolean updateLowerBound(int value, ICause cause, Reason reason) throws ContradictionException {
assert cause != null;
int old = this.getLB();
if (old >= value) return false;
value--;
value -= b;
if (a > 1) {
value = value / a - (value % a < 0 ? 1 : 0);
}
boolean change;
if (!p) {
change = var.updateUpperBound(-value - 1, this, reason); // todo channel??
} else {
change = var.updateLowerBound(value + 1, this, reason); // todo channel??
}
if (change) {
IntEventType e = IntEventType.INCLOW;
if (isInstantiated()) {
e = IntEventType.INSTANTIATE;
}
this.notifyPropagators(e, cause);
return true;
}
return false;
}
@Override
public boolean updateUpperBound(int value, ICause cause, Reason reason) throws ContradictionException {
assert cause != null;
int old = this.getUB();
if (old <= value) return false;
value -= b;
if (a > 1) {
value = value / a - (value % a < 0 ? 1 : 0);
}
boolean change;
if (!p) {
change = var.updateLowerBound(-value, this, reason); // todo channel??
} else {
change = var.updateUpperBound(value, this, reason); // todo channel??
}
if (change) {
IntEventType e = IntEventType.DECUPP;
if (isInstantiated()) {
e = IntEventType.INSTANTIATE;
}
this.notifyPropagators(e, cause);
return true;
}
return false;
}
@Override
public DisposableValueIterator getValueIterator(boolean bottomUp) {
if (_viterator == null || _viterator.isNotReusable()) {
_viterator = new DisposableValueIterator() {
DisposableValueIterator vit;
@Override
public void bottomUpInit() {
super.bottomUpInit();
vit = var.getValueIterator(p);
}
@Override
public void topDownInit() {
super.topDownInit();
vit = var.getValueIterator(!p);
}
@Override
public boolean hasNext() {
return p ? vit.hasNext() : vit.hasPrevious();
}
@Override
public boolean hasPrevious() {
return p ? vit.hasPrevious() : vit.hasNext();
}
@Override
public int next() {
return (p ? vit.next() : -vit.previous()) * a + b;
}
@Override
public int previous() {
return (p ? vit.previous() : -vit.next()) * a + b;
}
@Override
public void dispose() {
super.dispose();
vit.dispose();
}
};
}
if (bottomUp) {
_viterator.bottomUpInit();
} else {
_viterator.topDownInit();
}
return _viterator;
}
@Override
public DisposableRangeIterator getRangeIterator(boolean bottomUp) {
if (_riterator == null || _riterator.isNotReusable()) {
if (a == 1) {
// range iterator works on var
_riterator = new DisposableRangeIterator() {
DisposableRangeIterator rit;
int min;
int max;
@Override
public void bottomUpInit() {
rit = var.getRangeIterator(p);
if (p) {
min = rit.min() + b;
max = rit.max() + b;
} else {
min = -rit.max() + b;
max = -rit.min() + b;
}
}
@Override
public void topDownInit() {
rit = var.getRangeIterator(!p);
if (p) {
min = rit.min() + b;
max = rit.max() + b;
} else {
min = -rit.max() + b;
max = -rit.min() + b;
}
}
@Override
public boolean hasNext() {
return p ? rit.hasNext() : rit.hasPrevious();
}
@Override
public boolean hasPrevious() {
return p ? rit.hasPrevious() : rit.hasNext();
}
@Override
public void next() {
if (p) {
rit.next();
min = rit.min() + b;
max = rit.max() + b;
} else {
rit.previous();
min = -rit.max() + b;
max = -rit.min() + b;
}
}
@Override
public void previous() {
if (p) {
rit.previous();
min = rit.min() + b;
max = rit.max() + b;
} else {
rit.next();
min = -rit.max() + b;
max = -rit.min() + b;
}
}
@Override
public int min() {
return min;
}
@Override
public int max() {
return max;
}
};
} else {
// value iterator works on this
_riterator = new DisposableRangeIterator() {
DisposableValueIterator vit;
int min;
int max;
boolean iterable;
@Override
public void bottomUpInit() {
vit = getValueIterator(true);
iterable = vit.hasNext();
min = max = vit.next();
}
@Override
public void topDownInit() {
vit = getValueIterator(false);
iterable = vit.hasPrevious();
min = max = vit.previous();
}
@Override
public boolean hasNext() {
boolean isIterable = iterable;
iterable = vit.hasNext();
return isIterable;
}
@Override
public boolean hasPrevious() {
boolean isIterable = iterable;
iterable = vit.hasPrevious();
return isIterable;
}
@Override
public void next() {
min = max = vit.next();
}
@Override
public void previous() {
min = max = vit.previous();
}
@Override
public int min() {
return min;
}
@Override
public int max() {
return max;
}
};
}
}
if (bottomUp) {
_riterator.bottomUpInit();
} else {
_riterator.topDownInit();
}
return _riterator;
}
@Override
public boolean contains(int value) {
value -= b;
if (a > 1) {
if ((value % a) != 0) {
return false;
}
value /= a;
}
value = (!p) ? -value : value;
return var.contains(value);
}
@Override
public boolean isInstantiatedTo(int value) {
return var.isInstantiated() && contains(value);
}
@Override
public int getValue() throws IllegalStateException {
if (!isInstantiated()) {
throw new IllegalStateException("getValue() can be only called on instantiated variable. " +
name + " is not instantiated");
}
return (p ? var.getValue() : -var.getValue()) * a + b;
}
@Override
public int getLB() {
return (!p ? -var.getUB() : var.getLB()) * a + b;
}
@Override
public int getUB() {
return (!p ? -var.getLB() : var.getUB()) * a + b;
}
@Override
public int nextValue(int v) {
if (v < getLB()) return getLB();
if (v > getUB()) return Integer.MAX_VALUE;
// y = p * a * x + b where p in {-1,1}, a and b in Z.
// => x = (y - b) / (p * a)
v -= b;
v = (v < 0 && a > 1 && v % a != 0) ? v - a : v;
v /= (p ? a : -a);
if (p) {
v = var.nextValue(v);
} else {
v = var.previousValue(v);
}
if (v == Integer.MIN_VALUE || v == Integer.MAX_VALUE) return Integer.MAX_VALUE;
return (p ? v : -v) * a + b;
}
@Override
public int nextValueOut(int v) {
v++;
if (v < getLB() || v > getUB()) return v;
// y = p * a * x + b where p in {-1,1}, a and b in Z.
// => x = (y - b) / (p * a)
double w = v - b;
w /= (p ? a : -a);
int k = (int) w;
if (w == k && var.contains(k)) {
if (a > 1) {
v++;
} else {
if (p) {
k = var.nextValueOut(k);
} else {
k = var.previousValueOut(k);
}
v = ((p ? k : -k) * a + b);
}
}
return v;
}
@Override
public int previousValue(int v) {
if (v > getUB()) return getUB();
if (v < getLB()) return Integer.MIN_VALUE;
// y = p * a * x + b where p in {-1,1}, a and b in Z.
// => x = (y - b) / (p * a)
v -= b;
v = (v > 0 && a > 1 && v % a != 0) ? v + a : v;
v /= (p ? a : -a);
if (p) {
v = var.previousValue(v);
} else {
v = var.nextValue(v);
}
if (v == Integer.MIN_VALUE || v == Integer.MAX_VALUE) return Integer.MIN_VALUE;
return (p ? v : -v) * a + b;
}
@Override
public int previousValueOut(int v) {
v--;
if (v < getLB() || v > getUB()) return v;
// y = p * a * x + b where p in {-1,1}, a and b in Z.
// => x = (y - b) / (p * a)
double w = v - b;
w /= (p ? a : -a);
int k = (int) w;
if (w == k && var.contains(k)) {
if (a > 1) {
v--;
} else {
if (p) {
k = var.previousValueOut(k);
} else {
k = var.nextValueOut(k);
}
v = ((p ? k : -k) * a + b);
}
}
return v;
}
@Override
protected EvtScheduler createScheduler() {
return new IntEvtScheduler();
}
@Override
public String toString() {
return this.getName() + "[" + getLB() + "," + getUB() + "]";
}
@Override
public IEventType transformEvent(IEventType evt) {
if (evt == INCLOW) {
if (!p) return DECUPP;
} else if (evt == DECUPP) {
if (!p) return INCLOW;
}
return evt;
}
@Override
public boolean hasEnumeratedDomain() {
return var.hasEnumeratedDomain() || a > 1;
}
public boolean equals(IntVar v, int a, int b) {
if (!this.var.equals(v)) return false;
return this.a == Math.abs(a) && this.b == b && this.p == (a >= 0);
}
@Override
public int getLit(int val, int t) {
val -= b;
if (a > 1) {
int k = val % a;
val = val / a;
if (k != 0) {
if (t == 0) {
throw new SolverException("Cannot compute lit from " + this + " and " + val + " and " + t);
}
if (t == 1) {
//return MiniSat.falseLit;
throw new SolverException("Check falseLit");
}
if (t == 2 && k > 0) {
val++;
}
if (t == 3 && k < 0) {
val--;
}
}
}
if (!p) {
val = -val;
if (t >= 2) {
assert 5 - t >= 0;
return var.getLit(val, 5 - t);
}
}
return var.getLit(val, t);
}
@Override
public int getMinLit() {
return p ? var.getMinLit() : var.getMaxLit();
}
@Override
public int getMaxLit() {
return p ? var.getMaxLit() : var.getMinLit();
}
@Override
public int getValLit() {
return var.getValLit();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy