This example illustrates the use of the global cardinality constraint (GCC). The alphabet blocks problem consists of finding an arrangement of letters on four alphabet blocks. Each alphabet block has a single letter on each of its six sides. Collectively, the four blocks contain every letter of the alphabet except Q and Z. By arranging the blocks in various ways, the following words should be spelled out: BAKE, ONYX, ECHO, OVAL, GIRD, SMUG, JUMP, TORN, LUCK, VINY, LUSH, and WRAP.
You can formulate this problem as a CSP by representing each of the 24 letters as an integer variable. The domain of each variable is the set , which represents block1 through block4. The assignment A = 1 indicates that the letter A is on a side of block1. Because each block has six sides, each value v in must be assigned to exactly six variables so that each side of a block has a letter on it. This restriction can be formulated as a global cardinality constraint over all 24 variables, with common lower and upper bounds set equal to 6.
Moreover, in order to spell all the words listed previously, the four letters in each of the 12 words must be on different blocks. Another GCC statement that specifies 12 global cardinality constraints enforces these conditions. You can also formulate these restrictions by using 12 all-different constraints. Finally, four FIX statements break the symmetries that blocks are interchangeable. These constraints preset the blocks that contain the letters B, A, K, and E as block1, block2, block3, and block4, respectively.
The complete representation of the problem is as follows:
proc optmodel; /* Each letter except Q and Z is represented with a variable. */ /* The domain of each variable is the set of 4 blocks, */ /* or {1, 2, 3, 4} for short. */ set LETTERS = / A B C D E F G H I J K L M N O P R S T U V W X Y /; var Block {LETTERS} integer >= 1 <= 4; set BLOCKS = 1..4; /* There are exactly 6 letters on each alphabet block */ con SixLettersPerBlock: gcc(Block, setof {b in BLOCKS} <b,6,6>); /* The letters in each word must be on different blocks. */ set WORDS = / BAKE ONYX ECHO OVAL GIRD SMUG JUMP TORN LUCK VINY LUSH WRAP /; con CanSpell {w in WORDS}: gcc({k in 1..length(w)} Block[char(w,k)], setof {b in BLOCKS} <b,0,1>); /* Note 2: These restrictions can also be enforced by ALLDIFF constraints: con CanSpellv2 {w in WORDS}: alldiff({k in 1..length(w)} Block[char(w,k)]); */ /* Breaking the symmetry that blocks can be interchanged by setting the block that contains the letter B as block1, the block that contains the letter A as block2, etc. */ for {k in 1..length('BAKE')} fix Block[char('BAKE',k)] = k; solve; print Block; quit;
The solution to this problem is shown in Output 6.2.1.
Output 6.2.1: Solution to Alphabet Blocks Problem
Problem Summary | |
---|---|
Objective Sense | Minimization |
Objective Function | (0) |
Objective Type | Constant |
Number of Variables | 24 |
Bounded Above | 0 |
Bounded Below | 0 |
Bounded Below and Above | 20 |
Free | 0 |
Fixed | 4 |
Binary | 1 |
Integer | 23 |
Number of Constraints | 13 |
Linear LE (<=) | 0 |
Linear EQ (=) | 0 |
Linear GE (>=) | 0 |
Linear Range | 0 |
Alldiff | 0 |
Element | 0 |
GCC | 13 |
Lexico (<=) | 0 |
Lexico (<) | 0 |
Pack | 0 |
Reify | 0 |
Constraint Coefficients | 0 |