Example channelling constraints ECLiPSe
Asked Answered
F

3

8

Can someone provide a simple example of channelling constraints?

Channelling constraints are used to combine viewpoints of a constraint problem. Handbook of Constraint Programming gives a good explanation of how it works and why it can be useful:

The search variables can be the variables of one of the viewpoints, say X1 (this is discussed further below). As search proceeds, propagating the constraints C1 removes values from the domains of the variables in X1. The channelling constraints may then allow values to be removed from the domains of the variables in X2. Propagating these value deletions using the constraints of the second model, C2, may remove further values from these variables, and again these removals can be translated back into the first viewpoint by the channelling constraints. The net result can be that more values are removed within viewpoint V1 than by the constraints C1 alone, leading to reduced search.

I do not understand how this is implemented. What are these constraints exactly, how do they look like in a real problem? A simple example would be very helpful.

Feola answered 21/6, 2016 at 14:48 Comment(2)
Nice question ! The handbook is 900+ pages, I'm unable to locate the paragraph you cite. Could you provide an hint ?Nudi
@Nudi Damn, better late than never, page 392 at the bottom.Feola
F
6

As stated in Dual Viewpoint Heuristics for Binary Constraint Satisfaction Problems (P.A. Geelen):

Channelling constraints of two different models allows for the expression of a relationship between two sets of variables, one of each model.

This implies assignments in one of the viewpoints can be translated into assignments in the other and vice versa, as well as, when search initiates, excluded values from one model can be excluded from the other as well.

Let me throw in an example I implemented a while ago while writing a Sudoku solver.

Classic viewpoint

Here we interpret the problem in the same way a human would: using the integers between 1 and 9 and a definition that all rows, columns and blocks must contain every integer exactly once.

We can easily state this in ECLiPSe using something like:

% Domain
dim(Sudoku,[N,N]),
Sudoku[1..N,1..N] :: 1..N

% For X = rows, cols, blocks
alldifferent(X)

And this is yet sufficient to solve the Sudoku puzzle.

Binary boolean viewpoint

One could however choose to represent integers by their binary boolean arrays (shown in the answer by @jschimpf). In case it's not clear what this does, consider the small example below (this is built-in functionality!):

?­ ic_global:bool_channeling(Digit, [0,0,0,1,0], 1).
    Digit = 4
    Yes (0.00s cpu)
?­ ic_global:bool_channeling(Digit, [A,B,C,D], 1), C = 1.
    Digit = 3
    A = 0
    B = 0
    C = 1
    D = 0
    Yes (0.00s cpu)

If we use this model to represent a Sudoku, every number will be replaced by its binary boolean array and corresponding constraints can be written. Being trivial for this answer, I will not include all the code for the constraints, but a single sum constraint is yet enough to solve a Sudoku puzzle in its binary boolean representation.

Channelling

Having these two viewpoints with corresponding constrained models now gives the opportunity to channel between them and see if any improvements were made.

Since both models are still just an NxN board, no difference in dimension of representation exists and channelling becomes real easy.

Let me first give you a last example of what a block filled with integers 1..9 would look like in both of our models:

% Classic viewpoint
1 2 3
4 5 6
7 8 9

% Binary Boolean Viewpoint
[](1,0,0,0,0,0,0,0,0)  [](0,1,0,0,0,0,0,0,0)  [](0,0,1,0,0,0,0,0,0) 
[](0,0,0,1,0,0,0,0,0)  [](0,0,0,0,1,0,0,0,0)  [](0,0,0,0,0,1,0,0,0) 
[](0,0,0,0,0,0,1,0,0)  [](0,0,0,0,0,0,0,1,0)  [](0,0,0,0,0,0,0,0,1)

We now clearly see the link between the models and simply write the code to channel our decision variables. Using Sudoku and BinBools as our boards, the code would look something like:

( multifor([Row,Col],1,N), param(Sudoku,BinBools,N) 
do
  Value is Sudoku[Row,Col], 
  ValueBools is BinBools[Row,Col,1..N], 
  ic_global:bool_channeling(Value,ValueBools,1) 
).

At this point, we have a channelled model where, during search, if values are pruned in one of the models, its impact will also occur in the other model. This can then of course lead to further overall constraint propagation.

Reasoning

To explain the usefulness of the binary boolean model for the Sudoku puzzle, we must first differentiate between some provided alldifferent/1 implementations by ECLiPSe:

Reasoning of alldifferent/1

What this means in practice can be shown as following:

?­ [A, B, C] :: [0..1], ic:alldifferent([A, B, C]). 
  A = A{[0, 1]} 
  B = B{[0, 1]} 
  C = C{[0, 1]} 
  There are 3 delayed goals. 
  Yes (0.00s cpu) 

?­ [A, B, C] :: [0..1], ic_global:alldifferent([A, B, C]). 
  No (0.00s cpu)

As there has not yet occurred any assignment using the Forward Checking (ic library), the invalidity of the query is not yet detected, whereas the Bounds Consistent version immediately notices this. This behaviour can lead to considerable differences in constraint propagation while searching and backtracking through highly constrained models.

On top of these two libraries there is the ic_global_gac library intended for global constraints for which generalized arc consistency (also called hyper arc consistency or domain consistency) is maintained. This alldifferent/1 constraint provides even more pruning opportunities than the bounds consistent one, but preserving full domain consistency has its cost and using this library in highly constrained models generally leads to a loss in running performance.

Because of this, I found it interesting for the Sudoku puzzle to try and work with the bounds consistent (ic_global) implementation of alldifferent to maximise performance and subsequently try to approach domain consistency myself by channelling the binary boolean model.

Experiment results

Below are the backtrack results for the 'platinumblonde' Sudoku puzzle (referenced as being the hardest, most chaotic Sudoku puzzle to solve in The Chaos Within Sudoku, M. Ercsey­Ravasz and Z. Toroczkai) using respectively forward checking, bounds consistency, domain consistency, standalone binary boolean model and finally, the channelled model:

      (ic)   (ic_global)  (ic_global_gac)  (bin_bools)  (channelled)
BT    6 582      43             29             143           30

As we can see, our channelled model (using bounds consistency (ic_global)) still needs one backtrack more than the domain consistent implementation, but it definitely performs better than the standalone bounds consistent version.

When we now take a look at the running times (results are the product of calculating an average over multiple executions, this to avoid extremes) excluding the forward checking implementation as it's proven to no longer be interesting for solving Sudoku puzzles:

          (ic_global)  (ic_global_gac)  (bin_bools)  (channelled)
Time(ms)     180ms          510ms           100ms        220ms

Looking at these results, I think we can successfully conclude the experiment (these results were confirmed by 20+ other Sudoku puzzle instances):

Channelling the binary boolean viewpoint to the bounds consistent standalone implementation produces a slightly less strong constraint propagation behaviour than that of the domain consistent standalone implementation, but with running times ranging from just as long to notably faster.

EDIT: attempt to clarify

Imagine some domain variable representing a cell on a Sudoku board has a remaining domain of 4..9. Using bounds consistency, it is guaranteed that for both value 4 and 9 other domain values can be found which satisfy all constraints and thus provides consistency. However, no consistency is explicitly guaranteed for other values in the domain (this is what 'domain consistency' is).

Using a binary boolean model, we define the following two sum constraints:

  • The sum of every binary boolean array is always equal to 1
  • The sum of every N'th element of every array in every row/col/block is always equal to 1

The extra constraint strength is enforced by the second constraint which, apart from constraining row, columns and blocks, also implicitly says: "every cell can only contain every digit once". This behaviour is not actively expressed when using just the bounds consistent alldifferent/1 constraint!

Conclusion

It is clear that channelling can be very useful to improve a standalone constrained model, however if the new model's constraint strengthness is weaker than that of the current model, obviously, no improvements will be made. Also note that having a more constrained model doesn't necesarilly also mean an overall better performance! Adding more constraints will in fact decrease amounts of backtracks required to solve a problem, but it might also increase the running times of your program if more constraint checks have to occur.

Felder answered 15/7, 2016 at 10:2 Comment(1)
Great writeup, +1! Thank you for adding these examples.Gerger
G
5

Channeling constraints are used when, in a model, aspects of a problem are represented in more than one way. They are then necessary to synchronize these multiple representations, even though they do not themselves model an aspect of the problem.

Typically, when modelling a problem with constraints, you have several ways of choosing your variables. For example, in a scheduling problem, you could choose to have

  • an integer variable for each job (indicating which machine does the job)
  • an integer variable for each machine (indicating which job it performs)
  • a matrix of Booleans (indicating which job runs on which machine)
  • or something more exotic

In a simple enough problem, you choose the representation that makes it easiest to formulate the constraints of the problem. However, in real life problems with many heterogeneous constraints it is often impossible to find such a single best representation: some constraints are best represented with one type of variable, others with another.

In such cases, you can use multiple sets of variables, and formulate each individual problem constraint over the most convenient variable set. Of course, you then end up with multiple independent subproblems, and solving these in isolation will not give you a solution for the whole problem. But by adding channeling constraints, the variable sets can be synchronized, and the subproblems thus re-connected. The result is then a valid model for the whole problem.

As hinted in the quote from the handbook, in such a formulation is is sufficient to perform search on only one of the variable sets ("viewpoints"), because the values of the others are implied by the channeling constraints.

Some common examples for channeling between two representations are:

Integer variable and Array of Booleans: Consider an integer variable T indicating the time slot 1..N when an event takes place, and an array of Booleans Bs[N] such that Bs[T] = 1 iff an event takes place in time slot T. In ECLiPSe:

    T #:: 1..N,
    dim(Bs, [N]), Bs #:: 0..1,

Channeling between the two representations can then be set up with

    ( for(I,1,N), param(T,Bs) do Bs[I] #= (T#=I) )

which will propagate information both ways between T and Bs. Another way of implementing this channeling is the special purpose bool_channeling/3 constraint.

Start/End integer variables and Array of Booleans (timetable): We have integer variables S,E indicating the start and end time of an activity. On the other side an array of Booleans Bs[N] such that Bs[T] = 1 iff the activity takes place at time T. In ECLiPSe:

    [S,E] #:: 1..N,
    dim(Bs, [N]), Bs #:: 0..1,

Channeling can be achieved via

    ( for(I,1,N), param(S,E,Bs) do Bs[I] #= (S#=<I and I#=<E) ).

Dual representation Job/Machine integer variables: Here, Js[J] = M means that job J is executed on machine M, while the dual formulation Ms[M] = J means that machine M executes job J

    dim(Js, [NJobs]), Js #:: 0..NMach,
    dim(Ms, [NMach]), Ms #:: 1..NJobs,

And channeling is achieved via

    ( multifor([J,M],1,[NJobs,NMach]), param(Js,Ms) do
        (Js[J] #= M) #= (Ms[M] #= J)
    ).

Set variable and Array of Booleans: If you use a solver (such as library(ic_sets)) that can directly handle set-variables, these can be reflected into an array of booleans indicating membership of elements in the set. The library provides a dedicated constraint membership_booleans/2 for this purpose.

Garzon answered 26/6, 2016 at 0:54 Comment(0)
S
1

Here is a simple example, works in SWI-Prolog, but should also work in ECLiPSe Prolog (in the later you have to use (::)/2 instead of (in)/2):

Constraint C1:

 ?- Y in 0..100.
 Y in 0..100.

Constraint C2:

 ?- X in 0..100.
 X in 0..100.

Channelling Constraint:

 ?- 2*X #= 3*Y+5.
 2*X#=3*Y+5.

All together:

?- Y in 0..100, X in 0..100, 2*X #= 3*Y+5.
Y in 1..65,
2*X#=3*Y+5,
X in 4..100.

So the channel constraint works in both directions, it reduces the domain of C1 as well as the domain of C2.

Some systems use iterative methods, with the result that this channelling can take quite some time, here is an example which needs around 1 minute to compute in SWI-Prolog:

?- time(([U,V] ins 0..1_000_000_000, 36_641*U-24 #= 394_479_375*V)).
% 9,883,559 inferences, 53.616 CPU in 53.721 seconds 
(100% CPU, 184341 Lips)
U in 346688814..741168189,
36641*U#=394479375*V+24,
V in 32202..68843.

On the other hand ECLiPSe Prolog does it in a blink:

[eclipse]: U::0..1000000000, V::0..1000000000, 
              36641*U-24 #= 394479375*V.
U = U{346688814 .. 741168189}
V = V{32202 .. 68843}
Delayed goals:
     -394479375 * V{32202 .. 68843} + 
     36641 * U{346688814 .. 741168189} #= 24
Yes (0.11s cpu)

Bye

Snapp answered 24/6, 2016 at 22:10 Comment(6)
Taking some arbitrary case for comparison does not produce much insight. Take 2#= 3*(A+A*A). Does it still loop in ECLiPSe? Please add decorations as you see fit.Sufficiency
The nice property in SWI's clpfd is: It always terminates. And yes, sometimes that means to wait till our universe collapses, and respreads the X-illionth time: abs(X)#<7^7^7, X*X#<X.Sufficiency
In Jekejeke Minlog all this never happens, since I havent implemented bidirectional channelling, only directional channelling: jekejeke.ch/idatab/doclet/prod/en/docs/15_min/10_docu/…Snapp
I think bidirectional channeling violates a little bit the idea of first model than label. Why should you channel already during modelling when you are anyway later labeling? You only defer more constraints, and thus more pruning and intelligent search.Snapp
2#= 3 * (A+A * A) is not typical channel constraint. On the surface it doesn't have two viewpoints X1 and X2. Of course many Prolog systems replace A * A by a helper variable H, so that we have two constraints. 2#=3 * (A+H) and H#=A * A.Snapp
But I see that abs(A) #<7^7^7, 2#= 3*(A+A*A) will take very long, but why, whats going wrong? (tested for SWI-Prolog). The following works in a blink ?- abs(A) #< 7^7^7, 2#= 3 * (A+B * B). false.Snapp

© 2022 - 2024 — McMap. All rights reserved.