org.chocosolver.solver.constraints.ISetConstraintFactory Maven / Gradle / Ivy
Show all versions of choco-solver Show documentation
/*
* This file is part of choco-solver, http://choco-solver.org/
*
* Copyright (c) 2023, 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;
import org.chocosolver.solver.ISelf;
import org.chocosolver.solver.Model;
import org.chocosolver.solver.constraints.set.*;
import org.chocosolver.solver.variables.BoolVar;
import org.chocosolver.solver.variables.IntVar;
import org.chocosolver.solver.variables.SetVar;
import org.chocosolver.util.objects.setDataStructures.iterable.IntIterableRangeSet;
import org.chocosolver.util.tools.ArrayUtils;
import java.util.Arrays;
/**
* Interface to make constraints over SetVar
*
* A kind of factory relying on interface default implementation to allow (multiple) inheritance
*
* @author Jean-Guillaume FAGES
* @since 4.0.0
*/
public interface ISetConstraintFactory extends ISelf {
//***********************************************************************************
// BASICS : union/inter/subset/card
//***********************************************************************************
/**
* Creates a constraint ensuring that union is exactly the union of values taken by ints,
*
* @param ints an array of integer variables
* @param union a set variable
* @return a constraint ensuring that union = {x | x in ints}
*/
default Constraint union(IntVar[] ints, SetVar union) {
return new Constraint(ConstraintsName.SETINTVALUESUNION
, new PropSetIntValuesUnion(ints, union)
, new PropSetIntValuesUnion(ints, union)
);
}
/**
* Creates a constraint which ensures that the union of sets is equal to unionSet
*
* @param sets an array of set variables
* @param unionSet set variable representing the union of sets
* @return A constraint ensuring that the union of sets is equal to unionSet
*/
default Constraint union(SetVar[] sets, SetVar unionSet) {
return new Constraint(ConstraintsName.SETUNION, new PropUnion(sets, unionSet), new PropUnion(sets, unionSet));
}
/**
* Creates a constraint which ensures that the union of sets_i, where i in indices,
* is equal to unionSet.
*
* @param unionSet set variable representing the union of sets
* @param vOffset value offset
* @param indices set variable representing the indices of selected variables in sets
* @param iOffset index offset
* @param sets an array of set variables
* @return A constraint ensuring that the indices-union of sets is equal to unionSet
*/
default Constraint union(SetVar unionSet, int vOffset, SetVar indices, int iOffset, SetVar[] sets) {
return new Constraint(ConstraintsName.SETUNION, new PropUnionVar(unionSet, vOffset, indices, iOffset, sets));
}
/**
* Creates a constraint which ensures that the union of sets_i, where i in indices,
* is equal to unionSet.
*
* @param unionSet set variable representing the union of sets
* @param indices set variable representing the indices of selected variables in sets
* @param sets an array of set variables
* @return A constraint ensuring that the indices-union of sets is equal to unionSet
*/
default Constraint union(SetVar unionSet, SetVar indices, SetVar[] sets) {
return union(unionSet, 0, indices, 0, sets);
}
/**
* Creates a constraint which ensures that the intersection of sets is equal to intersectionSet
*
* @param sets an array of set variables
* @param intersectionSet a set variable representing the intersection of sets
* @return A constraint ensuring that the intersection of sets is equal to intersectionSet
*/
default Constraint intersection(SetVar[] sets, SetVar intersectionSet) {
return intersection(sets, intersectionSet, false);
}
/**
* Creates a constraint which ensures that the intersection of sets is equal to intersectionSet
*
* @param sets an array of set variables
* @param intersectionSet a set variable representing the intersection of sets
* @param boundConsistent adds an additional propagator to guarantee BC
* @return A constraint ensuring that the intersection of sets is equal to intersectionSet
* @throws IllegalArgumentException when the array of variables is either null or empty.
*/
default Constraint intersection(SetVar[] sets, SetVar intersectionSet, boolean boundConsistent) {
if (sets.length == 0) {
throw new IllegalArgumentException("The intersection of zero sets is undefined.");
}
if (boundConsistent) {
return new Constraint(ConstraintsName.SETINTERSECTION,
new PropIntersection(sets, intersectionSet),
sets.length == 1
? new PropAllEqual(new SetVar[]{sets[0], intersectionSet})
: new PropIntersectionFilterSets(sets, intersectionSet));
} else {
return new Constraint(ConstraintsName.SETINTERSECTION, new PropIntersection(sets, intersectionSet));
}
}
/**
* Creates a constraint establishing that sets[i] is a subset of sets[j] if isets[i] is a subset of sets[j] if i[] props = new Propagator[sets.length - 1];
for (int i = 0; i < sets.length - 1; i++) {
props[i] = new PropSubsetEq(sets[i], sets[i + 1]);
}
return new Constraint(ConstraintsName.SETSUBSETEQ, props);
}
/**
* Creates a constraint counting the number of empty sets sets
* |{s in sets where |s|=0}| = nbEmpty
*
* @param sets an array of set variables
* @param nbEmpty integer restricting the number of empty sets in sets
* @return A constraint ensuring that |{s in sets where |s|=0}| = nbEmpty
*/
default Constraint nbEmpty(SetVar[] sets, int nbEmpty) {
return nbEmpty(sets, ref().intVar(nbEmpty));
}
/**
* Creates a constraint counting the number of empty sets sets
* |{s in sets where |s|=0}| = nbEmpty
*
* @param sets an array of set variables
* @param nbEmpty integer variable restricting the number of empty sets in sets
* @return A constraint ensuring that |{s in sets where |s|=0}| = nbEmpty
*/
default Constraint nbEmpty(SetVar[] sets, IntVar nbEmpty) {
return new Constraint(ConstraintsName.SETNBEMPTY, new PropNbEmpty(sets, nbEmpty));
}
/**
* Creates a constraint linking set1 and set2 with an index offset :
* x in set1 <=> x+offset in set2
*
* @param set1 a set variable
* @param set2 a set variable
* @param offset offset index
* @return a constraint ensuring that x in set1 <=> x+offset in set2
*/
default Constraint offSet(SetVar set1, SetVar set2, int offset) {
return new Constraint(ConstraintsName.SETOFFSET, new PropOffSet(set1, set2, offset));
}
/**
* Creates a constraint preventing set to be empty
*
* @param set a SetVar which should not be empty
* @return a constraint ensuring that set is not empty
*/
default Constraint notEmpty(SetVar set) {
return new Constraint(ConstraintsName.SETNOTEMPTY, new PropNotEmpty(set));
}
//***********************************************************************************
// SUM
//***********************************************************************************
/**
* Creates a constraint summing elements of set
* sum{i | i in set} = sum
*
* @param set a set variable
* @param sum an integer variable representing sum{i | i in set}
* @return a constraint ensuring that sum{i | i in set} = sum
*/
default Constraint sum(SetVar set, IntVar sum) {
return sumElements(set, null, 0, sum);
}
/**
* Creates a constraint summing weights given by a set of indices:
* sum{weights[i] | i in indices} = sum
*
* Also ensures that elements in indices belong to [0, weights.length-1]
*
* @param indices a set variable
* @param weights integers representing the weight of each element in indices
* @param sum an integer variable representing sum{weights[i] | i in indices}
* @return a constraint ensuring that sum{weights[i] | i in indices} = sum
*/
default Constraint sumElements(SetVar indices, int[] weights, IntVar sum) {
return sumElements(indices, weights, 0, sum);
}
/**
* Creates a constraint summing weights given by a set of indices:
* sum{weights[i-offset] | i in indices} = sum
*
* Also ensures that elements in indices belong to [offset, offset+weights.length-1]
*
* @param indices a set variable
* @param weights integers representing the weight of each element in indices
* @param offset offset index : should be 0 by default
* but generally 1 with MiniZinc API
* which counts from 1 to n instead of counting from 0 to n-1 (Java standard)
* @param sum an integer variable representing sum{weights[i-offset] | i in indices}
* @return a constraint ensuring that sum{weights[i-offset] | i in indices} = sum
*/
default Constraint sumElements(SetVar indices, int[] weights, int offset, IntVar sum) {
return new Constraint(ConstraintsName.SETSUM, new PropSumOfElements(indices, weights, offset, sum));
}
//***********************************************************************************
// MAX - MIN
//***********************************************************************************
/**
* Creates a constraint over the maximum element in a set:
* max{i | i in set} = maxElementValue
*
* @param set a set variable
* @param maxElementValue an integer variable representing the maximum element in set
* @param notEmpty true : the set variable cannot be empty
* false : the set may be empty (if set is empty, this constraint is not applied)
* @return a constraint ensuring that max{i | i in set} = maxElementValue
*/
default Constraint max(SetVar set, IntVar maxElementValue, boolean notEmpty) {
return max(set, null, 0, maxElementValue, notEmpty);
}
/**
* Creates a constraint over the maximum element induces by a set:
* max{weights[i-offset] | i in indices} = maxElementValue
*
* @param indices a set variable containing elements in range [offset,weights.length-1+offset]
* @param weights integers representing the weight of each element in indices
* @param offset offset index : should be 0 by default
* but generally 1 with MiniZinc API
* which counts from 1 to n instead of counting from 0 to n-1 (Java standard)
* @param maxElementValue an integer variable representing the maximum weight induced by indices
* @param notEmpty true : the set variable cannot be empty
* false : the set may be empty (if indices is empty, this constraint is not applied)
* @return a constraint ensuring that max{weights[i-offset] | i in indices} = maxElementValue
*/
default Constraint max(SetVar indices, int[] weights, int offset, IntVar maxElementValue, boolean notEmpty) {
if (notEmpty) {
return new Constraint(ConstraintsName.SETMAX, new PropNotEmpty(indices), new PropMaxElement(indices, weights, offset, maxElementValue, true));
} else {
return new Constraint(ConstraintsName.SETMAX, new PropMaxElement(indices, weights, offset, maxElementValue, false));
}
}
/**
* Creates a constraint over the minimum element in a set:
* min{i | i in set} = minElementValue
*
* @param set a set variable
* @param minElementValue an integer variable representing the minimum element in set
* @param notEmpty true : the set variable cannot be empty
* false : the set may be empty (if set is empty, this constraint is not applied)
* @return a constraint ensuring that min{i | i in set} = minElementValue
*/
default Constraint min(SetVar set, IntVar minElementValue, boolean notEmpty) {
return min(set, null, 0, minElementValue, notEmpty);
}
/**
* Creates a constraint over the minimum element induces by a set:
* min{weights[i-offset] | i in indices} = minElementValue
*
* @param indices a set variable containing elements in range [offset,weights.length-1+offset]
* @param weights integers representing the weight of each element in indices
* @param offset offset index : should be 0 by default
* but generally 1 with MiniZinc API
* which counts from 1 to n instead of counting from 0 to n-1 (Java standard)
* @param minElementValue an integer variable representing the minimum weight induced by indices
* @param notEmpty true : the set variable cannot be empty
* false : the set may be empty (if indices is empty, this constraint is not applied)
* @return a constraint ensuring that min{weights[i-offset] | i in indices} = minElementValue
*/
default Constraint min(SetVar indices, int[] weights, int offset, IntVar minElementValue, boolean notEmpty) {
if (notEmpty) {
return new Constraint(ConstraintsName.SETMIN, new PropNotEmpty(indices), new PropMinElement(indices, weights, offset, minElementValue, true));
} else {
return new Constraint(ConstraintsName.SETMIN, new PropMinElement(indices, weights, offset, minElementValue, false));
}
}
//***********************************************************************************
// CHANNELING CONSTRAINTS : bool/int/graph
//***********************************************************************************
/**
* Creates a constraint channeling a set variable with boolean variables :
* i in set <=> bools[i] = TRUE
*
* @param bools an array of boolean variables
* @param set a set variable
* @return a constraint ensuring that i in set <=> bools[i] = TRUE
*/
default Constraint setBoolsChanneling(BoolVar[] bools, SetVar set) {
return setBoolsChanneling(bools, set, 0);
}
/**
* Creates a constraint channeling a set variable with boolean variables :
* i in set <=> bools[i-offset] = TRUE
*
* @param bools an array of boolean variables
* @param set a set variable
* @param offset offset index : should be 0 by default
* but generally 1 with MiniZinc API
* which counts from 1 to n instead of counting from 0 to n-1 (Java standard)
* @return a constraint ensuring that i in set <=> bools[i-offset] = TRUE
*/
default Constraint setBoolsChanneling(BoolVar[] bools, SetVar set, int offset) {
return new Constraint(ConstraintsName.SETBOOLCHANNELING, new PropBoolChannel(set, bools, offset));
}
/**
* Creates a constraint channeling set variables and integer variables :
* x in sets[y] <=> ints[x] = y
*
* @param sets an array of set variables
* @param ints an array of integer variables
* @return a constraint ensuring that x in sets[y] <=> ints[x] = y
*/
default Constraint setsIntsChanneling(SetVar[] sets, IntVar[] ints) {
return setsIntsChanneling(sets, ints, 0, 0);
}
/**
* Creates a constraint channeling set variables and integer variables :
* x in sets[y-offset1] <=> ints[x-offset2] = y
*
* @param sets an array of set variables
* @param ints an array of integer variables
* @param offset1 offset index : should be 0 by default
* but generally 1 with MiniZinc API
* which counts from 1 to n instead of counting from 0 to n-1 (Java standard)
* @param offset2 offset index : should be 0 by default
* but generally 1 with MiniZinc API
* which counts from 1 to n instead of counting from 0 to n-1 (Java standard)
* @return a constraint ensuring that x in sets[y-offset1] <=> ints[x-offset2] = y
*/
default Constraint setsIntsChanneling(SetVar[] sets, IntVar[] ints, int offset1, int offset2) {
return new Constraint(ConstraintsName.SETINTCHANNELING,
new PropIntChannel(sets, ints, offset1, offset2),
new PropIntChannel(sets, ints, offset1, offset2), new PropAllDisjoint(sets)
);
}
/**
* Channeling constraint stating that int[i] = v + offset, where v is the ith element of the sorted elements of set.
* If i is out of bounds with set, int[i] = nullValue.
* @param set A set variable.
* @param ints An array of integer variables.
* @param nullValue An int.
* @param offset An int.
* @return A sortedSetIntsChanneling constraint.
*/
default Constraint sortedSetIntsChanneling(SetVar set, IntVar[] ints, int nullValue, int offset) {
return new Constraint(ConstraintsName.SETORDEREDINTCHANNELING, new PropSortedIntChannel(set, ints, nullValue, offset));
}
//***********************************************************************************
// MINIZINC API
//***********************************************************************************
/**
* Creates a constraint stating that the intersection of set1 and set2 should be empty
* Note that they can be both empty
*
* @param set1 a set variable
* @param set2 a set variable
* @return a constraint ensuring that set1 and set2 are disjoint (empty intersection)
*/
default Constraint disjoint(SetVar set1, SetVar set2) {
return allDisjoint(set1, set2);
}
/**
* Creates a constraint stating that the intersection of sets should be empty
* Note that there can be multiple empty sets
*
* @param sets an array of disjoint set variables
* @return a constraint ensuring that sets are all disjoint (empty intersection)
* @throws IllegalArgumentException when the array of variables is either null or empty.
*/
default Constraint allDisjoint(SetVar... sets) {
if (sets == null || sets.length == 0) {
throw new IllegalArgumentException("The array of variables cannot be null or empty");
}
if (sets.length == 1) return ref().trueConstraint();
return new Constraint(ConstraintsName.SETALLDISJOINT, new PropAllDisjoint(sets));
}
/**
* Creates a constraint stating that sets should all be different (not necessarily disjoint)
* Note that there cannot be more than one empty set
*
* @param sets an array of different set variables
* @return a constraint ensuring that sets are all different
* @throws IllegalArgumentException when the array of variables is either null or empty.
*/
default Constraint allDifferent(SetVar... sets) {
if (sets == null || sets.length == 0) {
throw new IllegalArgumentException("The array of variables cannot be null or empty");
}
if (sets.length == 1) return ref().trueConstraint();
return new Constraint(ConstraintsName.SETALLDIFFERENT, new PropAllDiff(sets),
new PropAllDiff(sets), new PropAtMost1Empty(sets)
);
}
/**
* Creates a constraint stating that sets should be all equal
*
* @param sets an array of set variables to be equal
* @return a constraint ensuring that all sets in sets are equal
* @throws IllegalArgumentException when the array of variables is either null or empty.
*/
default Constraint allEqual(SetVar... sets) {
if (sets == null || sets.length == 0) {
throw new IllegalArgumentException("The array of variables cannot be null or empty");
}
if (sets.length == 1) return ref().trueConstraint();
return new Constraint(ConstraintsName.SETALLEQUAL, new PropAllEqual(sets));
}
/**
* Creates a constraint stating that partitions universe into sets:
* union(sets) = universe
* intersection(sets) = {}
*
* @param sets an array of set variables whose values are subsets of universe
* @param universe a set variable representing the union of sets
* @return a constraint which ensures that sets forms a partition of universe
*/
default Constraint partition(SetVar[] sets, SetVar universe) {
Constraint allDisjoint = allDisjoint(sets);
allDisjoint.ignore();
return new Constraint(ConstraintsName.SETPARTITION, ArrayUtils.append(
allDisjoint.getPropagators(),
new Propagator[]{new PropUnion(sets, universe), new PropUnion(sets, universe)}
));
}
/**
* Creates a constraint stating that :
* x in sets[y-offset1] <=> y in invSets[x-offset2]
*
* @param sets an array of set variables
* @param invSets an array of set variables
* @param offset1 offset index : should be 0 by default
* but generally 1 with MiniZinc API
* which counts from 1 to n instead of counting from 0 to n-1 (Java standard)
* @param offset2 offset index : should be 0 by default
* but generally 1 with MiniZinc API
* which counts from 1 to n instead of counting from 0 to n-1 (Java standard)
* @return a constraint ensuring that x in sets[y-offset1] <=> y in invSets[x-offset2]
*/
default Constraint inverseSet(SetVar[] sets, SetVar[] invSets, int offset1, int offset2) {
return new Constraint(ConstraintsName.SETINVERSE, new PropInverse(sets, invSets, offset1, offset2));
}
/**
* Creates a constraint stating that sets are symmetric sets:
* x in sets[y] <=> y in sets[x]
*
* @param sets an array of set variables
* @return a constraint ensuring that x in sets[y] <=> y in sets[x]
* @throws IllegalArgumentException when the array of variables is either null or empty.
*/
default Constraint symmetric(SetVar... sets) {
if (sets == null || sets.length == 0) {
throw new IllegalArgumentException("The array of variables cannot be null or empty");
}
if (sets.length == 1) return ref().trueConstraint();
return symmetric(sets, 0);
}
/**
* Creates a constraint stating that sets are symmetric sets:
* x in sets[y-offset] <=> y in sets[x-offset]
*
* @param sets an array of set variables
* @param offset offset index : should be 0 by default
* but generally 1 with MiniZinc API
* which counts from 1 to n instead of counting from 0 to n-1 (Java standard)
* @return a constraint ensuring that x in sets[y-offset] <=> y in sets[x-offset]
*/
default Constraint symmetric(SetVar[] sets, int offset) {
return new Constraint(ConstraintsName.SETSYMMETRIC, new PropSymmetric(sets, offset));
}
/**
* Creates a constraint enabling to retrieve an element set in sets:
* sets[index] = set
*
* @param index an integer variable pointing to set's index into array sets
* @param sets an array of set variables representing possible values for set
* @param set a set variable equal to sets[index]
* @return a constraint ensuring that sets[index] = set
*/
default Constraint element(IntVar index, SetVar[] sets, SetVar set) {
return element(index, sets, 0, set);
}
/**
* Creates a constraint enabling to retrieve an element set in sets:
* sets[index-offset] = set
*
* @param index an integer variable pointing to set's index into array sets
* @param sets an array of set variables representing possible values for set
* @param offset offset index : should be 0 by default
* but generally 1 with MiniZinc API
* which counts from 1 to n instead of counting from 0 to n-1 (Java standard)
* @param set a set variable equal to sets[index-offset]
* @return a constraint ensuring that sets[index-offset] = set
*/
default Constraint element(IntVar index, SetVar[] sets, int offset, SetVar set) {
return new Constraint(ConstraintsName.SETELEMENT, new PropElement(index, sets, offset, set), new PropElement(index, sets, offset, set));
}
/**
* Creates a member constraint stating that set belongs to sets
*
* @param sets set variables representing possible values for set
* @param set a set variable which takes its value in sets
* @return a constraint ensuring that set belongs to sets
*/
default Constraint member(SetVar[] sets, SetVar set) {
IntVar index = ref().intVar("idx_tmp", 0, sets.length - 1, false);
return element(index, sets, 0, set);
}
/**
* Creates a member constraint stating that the value of intVar is in set
*
* @param intVar an integer variables which takes its value in set
* @param set a set variables containing possible values for intVar
* @return a constraint ensuring that the value of intVar is in set
*/
default Constraint member(final IntVar intVar, final SetVar set) {
if (intVar.isInstantiated()) {
return member(intVar.getValue(), set);
} else {
return new Constraint(ConstraintsName.SETMEMBER,
intVar.hasEnumeratedDomain() ?
new PropIntEnumMemberSet(set, intVar) :
new PropIntBoundedMemberSet(set, intVar)) {
@Override
public Constraint makeOpposite() {
return notMember(intVar, set);
}
};
}
}
/**
* Creates a member constraint stating that the constant cst is in set
*
* @param cst an integer
* @param set a set variable
* @return a constraint ensuring that cst is in set
*/
default Constraint member(final int cst, final SetVar set) {
return new Constraint(ConstraintsName.SETMEMBER, new PropIntCstMemberSet(set, cst)) {
@Override
public Constraint makeOpposite() {
return notMember(cst, set);
}
};
}
/**
* Creates a member constraint stating that the value of intVar is not in set
*
* @param intVar an integer variables which does not take its values in set
* @param set a set variables representing impossible values for intVar
* @return a constraint ensuring that the value of intVar is not in set
*/
default Constraint notMember(final IntVar intVar, final SetVar set) {
if (intVar.isInstantiated()) {
return notMember(intVar.getValue(), set);
} else {
IntVar integer = intVar;
if (!intVar.hasEnumeratedDomain()) {
integer = ref().intVar("enumViewOf(" + intVar.getName() + ")", intVar.getLB(), intVar.getUB(), false);
ref().arithm(integer, "=", intVar).post();
}
return new Constraint(ConstraintsName.SETNOTMEMBER,
new PropNotMemberIntSet(integer, set),
new PropNotMemberSetInt(integer, set)) {
@Override
public Constraint makeOpposite() {
return member(intVar, set);
}
};
}
}
/**
* Creates a member constraint stating that the constant cst is not in set
*
* @param cst an integer
* @param set a set variable
* @return a constraint ensuring that cst is not in set
*/
default Constraint notMember(final int cst, final SetVar set) {
return new Constraint(ConstraintsName.SETNOTMEMBER, new PropIntCstNotMemberSet(set, cst)) {
@Override
public Constraint makeOpposite() {
return member(cst, set);
}
};
}
/**
* Creates a "less or equal" constraint stating that the constant a ≼ b.
*
Lexicographic order of the sorted lists of elements.
*
* @param a a SetVar
* @param b a SetVar
* @return a constraint ensuring that a and b are lexicographically ordered.
* @implSpec This is based on {@link org.chocosolver.solver.variables.IViewFactory#setBoolsView(SetVar, int, int)}
* and {@link IIntConstraintFactory#lexLessEq(IntVar[], IntVar[])}
*/
default Constraint setLe(final SetVar a, final SetVar b) {
IntIterableRangeSet union = new IntIterableRangeSet();
for (int v : a.getUB()) {
union.add(v);
}
for (int v : b.getUB()) {
union.add(v);
}
int size = union.size();
int min = union.min();
int offset = 0;
if (min <= 0) {
offset = 1 - min;
}
int finalOffset = offset;
int[] ubA = Arrays.stream(a.getUB().toArray()).map(i -> i + finalOffset).toArray();
int[] ubB = Arrays.stream(b.getUB().toArray()).map(i -> i + finalOffset).toArray();
IntVar[] intsA = ref().intVarArray(size, ArrayUtils.concat(ubA, 0));
IntVar[] intsB = ref().intVarArray(size, ArrayUtils.concat(ubB, 0));
ref().sortedSetIntsChanneling(a, intsA, 0, offset).post();
ref().sortedSetIntsChanneling(b, intsB, 0, offset).post();
return ref().lexLessEq(intsA, intsB);
}
/**
* Creates a "strictly less" constraint stating that the constant a ≺ b.
*
Lexicographic order of the sorted lists of elements.
*
* @param a a SetVar
* @param b a SetVar
* @return a constraint ensuring that a and b are lexicographically ordered.
* @implSpec This is based on {@link org.chocosolver.solver.variables.IViewFactory#setBoolsView(SetVar, int, int)}
* and {@link IIntConstraintFactory#lexLess(IntVar[], IntVar[])}
*/
default Constraint setLt(final SetVar a, final SetVar b) {
IntIterableRangeSet union = new IntIterableRangeSet();
for (int v : a.getUB()) {
union.add(v);
}
for (int v : b.getUB()) {
union.add(v);
}
int size = union.size();
int min = union.min();
int offset = 0;
if (min <= 0) {
offset = 1 - min;
}
int finalOffset = offset;
int[] ubA = Arrays.stream(a.getUB().toArray()).map(i -> i + finalOffset).toArray();
int[] ubB = Arrays.stream(b.getUB().toArray()).map(i -> i + finalOffset).toArray();
IntVar[] intsA = ref().intVarArray(size, ArrayUtils.concat(ubA, 0));
IntVar[] intsB = ref().intVarArray(size, ArrayUtils.concat(ubB, 0));
ref().sortedSetIntsChanneling(a, intsA, 0, offset).post();
ref().sortedSetIntsChanneling(b, intsB, 0, offset).post();
return ref().lexLess(intsA, intsB);
}
}