Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Set Expressions (with Chainable Format) for Arithmetic and Relational Operations #1107

Open
wants to merge 1 commit into
base: develop
Choose a base branch
from

Conversation

gadavidd
Copy link

Set Expressions (with Chainable Format) for Arithmetic and Relational Operations

I've been working with SetVar in choco-Solver. Unlike other types of variables, such as RealVar or IntVar, there was no direct way to compose operations such as equals (eq), not equals (ne), contains, not contains (nc), subset (subSet), union, or intersection directly in the model definition. While propagators allow for constructing such constraints, it was not as intuitive as it is for other variable types like IntVar and RealVar. These features are extremely useful in facilitating the composition of constraints.

Thus, I've worked on adding this feature for SetVar, based on the operations already available for other variable types. For example:

realVar.eq(3).post();

However, such expressions were not possible for SetVar:

setVar.eq(1, 2, 3).post();  // Not available... until now!

Improvement

I've implemented a new expression framework for SetVar, allowing the use of familiar operations directly on set variables. This change introduces support for the following operations:

Relational Operations:

  • eq (Equals): setA.eq(1, 2, 3) or setA.eq(setB).
  • ne (Not Equals): setA.ne(1, 2) or setA.ne(setB).
  • contains: setA.contains(1, 2).
  • nc (notContains): setA.notContains(1, 2).
  • subSet: setA.subSet(1, 2, 3) or setA.subSet(setB).

Arithmetic Set Operations:

union: setA.union(setB)
intersection: setA.intersection(setB)

This enhancement allows for the expression of complex set relations with simplified syntax, enabling combinations such as:

setA.eq(setB.union(setC.intersection(setD))).post();

Example Usage:

Model model = new Model();

// Define set variables
SetVarImplArExpression setA = new SetVarImplArExpression(model, "setA", new int[]{}, new int[]{0, 1, 2, 3});
SetVarImplArExpression setB = new SetVarImplArExpression(model, "setB", new int[]{}, new int[]{1, 2, 3});
SetVarImplArExpression setC = new SetVarImplArExpression(model, "setC", new int[]{}, new int[]{2, 3, 4, 5});

// Compose set expressions
setA.eq(setB.union(setC)).post();
model.getSolver().propagate();

// Assert the expected values in setA
assertTrue(Arrays.equals(setA.getUB().toArray(), new int[]{1, 2, 3, 4, 5}));

Internal Changes:

This change is implemented through three main components:

  • Unary Set Expressions (UnReSetExpression): Handle relations between a set and a list of values, like setVar.eq(1,2,3).post().
  • Binary Set Expressions (BiReSetExpression): Manage operations between two SetVar instances, such as setA.eq(setB).post().
  • Binary Arithmetic Expressions (BiArSetExpression): Handle arithmetic operations like unions and intersections, e.g., setA.eq(setB.union(setC)).post().

A new interface, SetExpression, inspired by other Choco-Solver expressions, allows SetVarExpression to be used in conjunction with these expression composition objects (UnReSetExpression, BiReSetExpression, and BiArSetExpression).

Backward Compatibility:

This improvement is fully backward-compatible with existing constraints and models. It adds considerable flexibility when working with SetVar, and I believe it will make modeling with sets much more intuitive.

Future Improvements:

I believe that additional operations, such as disjoint, could also be implemented to further enhance set operations in Choco-Solver.

Finally, I've included a comprehensive suite of tests in SetVarExpressionTest, which I hope will be useful to validate this enhancement.

I would greatly appreciate any feedback or suggestions for optimizations, as well as any edge cases that could further improve this approach!

@gadavidd gadavidd force-pushed the set-expression-improvements branch from b2f0ce3 to 020ac80 Compare October 22, 2024 04:33
@cprudhom
Copy link
Member

cprudhom commented Nov 5, 2024

Thank you for the PR.
It's in my TODOs but I'd like to finish another task before starting on this one, which I think is absolutely relevant.

@cprudhom cprudhom changed the base branch from master to develop January 8, 2025 16:26
if (result == null) {
switch (op) {
case SUBSET:
result = model.subsetEq(xSet, ySet).reify().eq(TRUE).boolVar();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The expected expression is : (xSet \in ySet) \impl result but you defined: ((xSet \in ySet) \impl True) \imp result.
You can simplify the expression to:

result = model.subsetEq(xSet, ySet).reify();


import java.util.HashSet;

import static org.chocosolver.solver.constraints.real.Ibex.TRUE;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ibex.TRUE is a constant for continuous constraint programming and should not be used in Set expressions

result = model.subsetEq(xSet, ySet).reify().eq(TRUE).boolVar();
break;
case EQ:
result = model.allEqual(xSet, ySet).reify().eq(TRUE).boolVar();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same as l.55

result = model.allDifferent(xSet, ySet).reify().eq(TRUE).boolVar();
break;
case CONTAINS:
result = model.subsetEq(ySet, xSet).reify().eq(TRUE).boolVar();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same as l.55

result = model.allEqual(xSet, ySet).reify().eq(TRUE).boolVar();
break;
case NE:
result = model.allDifferent(xSet, ySet).reify().eq(TRUE).boolVar();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same as l.55

result = model.subsetEq(ySet, xSet).reify().eq(TRUE).boolVar();
break;
case NC:
result = model.disjoint(ySet, xSet).reify().eq(TRUE).boolVar();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same as l.55

import org.chocosolver.solver.variables.SetVar;

public interface SetExpression {
default SetExpression union(SetExpression y){
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suppose we can have JavaDoc here


@Override
public void extractVar(HashSet<IntVar> variables) {
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would throw an exception here, to be safe


private BoolVar result;

public UnReSetExpression(SetOperator operator, SetExpression x, int... y) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure to understand why this class is needed, everything can be done with BiReSetExpression, no?

* setA.union(setB).post();
*/

public class SetVarExpression extends SetVarImpl implements SetExpression {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this class is useful, can't SetVar implement SetExpression directly?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants