This is the seventieth seventh part of the ILP series. For your convenience you can find other parts in the table of contents in Part 1 – Boolean algebra
Today we’re going to solve Cc kwadrat or Stained glass. The idea is: we have a 3×3 board and we need to put one of two types of pieces in each field. We need to count solutions.
Two solutions are considered equal if they are the same up to a) rotations b) rotations and mirroring
How to solve it? The idea is to calculate all the transformations in one model and then make sure that base one is selected uniquely.
Code for first case (rotations only):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
var variables = Enumerable.Range(0, 9).Select(v => solver.CreateAnonymous(Domain.BinaryInteger)).ToArray(); Func<IVariable[], IVariable[]> rotate = vars => { return new IVariable[]{vars[2], vars[5], vars[8], vars[1], vars[4], vars[7], vars[0], vars[3], vars[6]}; }; Func<IVariable[], IVariable> hash = vars => solver.Operation<Addition>(vars.Select((v, i) => solver.Operation<Multiplication>(v, solver.FromConstant((int)Math.Pow(2, i)))).ToArray()); IVariable[] all = new IVariable[]{ hash(variables), hash(rotate(variables)), hash(rotate(rotate(variables))), hash(rotate(rotate(rotate(variables)))) }; solver.Operation<Minimum>(all).Set<Equal>(all[0]); |
We create binary variables in line 1. Then, we define helper function to rotate the board in lines 3-5.
Next, we need a hashing function to reduce whole board to one number. Decomposing bits into a number is enough (line 7).
Finally, we define all possible transformations in lines 9-14. Next, in line 16 we define that the base solution must be minimum of all transformations.
We then add couple settings to CPLEX:
1 2 3 |
solver.Cplex.SetParam(ILOG.CPLEX.Cplex.Param.MIP.Pool.Intensity, 4); solver.Cplex.SetParam(ILOG.CPLEX.Cplex.Param.MIP.Limits.Populate, 1000); solver.Cplex.SetParam(ILOG.CPLEX.Cplex.Param.MIP.Pool.AbsGap, 0); |
And the output:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 |
Populate: phase I Tried aggregator 3 times. MIP Presolve eliminated 15 rows and 0 columns. MIP Presolve modified 46 coefficients. Aggregator did 16 substitutions. Reduced MIP has 42 rows, 32 columns, and 324 nonzeros. Reduced MIP has 27 binaries, 5 generals, 0 SOSs, and 0 indicators. Presolve time = 0.00 sec. (0.93 ticks) Found incumbent of value 0.000000 after 0.00 sec. (1.01 ticks) Root node processing (before b&c): Real time = 0.00 sec. (1.01 ticks) Parallel b&c, 8 threads: Real time = 0.00 sec. (0.00 ticks) Sync time (average) = 0.00 sec. Wait time (average) = 0.00 sec. ------------ Total (root+branch&cut) = 0.00 sec. (1.01 ticks) Populate: phase II Probing fixed 0 vars, tightened 4 bounds. Probing time = 0.00 sec. (0.07 ticks) Clique table members: 27. MIP emphasis: balance optimality and feasibility. MIP search method: dynamic search. Parallel mode: deterministic, using up to 8 threads. Root relaxation solution time = 0.00 sec. (0.06 ticks) Nodes Cuts/ Node Left Objective IInf Best Integer Best Bound ItCnt Gap * 0+ 0 0.0000 0.0000 0 0.00% 0 0 0.0000 0 0.0000 0.0000 0 0.00% 0 2 0.0000 1 0.0000 0.0000 0 0.00% Elapsed time = 0.02 sec. (1.80 ticks, tree = 0.02 MB, solutions = 2) Cover cuts applied: 5 Implied bound cuts applied: 5 Root node processing (before b&c): Real time = 0.02 sec. (0.79 ticks) Parallel b&c, 8 threads: Real time = 0.08 sec. (6.68 ticks) Sync time (average) = 0.02 sec. Wait time (average) = 0.00 sec. ------------ Total (root+branch&cut) = 0.09 sec. (7.47 ticks) 140 000000000 101010101 010101010 010101100 100000000 010111100 111111111 000101000 110000000 110100000 010000000 010010000 001000100 001010100 000111000 110110000 110111000 000010000 111101111 001101100 001111100 110010000 110011100 001101000 001111000 001110000 001110100 001100000 001100100 110111100 101101101 101111101 101011100 101111100 101111110 101101100 101101110 010101000 010111000 011110000 011010000 011110010 100001000 110001000 100101000 100111000 100111100 010111010 101111000 101011000 101101000 101111010 101001000 101101010 011000110 011001100 101000100 111000100 111100100 111110100 111111100 111110110 111110101 101100100 101110100 101110110 101100110 011100000 011100010 101011110 101001110 100010000 100011100 100001100 110111010 110011000 011111000 011101000 100011000 111011000 111101010 111111010 110001100 100101100 101001100 110101000 110101100 110101010 011010110 011000000 010001100 011101100 011111100 011110100 011110110 011011100 011010100 011101110 011111110 111001000 111101100 111101110 111100110 111010100 111010110 111100101 111000110 111111110 011000100 010011100 101010000 101000000 111010101 111000101 101000110 101010100 101010110 011100100 011100110 010100000 010110000 111110010 111010000 111000000 111110000 111100010 101110010 101110000 101100010 111101000 111111000 111001100 111100000 101100000 111101101 111111101 111011100 111001110 111011110 101000101 |
So you can see 140 solutions in total.
To solve second case (rotations and mirroring) we need to add this transformation:
1 2 3 |
Func<IVariable[], IVariable[]> mirror = vars => { return new IVariable[]{vars[2], vars[1], vars[0], vars[5], vars[4], vars[3], vars[8], vars[7], vars[6]}; }; |
and then create these solutions:
1 2 3 4 5 6 7 8 9 10 |
IVariable[] all = new IVariable[]{ hash(variables), hash(rotate(variables)), hash(rotate(rotate(variables))), hash(rotate(rotate(rotate(variables)))), hash(mirror(variables)), hash(rotate(mirror(variables))), hash(rotate(rotate(mirror(variables)))), hash(rotate(rotate(rotate(mirror(variables))))), }; |
Final output:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
Populate: phase I Tried aggregator 3 times. MIP Presolve eliminated 31 rows and 0 columns. MIP Presolve modified 94 coefficients. Aggregator did 36 substitutions. Reduced MIP has 102 rows, 64 columns, and 772 nonzeros. Reduced MIP has 51 binaries, 13 generals, 0 SOSs, and 0 indicators. Presolve time = 0.02 sec. (2.44 ticks) Found incumbent of value 0.000000 after 0.02 sec. (2.60 ticks) Root node processing (before b&c): Real time = 0.02 sec. (2.60 ticks) Parallel b&c, 8 threads: Real time = 0.00 sec. (0.00 ticks) Sync time (average) = 0.00 sec. Wait time (average) = 0.00 sec. ------------ Total (root+branch&cut) = 0.02 sec. (2.60 ticks) Populate: phase II Probing fixed 0 vars, tightened 11 bounds. Probing time = 0.00 sec. (0.14 ticks) Clique table members: 59. MIP emphasis: balance optimality and feasibility. MIP search method: dynamic search. Parallel mode: deterministic, using up to 8 threads. Root relaxation solution time = 0.00 sec. (0.14 ticks) Nodes Cuts/ Node Left Objective IInf Best Integer Best Bound ItCnt Gap * 0+ 0 0.0000 0.0000 0 0.00% 0 0 0.0000 0 0.0000 0.0000 0 0.00% 0 2 0.0000 1 0.0000 0.0000 0 0.00% Elapsed time = 0.03 sec. (4.43 ticks, tree = 0.02 MB, solutions = 2) Cover cuts applied: 21 Implied bound cuts applied: 24 Root node processing (before b&c): Real time = 0.02 sec. (1.82 ticks) Parallel b&c, 8 threads: Real time = 0.30 sec. (47.86 ticks) Sync time (average) = 0.01 sec. Wait time (average) = 0.00 sec. ------------ Total (root+branch&cut) = 0.31 sec. (49.68 ticks) 102 ... |
Works like a charm.