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

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) 2024, 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.solver.ICause;
import org.chocosolver.solver.exception.ContradictionException;
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 */ 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) throws ContradictionException { assert cause != null; int inf = getLB(); int sup = getUB(); if (inf > value || value > sup) return false; model.getSolver().getEventObserver().removeValue(this, value, cause); value -= b; if (a > 1) { if ((value % a) != 0) { return false; } value = value / a; } if (!p) { value = -value; } if (var.removeValue(value, this)) { 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; } else { model.getSolver().getEventObserver().undo(); return false; } } @Override public boolean instantiateTo(int value, ICause cause) throws ContradictionException { assert cause != null; model.getSolver().getEventObserver().instantiateTo(this, value, cause, getLB(), getUB()); value -= b; if (a > 1) { if ((value % a) != 0) { this.contradiction(this, MSG_INST); } value /= a; } if (!p) { value = -value; } if (var.instantiateTo(value, this)) { notifyPropagators(IntEventType.INSTANTIATE, cause); return true; } else { model.getSolver().getEventObserver().undo(); return false; } } @Override public boolean updateLowerBound(int value, ICause cause) throws ContradictionException { assert cause != null; int old = this.getLB(); if (old >= value) return false; model.getSolver().getEventObserver().updateLowerBound(this, value, getLB(), cause); value--; value -= b; if (a > 1) { value = value / a - (value % a < 0 ? 1 : 0); } boolean change; if (!p) { change = var.updateUpperBound(-value - 1, this); } else { change = var.updateLowerBound(value + 1, this); } if (change) { IntEventType e = IntEventType.INCLOW; if (isInstantiated()) { e = IntEventType.INSTANTIATE; } this.notifyPropagators(e, cause); return true; } else { model.getSolver().getEventObserver().undo(); return false; } } @Override public boolean updateUpperBound(int value, ICause cause) throws ContradictionException { assert cause != null; int old = this.getUB(); if (old <= value) return false; model.getSolver().getEventObserver().updateUpperBound(this, value, getUB(), cause); value -= b; if (a > 1) { value = value / a - (value % a < 0 ? 1 : 0); } boolean change; if (!p) { change = var.updateLowerBound(-value, this); } else { change = var.updateUpperBound(value, this); } if (change) { IntEventType e = IntEventType.DECUPP; if (isInstantiated()) { e = IntEventType.INSTANTIATE; } this.notifyPropagators(e, cause); return true; } else { model.getSolver().getEventObserver().undo(); 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 void justifyEvent(IntEventType mask, int one, int two, int three) { switch (mask) { case DECUPP: if (p) { model.getSolver().getEventObserver().updateUpperBound(this, -one * a + b, -two * a + b, this); } else { model.getSolver().getEventObserver().updateLowerBound(this, one * a + b, two * a + b, this); } break; case INCLOW: if (p) { model.getSolver().getEventObserver().updateLowerBound(this, one * a + b, two * a + b, this); } else { model.getSolver().getEventObserver().updateUpperBound(this, -one * a + b, -two * a + b, this); } break; case REMOVE: model.getSolver().getEventObserver().removeValue(this, one * (p ? 1 : -1) * a + b, this); case INSTANTIATE: model.getSolver().getEventObserver().instantiateTo(this, one * (p ? 1 : -1) * a + b, this, (p ? two : -three) * a + b, (p ? three : -two) * a + b); break; } } }