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

org.chocosolver.solver.constraints.nary.PropSweepBasedDiffN 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.constraints.nary;

import org.chocosolver.memory.IStateBitSet;
import org.chocosolver.solver.constraints.Propagator;
import org.chocosolver.solver.constraints.PropagatorPriority;
import org.chocosolver.solver.exception.ContradictionException;
import org.chocosolver.solver.variables.IntVar;
import org.chocosolver.solver.variables.events.IntEventType;
import org.chocosolver.solver.variables.events.PropagatorEventType;
import org.chocosolver.util.ESat;
import org.chocosolver.util.objects.graphs.UndirectedGraph;
import org.chocosolver.util.objects.setDataStructures.ISet;
import org.chocosolver.util.objects.setDataStructures.ISetIterator;
import org.chocosolver.util.objects.setDataStructures.SetType;
import org.chocosolver.util.objects.setDataStructures.iterable.IntIterableRangeSet;
import org.chocosolver.util.tools.ArrayUtils;

import java.util.Arrays;
import java.util.stream.IntStream;

/**
 * Implementation of a sweep-based algorithm for k-dimensional diffN constraint.
 * 
* This is based on the following master thesis. * In particular, the algorithms described in Section 3 serve as a basis of this class. *
* * @author Charles Prud'homme * @since 30/08/2022 */ public class PropSweepBasedDiffN extends Propagator { // Number of orthotopes private final int nbOrthotopes; // Collections of orthotopes to consider private final Orthotope[] os; // Store unfixed orthotopes private final IStateBitSet unfixed; // Store overlapping orthotopes, updated in getOutBoxes private final UndirectedGraph overlapping; // maximal length in dimension d over all objects private final int[] maxl; // Number of dimensions private final int nbDimensions; // Array of forbidden regions private final Forbidden[] fs; // supports for enumerated domain private final IntIterableRangeSet supports; private final int[] c; private final int[] j; private final int[] dl; private final int[] ur; private final int[] ls; private final int options; private final boolean dom; public PropSweepBasedDiffN(IntVar[][] x, int[][] l) { super(ArrayUtils.flatten(x), PropagatorPriority.QUADRATIC, true); this.dom = (int) (model.getHookOrDefault("diffn", 2)) == 2; this.options = (int) (model.getHookOrDefault("diffnOpt", 1)); this.nbDimensions = x[0].length; this.nbOrthotopes = x.length; this.os = new Orthotope[nbOrthotopes]; this.overlapping = new UndirectedGraph(model, os.length, SetType.BITSET, true); this.maxl = new int[nbDimensions]; this.c = new int[nbDimensions]; this.j = new int[nbDimensions]; this.dl = new int[nbDimensions]; this.ur = new int[nbDimensions]; this.ls = new int[nbDimensions]; this.supports = new IntIterableRangeSet(); this.unfixed = getModel().getEnvironment().makeBitSet(nbOrthotopes); this.unfixed.set(0, nbOrthotopes); for (int i = 0; i < nbOrthotopes; i++) { this.os[i] = new Orthotope(x[i], l[i], true); for (int d = 0; d < nbDimensions; d++) { maxl[d] = Math.max(maxl[d], l[i][d]); } } this.fs = new Forbidden[os.length - 1]; for (int i = 0; i < fs.length; i++) { fs[i] = new Forbidden(nbDimensions); } } @Override public int getPropagationConditions(int vIdx) { return IntEventType.boundAndInst(); } @Override public void propagate(int evtmask) throws ContradictionException { if (PropagatorEventType.isFullPropagation(evtmask)) { for (int i = 0; i < nbOrthotopes; i++) { for (int j = 0; j < i; j++) { if (os[i].mayOverlap(os[j])) { overlapping.addEdge(j, i); } } } } filter(); } @Override public void propagate(int idxVarInProp, int mask) throws ContradictionException { int i = idxVarInProp / nbDimensions; os[i].checkSkippable(maxl); forcePropagate(PropagatorEventType.CUSTOM_PROPAGATION); } @Override public ESat isEntailed() { for (int i = 0; i < os.length; i++) { if (os[i].assignedInAllDimensions()) { for (int j = i + 1; j < os.length; j++) { if (os[j].assignedInAllDimensions()) { if (os[i].mayOverlap(os[j])) { return ESat.FALSE; } } } } } if (isCompletelyInstantiated()) { return ESat.TRUE; } return ESat.UNDEFINED; } private void filter() throws ContradictionException { boolean nonfix = true; boolean allFixed = true; boolean checkArea = false; while (nonfix) { nonfix = false; allFixed = true; for (int i = unfixed.nextSetBit(0); i > -1; i = unfixed.nextSetBit(i + 1)) { Orthotope o = os[i]; o.modified = false; int nbF = getOutBoxes(os, nbDimensions, o, i); if (o.assignedInAllDimensions()) { if (nbF > 0) { this.fails(); } } else { for (int d = 0; d < nbDimensions && nbF > 0; d++) { if (!o.assignedInDimension(d)) { pruneMin(o, d, nbF); pruneMax(o, d, nbF); if (o.enumeratedOnDimension(d) && dom) { pruneDom(o, d, nbF); } if (o.modified) { nonfix = true; } } } } if (!o.assignedInAllDimensions()) { allFixed = false; } else { unfixed.clear(i); } //checkEnergy(i); if (options == 1 || options == 3) { checkArea = checkEnergy(i);// && this.areaCheck; } } } if (options == 2 || (checkArea && options == 3)) { checkArea(); } if (allFixed) { setPassive(); }// else return FIXPOINT } /** * For generating the forbidden regions of an orthotope o, according to other orthotopes. * * @param os collection of orthotopes * @param k number of dimensions * @param o the orthotope whose forbidden regions are to be calculated * @param i index of o in os * @return a set of k-dimensional forbidden regions for the orthotope o relative to the orthotopes in os. */ private int getOutBoxes(Orthotope[] os, int k, Orthotope o, int i) { int m = 0; ISetIterator iter = overlapping.getNeighborsOf(i).iterator(); while (iter.hasNext()) { int j = iter.nextInt(); Orthotope o2 = os[j]; //if (o2.isSkippable()) continue; // TODO check Forbidden f = fs[m]; boolean exists = true; boolean overlap = true; for (int d = 0; d < k; d++) { overlap &= o.mayOverlap(o2, d); if (o2.x[d].getUB() - o.l[d] + 1 <= o2.x[d].getLB() + o2.l[d] - 1) { f.set(d, o2.x[d].getUB() - o.l[d] + 1, o2.x[d].getLB() + o2.l[d] - 1); } else exists = false; } if (exists && overlaps(o, f)) { m++; } if (!overlap) { overlapping.removeEdge(i, j); } } return m; } /** * Used for checking if the domain of orthotope o overlaps the forbidden region f. * * @param o orthotope * @param f forbidden region * @return true iff the domain of o overlaps the forbidden region f */ private boolean overlaps(Orthotope o, Forbidden f) { for (int d = 0; d < nbDimensions; d++) { if (o.x[d].getUB() < f.min(d) || o.x[d].getLB() > f.max(d)) { return false; } } return true; } /** * Used for pruning the lower bound of the dth coordinate of an orthotope o. * * @param o the orthotope that is being filtered * @param d dimension in which filtering is being performed * @param nbF number of forbidden regions to read in fs * The function returns false if no feasible minimum point is obtainable in dimension d for o, * true otherwise. * @implSpec Tightens the lower bound of o.x[d] so that o and o' do not overlap, if possible. */ private void pruneMin(Orthotope o, int d, int nbF) throws ContradictionException { boolean b = true; for (int i = 0; i < nbDimensions; i++) { c[i] = o.x[i].getLB(); j[i] = o.x[i].getUB() + 1; } Forbidden f = getFR(c, nbF); while (b && f != null) { for (int i = 0; i < j.length; i++) { j[i] = Math.min(j[i], f.max(i) + 1); } b = adjust(c, j, o, d, true); f = getFR(c, nbF); } o.modified |= o.x[d].updateLowerBound(c[d], this); o.checkSkippable(maxl); } /** * Used for pruning the upper bound of the dth coordinate of an orthotope o. * * @param o the orthotope that is being filtered * @param d dimension in which filtering is being performed * @param nbF number of forbidden regions to read in fs * The function returns false if no feasible minimum point is obtainable in dimension d for o, * true otherwise. * @implSpec Tightens the upper bound of o.x[d] so that o and o' do not overlap, if possible. */ private void pruneMax(Orthotope o, int d, int nbF) throws ContradictionException { boolean b = true; for (int i = 0; i < nbDimensions; i++) { c[i] = o.x[i].getUB(); j[i] = o.x[i].getLB() - 1; } Forbidden f = getFR(c, nbF); while (b && f != null) { for (int i = 0; i < j.length; i++) { j[i] = Math.max(j[i], f.min(i) - 1); } b = adjust(c, j, o, d, false); f = getFR(c, nbF); } o.modified |= o.x[d].updateUpperBound(c[d], this); o.checkSkippable(maxl); } /** * Used for pruning the domain of the dth coordinate of an orthotope o. * * @param o the orthotope that is being filtered * @param d dimension in which filtering is being performed * @param nbF number of forbidden regions to read in fs * The function returns false if no feasible minimum point is obtainable in dimension d for o, * true otherwise. * @implSpec Tightens the upper bound of o.x[d] so that o and o' do not overlap, if possible. */ private void pruneDom(Orthotope o, int d, int nbF) throws ContradictionException { boolean b = true; supports.clear(); for (int i = 0; i < nbDimensions; i++) { c[i] = o.x[i].getLB(); j[i] = o.x[i].getUB() + 1; } Forbidden f = getFR(c, nbF); while (b) { while (b && f != null) { for (int i = 0; i < j.length; i++) { j[i] = Math.min(j[i], f.max(i) + 1); } b = adjustDom(c, j, o, d, false); f = getFR(c, nbF); } if (b) { supports.add(c[d]); b = adjustDom(c, j, o, d, true); f = getFR(c, nbF); } } o.modified |= o.x[d].removeAllValuesBut(supports, this); o.checkSkippable(maxl); } /** * Used for obtaining a forbidden region according to the current sweep point c and the orthotope being pruned o. * * @param c sweep point * @param nbF number of forbidden regions to read in fs * @return a forbidden region which overlaps c */ private Forbidden getFR(int[] c, int nbF) { int i = 0; while (i < nbF && isFeasible(fs[i], c)) { i++; } if (i < nbF) { return fs[i]; } else return null; //return Arrays.stream(fs).limit(nbF).filter(f -> !isFeasible(f, k, c)).findFirst().orElse(null); } /** * Used for checking if c is feasible according to the forbidden region f * * @param f forbidden region * @param c the sweep point c * @return true iff c does not overlap with the input forbidden region f */ private boolean isFeasible(Forbidden f, int[] c) { for (int j = 0; j < nbDimensions; j++) { if (c[j] < f.min(j) || c[j] > f.max(j)) { return true; } } return false; } /** * Moves the sweep point c to the next feasible position based on the jump vector j. * * @param c the sweep point * @param j the jump vector * @param o the orthotope being filtered * @param d the dimension in which filtering occurs * @param minimum whether the minimum or the maximum is being tightened * @return a Boolean indicating whether a candidate new sweep point was found or not * @implSpec the sweep point c is updated, the jump vector is reset */ private boolean adjust(int[] c, int[] j, Orthotope o, int d, boolean minimum) { if (minimum) { for (int i = nbDimensions - 1; i >= 0; i--) { int r = (i + d) % nbDimensions; c[r] = j[r]; j[r] = o.x[r].getUB() + 1; if (c[r] <= o.x[r].getUB()) { return true; } else { c[r] = o.x[r].getLB(); } } } else { for (int i = nbDimensions - 1; i >= 0; i--) { int r = (i + d) % nbDimensions; c[r] = j[r]; j[r] = o.x[r].getLB() - 1; if (c[r] >= o.x[r].getLB()) { return true; } else { c[r] = o.x[r].getUB(); } } } c[d] = j[d]; return false; } /** * Moves the sweep point c to the next feasible position based on the jump vector j. * * @param c the sweep point * @param j the jump vector * @param o the orthotope being filtered * @param d the dimension in which filtering occurs * @return a Boolean indicating whether a candidate new sweep point was found or not * @implSpec the sweep point c is updated, the jump vector is reset */ private boolean adjustDom(int[] c, int[] j, Orthotope o, int d, boolean next) { if (next) { for (int i = nbDimensions - 1; i > 0; i--) { int r = (i + d) % nbDimensions; c[r] = o.x[r].getLB(); j[r] = o.x[r].getUB() + 1; } c[d] = o.x[d].nextValue(c[d]); j[d] = o.x[d].getUB() + 1; if (c[d] <= j[d] - 1) { return true; } else { c[d] = o.x[d].getLB(); return false; } } else { for (int i = nbDimensions - 1; i >= 0; i--) { int r = (i + d) % nbDimensions; c[r] = j[r]; int u = o.x[r].getUB(); j[r] = u + 1; if (c[r] <= u) { return true; } else { c[r] = o.x[r].getLB(); } } } c[d] = j[d]; // TODO: check if useful return false; } /** * Check energy for orthotope i relatively to overlapping orthotopes. * * @param i index of orthotope to check * @return true if area must be checked too. * @throws ContradictionException if minimum energy conditions not met */ private boolean checkEnergy(int i) throws ContradictionException { long am = os[i].al; for (int d = 0; d < nbDimensions; d++) { dl[d] = os[i].x[d].getLB(); ur[d] = os[i].x[d].getUB() + os[i].l[d]; ls[d] = os[i].l[d]; } ISet neigh = overlapping.getNeighborsOf(i); //ISetIterator iter = neigh.iterator(); //while (iter.hasNext()) { // int j = iter.nextInt(); for (int j : neigh.toArray()) { long ar = 1; for (int d = 0; d < nbDimensions; d++) { dl[d] = Math.min(dl[d], os[j].x[d].getLB()); ur[d] = Math.max(ur[d], os[j].x[d].getUB() + os[j].l[d]); ls[d] = Math.min(ls[d], os[j].l[d]); ar *= ur[d] - dl[d]; } am += os[j].al; if (am > ar) { //System.out.printf("(%d,%d) : %d > %d ?\n", i, j, am, ar); this.fails(); } } if (Arrays.stream(ls).min().orElse(0) > 0) { double nbOr = 1.; for (int d = 0; d < nbDimensions; d++) { nbOr *= (ur[d] - dl[d]) * 1. / ls[d]; } if (nbOr <= neigh.size()) { this.fails(); } } return os[i].area() < am; } /** * Check area for all orthotopes, considering distance to overlapping ones. * * @throws ContradictionException if area condition is not met */ private void checkArea() throws ContradictionException { for (int i = 0; i < os.length; i++) { long limArea = os[i].area(); long area_i = os[i].al; ISet neigh = overlapping.getNeighborsOf(i); ISetIterator iter = neigh.iterator(); while (iter.hasNext() && area_i <= limArea) { int j = iter.nextInt(); area_i += computeArea(i, j); } if (area_i > limArea) { this.fails();// TODO: could be more precise, for explanation purpose } } } /** * Compute the common area between two orthotopes * * @param i index of an orthotope * @param j index of another orthotope * @return common area */ private long computeArea(int i, int j) { long area_ks = 1;// for (int d = 0; d < this.nbDimensions && area_ks > 0; d++) { long aa = computeAreaForDim(i, j, d); area_ks *= aa; } return area_ks; } private long computeAreaForDim(int i, int j, int d) { int min_i = os[i].x[d].getLB(); int max_i = os[i].x[d].getUB() + os[i].l[d]; long ld = os[j].l[d]; if (os[j].x[d].getLB() <= min_i) { // j starts before starting of i if (os[j].x[d].getUB() + os[j].l[d] <= max_i) { // j ends before ending of i int dist = os[j].x[d].getLB() + os[j].l[d] - min_i; ld = Math.max(dist, 0); } else { // j ends after ending of i int d1 = os[j].x[d].getLB() + os[j].l[d] - min_i; int d2 = -os[j].x[d].getUB() + max_i; d1 = Math.min(d1, max_i - min_i); d2 = Math.min(d2, max_i - min_i); if (d1 < d2) ld = Math.max(d1, 0); else if (d2 > 0) { if (d2 < os[j].l[d]) ld = d2; } else ld = 0; } } else if (os[j].x[d].getUB() + os[j].l[d] > max_i) { // j ends after i int distance2 = -os[j].x[d].getUB() + os[i].x[d].getLB() + os[i].l[d]; if (distance2 > 0) { if (distance2 < os[j].l[d]) ld = distance2; } else ld = 0; } return ld; } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// private static class Orthotope { final IntVar[] x; final int[] l; final long al; // area boolean modified = false; boolean skippable; public Orthotope(IntVar[] x, int[] l, boolean sk) { this.x = x; this.l = l; this.al = IntStream.of(l).reduce((i, j) -> i * j).orElse(0); this.skippable = sk; } public boolean isSkippable() { return skippable; } private int dsize(int d) { return x[d].getUB() - x[d].getLB() - l[d]; } private void checkSkippable(int[] maxl) { //TODO fix issue with skippable skippable = true; for (int d = 0; d < x.length && skippable; d++) { if(dsize(d) <= maxl[d] - 2){ skippable = false; } } } boolean assignedInAllDimensions() { int i = 0; while (i < x.length && x[i].isInstantiated()) { i++; } return i == x.length; } boolean assignedInDimension(int k) { return x[k].isInstantiated(); } boolean enumeratedOnDimension(int k) { return x[k].hasEnumeratedDomain(); } long area() { long a = 1; for (int i = 0; i < x.length; i++) { a *= x[i].getUB() - x[i].getLB() + l[i]; } return a; } public boolean mayOverlap(Orthotope o2) { boolean overlap = true; for (int d = 0; d < x.length && overlap; d++) { overlap = mayOverlap(o2, d); } return overlap; } public boolean mayOverlap(Orthotope o, int d) { return x[d].getLB() < o.x[d].getUB() + o.l[d] && o.x[d].getLB() < x[d].getUB() + l[d]; } } private static class Forbidden { private final int[][] f; public Forbidden(int k) { f = new int[k][2]; } public int min(int i) { return f[i][0]; } public int max(int i) { return f[i][1]; } public void set(int d, int fmin, int fmax) { f[d][0] = fmin; f[d][1] = fmax; } @Override public String toString() { StringBuilder st = new StringBuilder("FR ["); for (int d = 0; d < f.length; d++) { st.append('(').append(f[d][0]).append(',').append(f[d][1]).append(')'); } st.append("]"); return st.toString(); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy