This is the eightieth first 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 are going to solve Kakuro:

Code:

var rows = new int[][]{ new int[]{0,0,0,0,0,0,0,0,0,0}, new int[]{0,0,1,1,1,1,0,2,2,2}, new int[]{0,0,3,3,3,3,3,3,3,3}, new int[]{0,4,4,4,0,0,5,5,0,0}, new int[]{0,6,6,0,0,7,7,7,7,0}, new int[]{0,8,8,0,9,9,0,10,10,10}, new int[]{0,11,11,11,11,0,12,12,12,12}, new int[]{0,0,13,13,13,13,13,0,14,14}, new int[]{0,0,0,15,15,15,0,0,16,16}, new int[]{0,17,17,17,17,0,18,18,18,18}, new int[]{0,19,19,0,0,20,20,20,0,0}, new int[]{0,21,21,0,22,22,22,22,22,0}, new int[]{0,23,23,23,23,0,24,24,24,24}, new int[]{0,25,25,25,0,26,26,0,27,27}, new int[]{0,0,28,28,28,28,0,0,29,29}, new int[]{0,0,0,30,30,0,0,31,31,31}, new int[]{0,32,32,32,32,32,32,32,32,0}, new int[]{0,33,33,33,0,34,34,34,34,0} }; var rowPoints = new Dictionary<int, int>(){ {1, 14}, {2, 9}, {3, 42}, {4, 11}, {5, 15}, {6, 13}, {7, 17}, {8, 6}, {9, 3}, {10, 14}, {11, 24}, {12, 14}, {13, 35}, {14, 7}, {15, 24}, {16, 10}, {17, 26}, {18, 17}, {19, 10}, {20, 10}, {21, 12}, {22, 27}, {23, 23}, {24, 14}, {25, 17}, {26, 10}, {27, 11}, {28, 23}, {29, 9}, {30, 9}, {31, 14}, {32, 41}, {33, 20}, {34, 11} }; var columns = new int[][]{ new int[]{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, new int[]{0,0,0,1,1,1,1,0,0,2,2,2,2,2,0,0,3,3}, new int[]{0,4,4,4,4,4,4,4,0,5,5,5,5,5,5,0,6,6}, new int[]{0,7,7,7,0,0,8,8,8,8,0,0,9,9,9,9,9,9}, new int[]{0,10,10,0,0,11,11,11,11,11,0,12,12,0,13,13,13,0}, new int[]{0,14,14,0,15,15,0,16,16,0,17,17,0,18,18,0,19,19}, new int[]{0,0,20,20,20,0,21,21,0,22,22,22,22,22,0,0,23,23}, new int[]{0,24,24,24,24,24,24,0,0,25,25,25,25,0,0,26,26,26}, new int[]{0,27,27,0,28,28,28,28,28,28,0,29,29,29,29,29,29,29}, new int[]{0,30,30,0,0,31,31,31,31,31,0,0,32,32,32,32,0,0} }; var columnPoints = new Dictionary<int, int>(){ {1, 29}, {2, 18}, {3, 10}, {4, 31}, {5, 38}, {6, 16}, {7, 18}, {8, 15}, {9, 38}, {10, 9}, {11, 31}, {12, 10}, {13, 13}, {14, 6}, {15, 9}, {16, 16}, {17, 7}, {18, 7}, {19, 12}, {20, 16}, {21, 17}, {22, 17}, {23, 9}, {24, 23}, {25, 26}, {26, 6}, {27, 3}, {28, 21}, {29, 28}, {30, 4}, {31, 33}, {32, 26} }; var variables = Enumerable.Range(0, rows.Length).Select(r => Enumerable.Range(0, rows[r].Length).Select(c => solver.CreateAnonymous(Domain.PositiveOrZeroInteger)).ToArray()).ToArray(); foreach(var v in variables.SelectMany(r => r)){ v.Set<LessOrEqual>(solver.FromConstant(9)); } foreach(var p in rowPoints){ var key = p.Key; var sum = p.Value; List<IVariable> vars = new List<IVariable>(); for(int i=0;i<rows.Length;++i){ for(int j=0;j<rows[0].Length;++j){ if(rows[i][j] == key){ vars.Add(variables[i][j]); } } } solver.Operation<Addition>(vars.ToArray()).Set<Equal>(solver.FromConstant(sum)); solver.Set<AllDifferent>(solver.FromConstant(0), vars.ToArray()); }; foreach(var p in columnPoints){ var key = p.Key; var sum = p.Value; List<IVariable> vars = new List<IVariable>(); for(int i=0;i<columns.Length;++i){ for(int j=0;j<columns[0].Length;++j){ if(columns[i][j] == key){ vars.Add(variables[j][i]); } } } solver.Operation<Addition>(vars.ToArray()).Set<Equal>(solver.FromConstant(sum)); solver.Set<AllDifferent>(solver.FromConstant(0), vars.ToArray()); }; solver.AddGoal("g", solver.FromConstant(0)); solver.Solve(); for(int i=0;i<rows.Length;++i){ for(int j=0;j<rows[0].Length;++j){ Console.Write(solver.GetValue(variables[i][j])); } Console.WriteLine(); }

Should be self explanatory now. Solution:

Tried aggregator 3 times. MIP Presolve eliminated 3334 rows and 1776 columns. MIP Presolve modified 7505 coefficients. Aggregator did 32 substitutions. Reduced MIP has 1682 rows, 740 columns, and 4680 nonzeros. Reduced MIP has 652 binaries, 88 generals, 0 SOSs, and 0 indicators. Presolve time = 0.01 sec. (9.86 ticks) Probing fixed 105 vars, tightened 93 bounds. Probing changed sense of 275 constraints. Probing time = 0.02 sec. (6.33 ticks) Cover probing fixed 0 vars, tightened 98 bounds. Tried aggregator 2 times. MIP Presolve eliminated 421 rows and 207 columns. MIP Presolve modified 982 coefficients. Aggregator did 185 substitutions. Reduced MIP has 1076 rows, 348 columns, and 3219 nonzeros. Reduced MIP has 265 binaries, 83 generals, 0 SOSs, and 0 indicators. Presolve time = 0.00 sec. (5.22 ticks) Probing fixed 5 vars, tightened 9 bounds. Probing time = 0.00 sec. (1.53 ticks) Tried aggregator 1 time. MIP Presolve eliminated 19 rows and 5 columns. MIP Presolve modified 120 coefficients. Reduced MIP has 1057 rows, 343 columns, and 3165 nonzeros. Reduced MIP has 260 binaries, 83 generals, 0 SOSs, and 0 indicators. Presolve time = 0.00 sec. (1.54 ticks) Probing fixed 4 vars, tightened 10 bounds. Probing time = 0.00 sec. (1.49 ticks) Clique table members: 688. MIP emphasis: balance optimality and feasibility. MIP search method: dynamic search. Parallel mode: deterministic, using up to 8 threads. Root relaxation solution time = 0.02 sec. (7.98 ticks) Nodes Cuts/ Node Left Objective IInf Best Integer Best Bound ItCnt Gap 0 0 0.0000 65 0.0000 215 0 0 0.0000 30 Cuts: 60 271 0 0 0.0000 102 Cuts: 115 375 0 0 0.0000 35 Cuts: 20 393 0 0 0.0000 48 Cuts: 48 443 * 0+ 0 0.0000 0.0000 443 0.00% 0 0 cutoff 0.0000 0.0000 443 0.00% Elapsed time = 0.36 sec. (133.69 ticks, tree = 0.00 MB, solutions = 1) Clique cuts applied: 10 Cover cuts applied: 1 Implied bound cuts applied: 22 Flow cuts applied: 1 Mixed integer rounding cuts applied: 8 Zero-half cuts applied: 25 Gomory fractional cuts applied: 3 Root node processing (before b&c): Real time = 0.38 sec. (133.77 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.38 sec. (133.77 ticks) 0000000000 0028310513 0079658421 0731007800 0940071360 0510120248 0861908123 0085679016 0007890037 0892701259 0370012700 0480164970 0158903812 0269037038 0037940027 0006300149 0275198360 0893031250]]>

This is the eightieth 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 are going to solve Islanders game.

We have different types of buildings. Each building has a size and a range. When building is constructed, it automatically adds points for its base gain and for all buildings in its range constructed earlier (which may be negative). We want to maximize the income.

Let’s start with model:

class Building{ public string Name {get; set;} public int Range {get;set;} public int Size {get; set;} public int BaseScore {get;set;} }

Each building has a name (for debugging only), range and size, and a base score (points added when the building is constructed).

Next, for each building we’ll create an ILP model:

class Placement{ public Building Building {get;set;} // Center of building public IVariable X {get;set;} public IVariable Y {get;set;} public IVariable BuildOrder {get;set;} public int Identifier {get;set;} }

This has two variables for position, and third variable for build order. It also has an identifier for debugging.

Now, the code:

// Model var cityCenter = new Building(){ Name = "City Center", Range = 5, Size = 1, BaseScore = 15 }; var house = new Building(){ Name = "House", Range = 6, Size = 2, BaseScore = 1 }; var mansion = new Building(){ Name = "Mansion", Range = 3, Size = 2, BaseScore = 1 }; var fountain = new Building(){ Name = "Fountain", Range = 8, Size = 2, BaseScore = 0 }; var costs = new Dictionary<Tuple<Building, Building>, int>(){ { Tuple.Create(cityCenter, cityCenter), -40}, { Tuple.Create(cityCenter, house), 0}, { Tuple.Create(cityCenter, mansion), 0}, { Tuple.Create(cityCenter, fountain), 0}, { Tuple.Create(house, cityCenter), 6}, { Tuple.Create(house, house), 1}, { Tuple.Create(house, mansion), 0}, { Tuple.Create(house, fountain), 2}, { Tuple.Create(mansion, cityCenter), 8}, { Tuple.Create(mansion, house), 0}, { Tuple.Create(mansion, mansion), 1}, { Tuple.Create(mansion, fountain), 3}, { Tuple.Create(fountain, cityCenter), 7}, { Tuple.Create(fountain, house), 2}, { Tuple.Create(fountain, mansion), 3}, { Tuple.Create(fountain, fountain), -15} }; Building[] buildings = new Dictionary<Building, int>(){ { cityCenter, 1}, { house, 4}, { mansion, 0}, { fountain, 0} }.SelectMany(kv => Enumerable.Range(0, kv.Value).Select(i => kv.Key)).ToArray(); var boardSize = 20; // ILP Placement[] placements = buildings.Select((b, i) => new Placement(){ Building = b, X = solver.CreateAnonymous(Domain.PositiveOrZeroInteger), Y = solver.CreateAnonymous(Domain.PositiveOrZeroInteger), BuildOrder = solver.CreateAnonymous(Domain.PositiveOrZeroInteger), Identifier = i + 1 }).ToArray(); // Make sure buildings are on board foreach(var p in placements){ p.X.Set<LessOrEqual>(solver.FromConstant(boardSize - p.Building.Size)); p.Y.Set<LessOrEqual>(solver.FromConstant(boardSize - p.Building.Size)); } // Make sure buildings do not overlap foreach(var p in placements){ foreach(var p2 in placements){ if(p == p2)continue; var overlapX = p.X.Operation<Subtraction>(p2.X).Operation<AbsoluteValue>().Operation<Multiplication>(solver.FromConstant(2)).Operation<IsLessThan>(solver.FromConstant(p.Building.Size + p2.Building.Size)); var overlapY = p.Y.Operation<Subtraction>(p2.Y).Operation<AbsoluteValue>().Operation<Multiplication>(solver.FromConstant(2)).Operation<IsLessThan>(solver.FromConstant(p.Building.Size + p2.Building.Size)); var overlap = overlapX.Operation<Disjunction>(overlapY); overlap.Set<Equal>(solver.FromConstant(0)); } } // Make sure building order is different solver.Set<AllDifferent>(solver.FromConstant(0), placements.Select(p => p.BuildOrder).ToArray()); // Calculate cost IVariable baseInput = solver.Operation<Addition>(placements.Select(p => p.Building.BaseScore).Select(s => solver.FromConstant(s)).ToArray()); IVariable[] rangeInput = placements.SelectMany(p => { return placements.Where(p2 => p != p2).Select(p2 => { var isAfter = p.BuildOrder.Operation<IsGreaterOrEqual>(p2.BuildOrder); var isInRange = p.X.Operation<Subtraction>(p2.X).Operation<AbsoluteValue>().Operation<Addition>(p.Y.Operation<Subtraction>(p2.Y).Operation<AbsoluteValue>()).Operation<IsLessOrEqual>(solver.FromConstant(p.Building.Range)); return solver.Operation<Condition>( isAfter.Operation<Conjunction>(isInRange), solver.FromConstant(costs[Tuple.Create(p.Building, p2.Building)]), solver.FromConstant(0) ); }); }).ToArray(); var goal = baseInput.Operation<Addition>(rangeInput); solver.AddGoal("Goal", goal); solver.Solve(); Console.WriteLine("Result: " + solver.GetValue(goal)); var board = Enumerable.Range(0, boardSize).Select(i => new int[boardSize]).ToArray(); for(int i=0;i<boardSize;++i){ for(int j=0;j<boardSize;++j){ board[i][j] = 0; } } foreach(var p in placements){ int x = (int)Math.Round(solver.GetValue(p.X)); int y = (int)Math.Round(solver.GetValue(p.Y)); for(int i=0;i<p.Building.Size;++i){ for(int j=0;j<p.Building.Size;++j){ board[x+i][y+j] = p.Identifier; } } } for(int i=0;i<boardSize;++i){ for(int j=0;j<boardSize;++j){ if(board[i][j] != 0){ Console.Write(board[i][j]); }else{ Console.Write(" "); } } Console.WriteLine(); }

In lines 1-25 we define buildings. Next, in lines 27-44 we specify points for constructing buildings in ranges. Finally, we ask to build one city center and for houses, just to keep model simple. We also specify that our board side length is 20.

We initialize variables in lines 55-62. Then, we start adding constraints.

First, we make sure that buildings are built on the board (lines 64-68).

Next, we want to make sure that buildings do not overlap. Building size is set to one means that it spans half a field each direction (so it forms a square of size one). To avoid floating point errors we multiply differences by two and not divide. This is in lines 70-83.

Next, we make sure that buildings are constructed in some order (line 86).

Finally, we calculate the cost in lines 88-101

Sample output:

Tried aggregator 2 times. MIP Presolve eliminated 545 rows and 105 columns. MIP Presolve modified 1807 coefficients. Aggregator did 456 substitutions. Reduced MIP has 1050 rows, 655 columns, and 3540 nonzeros. Reduced MIP has 560 binaries, 95 generals, 0 SOSs, and 0 indicators. Presolve time = 0.02 sec. (7.63 ticks) Probing fixed 160 vars, tightened 80 bounds. Probing changed sense of 40 constraints. Probing time = 0.00 sec. (6.62 ticks) Tried aggregator 2 times. MIP Presolve eliminated 320 rows and 160 columns. MIP Presolve modified 376 coefficients. Aggregator did 200 substitutions. Reduced MIP has 530 rows, 295 columns, and 1860 nonzeros. Reduced MIP has 200 binaries, 95 generals, 0 SOSs, and 0 indicators. Presolve time = 0.01 sec. (2.63 ticks) Probing time = 0.00 sec. (0.53 ticks) Tried aggregator 1 time. Reduced MIP has 530 rows, 295 columns, and 1860 nonzeros. Reduced MIP has 200 binaries, 95 generals, 0 SOSs, and 0 indicators. Presolve time = 0.00 sec. (0.85 ticks) Probing changed sense of 10 constraints. Probing time = 0.00 sec. (0.60 ticks) Clique table members: 310. 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. (1.22 ticks) Nodes Cuts/ Node Left Objective IInf Best Integer Best Bound ItCnt Gap 0 0 55.0000 52 55.0000 59 0 0 49.0000 80 Cuts: 88 170 0 0 49.0000 64 Cuts: 104 217 0 0 49.0000 64 MIRcuts: 1 218 * 0+ 0 39.0000 49.0000 218 25.64% 0 2 49.0000 64 39.0000 49.0000 218 25.64% Elapsed time = 0.17 sec. (59.33 ticks, tree = 0.01 MB, solutions = 1) * 1155 462 integral 0 40.0000 49.0000 16771 22.50% * 4782+ 1764 43.0000 49.0000 73723 13.95% 5076 1836 48.8125 37 43.0000 49.0000 77941 13.95% * 5190+ 648 45.0000 49.0000 80331 8.89% 5623 126 47.9286 40 45.0000 49.0000 90495 8.89% Clique cuts applied: 10 Cover cuts applied: 22 Implied bound cuts applied: 10 Flow cuts applied: 3 Mixed integer rounding cuts applied: 63 Gomory fractional cuts applied: 12 Root node processing (before b&c): Real time = 0.16 sec. (59.06 ticks) Parallel b&c, 8 threads: Real time = 2.26 sec. (751.26 ticks) Sync time (average) = 0.33 sec. Wait time (average) = 0.00 sec. ------------ Total (root+branch&cut) = 2.42 sec. (810.32 ticks) Result: 45 55 55 33 33 1 22 22 44 44]]>

There are two players, each of them has at most 20 pieces, some are men, some are kings. The board is 10 by 10 big (so has 100 fields in total).

First solution we may have is to encode position using two integers, color with one byte, and state (man vs king) in another byte. This gives 10 bytes per piece, 400 bytes in total.

Next solution is to hold each field with information whether it’s occupied, by what piece (man vs king) and belonging to whom (color). This results in 100 fields multiplied by 3 bytes, so 300 bytes in total.

Next, we could encode state using bits. We have four longs per layer. Two longs for men, two longs for kings, each bit is set when a piece is in given field. This results in 8 longs so 64 bytes in total.

Now, we can encode position of a piece using 8 bits (4 for row, 4 for column) plus one additional bit for type. This results in 40 pieces multiplied by 9 bits so 360 bits or 45 bytes in total.

We can cut this even more. First, we don’t need to store whole board as checkers are played on black fields only. So we need to encode 50 fields only. For each we need to store three bits: is occupied, color and type. 150 bits in total or 19 bytes.

]]>This is the ninth part of the Chatterbox series. For your convenience you can find other parts in the table of contents in Part 1 – Origins

Until now we focused on how to integrate text communication. We can share some media the same way, namely via providing links to images or videos. We can even embed them in some webview to simplify previewing. However, to integrate interactive calls we need to do a little more.

How do we call from Facebook to Skype? Or from Whatsapp to Google Hangouts? We can provide a server reencoding all media streams but that would probably require a lot of coding and protocol reversing. Adding mobile calls (over GSM provider, not over Internet) would increase the complexity even more. However, we can actually do this much easier, by capturing screen + voice + webcam.

First, we need to be able to “dial in” from mobile phone to the call. To do that we can use some existing services, e.g. Chime or Zoom. So we create a bridge BridgeA, we dial in from our mobile phone and we’re done.

Next, we need to have some server which would route things between networks. We open two browsers: BrowserA which dials into BridgeA, and BrowserB which calls someone on Facebook, Hangouts, Meet, whatever else.

Now, we need to have two fake audio lines. On Windows you can use Virtual Audio Cable and VB-Cable which are free and can do the trick.

We also need to be able to route an application to selected audio line. Sometimes it can be selected in the application, sometimes it cannot. For the latter case you can use Audio Router.

Now, we configure BrowserA to emit output to Line1 and get input from Line2. Analogically, we configure BrowserB to emit output to Line2 and get input from Line1. Effectively, anything going out of BrowserA will go into BrowserB using Line1. In the same way everything going out of BrowserB will enter BrowserA using Line2.

That’s it. Now you can route voice between two bridges. Obviously, you can mix audio in any way to create multi-way bridges if needed. Nothing can stop you now.

Okay, what about video? For that we can use BridgeA the same way, just dial in using computer/mobile phone with video. Next, you need to configure virtual webcam source. You can use Chrome extension or OBS Virtual Cam. Just capture screen from BrowserA into virtual webcam used by BrowserB, and the other way round. Depending on the capture quality and configuration you can mimic multiple screens etc.

What about the delay? From my experience it is close to 1 second. This is mediocre but I believe people are now used to the latency so it should be fine, at least it’s okay for my purposes.

]]>This is the seventieth ninth 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 are going to explore constraints and comparisons a little more. We already know how to compare integers, we sort of know how to do the same for real numbers (and we remember it’s dangerous). We’ll now try asking questions “what if”.

This part describes constraints we already covered. They should be easy to grasp.

We start with “set is” constraints which are directly built into the model. They include the following:

- Set Is Equal –
- Set Is Less Or Equal –
- Set Is Greater Or Equal –

Notice that these are “positive” constraints as they want to make “something to happen”. Each solver supports them.

Now we may want to ask about the same. These include:

- Is Equal –
- Is Greater Or Equal –
- Is Less Or Equal –
- Is Greater Than –
- Is Less Than –

We can calculate all of them by using regular comparisons we already defined.

Rest of “positive is” constraints we can implement by using comparisons:

- Set Is Greater Than –
- Set Is Less Than –

Now we enter the world of negative constraints. We may ask the following:

- Is Not Equal – equivalent to Is Greater Than or Is Less Than
- Is Not Greater or Equal – equivalent to Is Less Than
- Is Not Less Or Equal – equivalent to Is Greater Than
- Is Not Greater Than – equivalent to Is Less Or Equal
- Is Not Less Than – equivalent to Is Greater or Or Equal

Still, no magic in here. We just use Positive Is formulas.

Finally, we may want to add constraints that something doesn’t happen. They are obvious as well, we just take Negative Is and set them to true.

Okay, now things get a little trickier. Previously we were asking whether something “is” happening. Now we ask if something “can be” happening. How is this useful? Let’s take this problem with integer variables:

with goal to minimize . The optimal solution is as the sum is zero then.

We may ask the following about variable where is either variable or constant:

- Can Be Equal –
- Can Be Greater Or Equal –
- Can Be Less Or Equal –
- Can Be Greater Than –
- Can Be Less Than –

What do they do? They answer whether can be changed (but does not need to) in the final solution to particular option in a way that the model is still feasible, no other variable gets changed, and the goal function is allowed to change. So we may have the following examples:

- – true as if we set then model is still feasible
- – true based on the same reasoning
- – true as it’s already met
- – false as then would be too big
- – true

Notice however that if we set then we get the following answers:

- – false as
- – false based on the same reasoning
- – true as we can set
- – false as then would be too big
- – true

So notice that we didn’t modify in any way, we only modified some other variable and still we affected . Now, how do we implement these constraints?

This is the simplest one and actually something we can implement. We just need to imagine that is indeed equal to something and then replicate all constraints including it. This is called “Level 0 Model Transformation” (L0MT). So in our case it would be:

And now we take conjunction of all these “positive is” constraints.

Other Positive Can Be constraints can’t be represented that easily. That’s because when we ask it may be that it’s true for but false for so we don’t know which value to use. This can be represented using Level 2 Model Transformation (L2MT) explained later.

Now we discuss constraints setting whether something can happen. We can implement them in some ways.

This is the simplest one. Since we know what value we’re talking about, we can just substitute it as in and then make this true.

For other constraints we need to use “Level 1 Model Transformation” (L1MT). We introduce a new variable and then check for constraints. Let’s see an example for . We start with this model:

and now we add this:

We can see that solver needs to find an assignment meeting constraints. The model must be feasible so we don’t break anything.

Now we ask the following:

- Can Be Not Equal – equivalent to Can Be Greater Than or Can Be Less Than
- Can Be Not Greater Or Equal – equivalent to Can Be Less Than
- Can Be Less Or Equal – equivalent to Can Be Greater Than
- Can Be Greater Than – equivalent to Can Be Less Or Equal
- Can Be Less Than – equivalent to Can Be Greater Or Equal

Notice that Can Be Not Greater Or Equal and Not Can Be Greater Or Equal are not equivalent anymore. The former effectively asks if there is a value Not Greater Or Equal to something which makes the model feasible, the latter negates the constraint which is different for infeasible models.

To implement these constraints we can’t introduce new variables as in case of negative answers the model would become infeasible. However, we can represent them using L2MT.

And now we consider constraints. Again, they can be represented using L2MT as if we added some more variables and constrain them then we would end up with infeasible model.

Now we go similar route but allow to change some other variables as well (not all of them).

Let’s take this model:

is not very useful here but actually adds some constraint to depict the principle. Let’s say that we ask so if could be equal to five if we are allowed to change some other variables. The answer is yes as we may set equal to five if we change . However, remains unchanged.

These constraints can be represented using L2MT. We cannot go with copying variables as for negative answer we would make model infeasible.

While we cannot answer the question easily, we can make the constraint. We go with L1MT, copy variables and then add constraints. We would need to make sure that only some variables change which adds a little bit of complexity.

Again, we need to remember that Could Not Be Equal and Not Could Be Equal are not equivalent in terms of infeasibility. We can represent these by using L2MT.

Now we enter the world when all variables can change values.

For these we basically ask if there is any solution with constraint met. Again, copying variables is not helpful. We need to go with L2MT.

Now, these are very interesting. In the former we need to guarantee that there exists any solution which can be done with L1MT. For the latter we need to make sure that no solution exists which requires L2MT. However, notice that in the latter case we’re very close to the halting problem so in some cases it may not be possible at all.

Okay, but what are these all model transformations?

In this transformation we already know all the values as we modify only one variable to some constant so we don’t need to introduce any other free-ish variables. What we need is to verify if all other constraints hold which we can do by replacing them with Positive Is? and Set Positive Is.

In this case we need to introduce free-ish variables and include them in the model we solve. This is somewhat similar to Type 1 Hypervisor in VMs. We sort of build a model on the side and solve it when solving the original model.

Now something interesting happens. When we need to make sure something is not possible then we need to create a model where something is impossible. But if we included that model in the original one then we would make the latter infeasible as well. So how do we do it?

The answer is Turing Machine. We know we can emulate Turing Machine in ILP and so we can emulate the ILP solving algorithm inside the model. Since we know how big the input is, we know the complexity, we can calculate the number of steps required to land at solution. So we effectively emulate a computer inside our model to solve some other model.

This is like Level 2 Hypervisors which need to do some emulation. We effectively do the same here. Obviously, it’s only theoretically possible now (as it would lead to gigantic number of variables) but can be represented in ILP.

Now all operations at a glance:

]]>

This is the seventieth eighth 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 are going to solve the Tic-Tac-Toe riddle.

We have a board with some circles and crosses. We need to fill it with more of these so that no 2 by 2 square has four same characters, and we can separate circles from crosses (each of them create a closed shape).

First part is simple. For separation we need to build a simple path of edges going from top to bottom.

First, the regular model:

public class Point{ public IVariable Up {get;set;} public IVariable Down {get;set;} public IVariable Left {get;set;} public IVariable Right {get;set;} public IVariable[] AllDirections { get { return new IVariable[]{Up, Down, Left, Right}; } } public IVariable Identifier {get;set;} public IVariable LowerNeighbourCount {get;set;} public List<IVariable> Neighbours {get; set;} }

As always, we have edges for directions, and an identifier which we’re going to propagate through the network.

Now the solution:

var board = new int [][]{ new int[]{0,0,0,0,0,0,0,0,2,0}, new int[]{0,0,0,0,0,1,2,0,0,0}, new int[]{0,0,0,1,0,2,0,0,0,0}, new int[]{0,1,0,0,0,0,0,1,0,0}, new int[]{0,0,0,1,0,0,0,0,0,0}, new int[]{0,0,0,0,0,0,0,0,2,0}, new int[]{0,0,0,0,0,0,2,0,0,0}, new int[]{0,0,0,0,1,0,0,2,2,0}, new int[]{0,0,1,1,0,0,0,0,0,0}, new int[]{0,1,0,0,0,0,0,0,0,0}, }; int height = board.Length; int width = board[0].Length; var fields = Enumerable.Range(0, height).Select(r => Enumerable.Range(0, width).Select(c => solver.CreateAnonymous(Domain.BinaryInteger)).ToArray()).ToArray(); Console.WriteLine("Initialize board"); for(int row=0;row<height;++row){ for(int column=0;column<width;++column){ if(board[row][column] > 0){ fields[row][column].Set<Equal>(solver.FromConstant(board[row][column] == 1 ? 0 : 1)); } } } Console.WriteLine("Disallow four equal"); for(int row=0;row<height-1;++row){ for(int column=0;column<width-1;++column){ solver.Operation<Addition>( fields[row][column], fields[row+1][column], fields[row][column+1], fields[row+1][column+1] ).Set<GreaterOrEqual>(solver.FromConstant(1)).Set<LessOrEqual>(solver.FromConstant(3)); } } Console.WriteLine("Create path"); var points = Enumerable.Range(0, height-1).Select(r => Enumerable.Range(0, width-1).Select(c => new Point(){ Up = solver.CreateAnonymous(Domain.BinaryInteger), Down = solver.CreateAnonymous(Domain.BinaryInteger), Left = solver.CreateAnonymous(Domain.BinaryInteger), Right = solver.CreateAnonymous(Domain.BinaryInteger), Identifier = solver.CreateAnonymous(Domain.PositiveOrZeroInteger), Neighbours = new List<IVariable>() }).ToArray()).ToArray(); Console.WriteLine("Make neighbours see the path the same way"); for(int row=0;row<height-1;++row){ for(int column=0;column<width-1;++column){ if(column < width - 2) points[row][column].Right.Set<Equal>(points[row][column+1].Left); if(row < height - 2) points[row][column].Down.Set<Equal>(points[row+1][column].Up); } } Console.WriteLine("Ban forks"); for(int row=0;row<height-1;++row){ for(int column=0;column<width-1;++column){ var sum = solver.Operation<Addition>(points[row][column].AllDirections); sum.Set<LessOrEqual>(solver.FromConstant(2)); sum.Operation<Subtraction>(solver.FromConstant(1)).Operation<AbsoluteValue>().Set<Equal>(solver.FromConstant(1)); } } Console.WriteLine("Make one entry at the top and one at the bottom"); solver.Operation<Addition>(points[0].Select(p => p.Up).ToArray()).Set<Equal>(solver.FromConstant(1)); solver.Operation<Addition>(points[height - 2].Select(p => p.Down).ToArray()).Set<Equal>(solver.FromConstant(1)); Console.WriteLine("Make no entries to the left nor to the right"); solver.Operation<Addition>(Enumerable.Range(0, height - 1).Select(i => points[i][0].Left).ToArray()).Set<Equal>(solver.FromConstant(0)); solver.Operation<Addition>(Enumerable.Range(0, height - 1).Select(i => points[i][width - 2].Right).ToArray()).Set<Equal>(solver.FromConstant(0)); Console.WriteLine("Separate characters"); for(int row=0;row<height-1;++row){ for(int column=0;column<width-1;++column){ points[row][column].Up.Operation<MaterialImplication>(fields[row][column].Operation<IsNotEqual>(fields[row][column+1])).Set<Equal>(solver.FromConstant(1)); points[row][column].Down.Operation<MaterialImplication>(fields[row+1][column].Operation<IsNotEqual>(fields[row+1][column+1])).Set<Equal>(solver.FromConstant(1)); points[row][column].Left.Operation<MaterialImplication>(fields[row][column].Operation<IsNotEqual>(fields[row+1][column])).Set<Equal>(solver.FromConstant(1)); points[row][column].Right.Operation<MaterialImplication>(fields[row][column+1].Operation<IsNotEqual>(fields[row+1][column+1])).Set<Equal>(solver.FromConstant(1)); points[row][column].Up.Operation<BinaryNegation>().Operation<MaterialImplication>(fields[row][column].Operation<IsEqual>(fields[row][column+1])).Set<Equal>(solver.FromConstant(1)); points[row][column].Down.Operation<BinaryNegation>().Operation<MaterialImplication>(fields[row+1][column].Operation<IsEqual>(fields[row+1][column+1])).Set<Equal>(solver.FromConstant(1)); points[row][column].Left.Operation<BinaryNegation>().Operation<MaterialImplication>(fields[row][column].Operation<IsEqual>(fields[row+1][column])).Set<Equal>(solver.FromConstant(1)); points[row][column].Right.Operation<BinaryNegation>().Operation<MaterialImplication>(fields[row][column+1].Operation<IsEqual>(fields[row+1][column+1])).Set<Equal>(solver.FromConstant(1)); } } Console.WriteLine("Propagate identifiers"); for(int row=0;row<height-1;++row){ for(int column=0;column<width-1;++column){ List<Tuple<IVariable, IVariable>> neighbours = new List<Tuple<IVariable, IVariable>>(); Point self = points[row][column]; if(row > 0)neighbours.Add(Tuple.Create(points[row-1][column].Identifier, self.Up)); if(row < height - 2)neighbours.Add(Tuple.Create(points[row+1][column].Identifier, self.Down)); if(column > 0)neighbours.Add(Tuple.Create(points[row][column-1].Identifier, self.Left)); if(column < width - 2)neighbours.Add(Tuple.Create(points[row][column+1].Identifier, self.Right)); foreach(var n in neighbours){ var difference = self.Identifier.Operation<Subtraction>(n.Item1).Operation<AbsoluteValue>(); n.Item2.Operation<MaterialImplication>(difference.Operation<IsEqual>(solver.FromConstant(1))).Set<Equal>(solver.FromConstant(1)); } foreach(var n1 in neighbours){ foreach(var n2 in neighbours){ if(n1.Item1 == n2.Item1)continue; var difference = n1.Item1.Operation<Subtraction>(n2.Item1).Operation<AbsoluteValue>(); n1.Item2.Operation<Conjunction>(n2.Item2).Operation<MaterialImplication>(difference.Operation<IsEqual>(solver.FromConstant(2))).Set<Equal>(solver.FromConstant(1)); } } } } Console.WriteLine("Calculate lower neighbours"); for(int row=0;row<height-1;++row){ for(int column=0;column<width-1;++column){ var result = solver.FromConstant(0); var self = points[row][column].Identifier; if(row > 0){ result = result.Operation<Addition>(points[row][column].Up.Operation<Conjunction>(points[row-1][column].Identifier.Operation<IsLessThan>(self))); points[row][column].Neighbours.Add(points[row][column].Up); } if(row < height - 2){ result = result.Operation<Addition>(points[row][column].Down.Operation<Conjunction>(points[row+1][column].Identifier.Operation<IsLessThan>(self))); points[row][column].Neighbours.Add(points[row][column].Down); } if(column > 0){ result = result.Operation<Addition>(points[row][column].Left.Operation<Conjunction>(points[row][column-1].Identifier.Operation<IsLessThan>(self))); points[row][column].Neighbours.Add(points[row][column].Left); } if(column < width - 2){ result = result.Operation<Addition>(points[row][column].Right.Operation<Conjunction>(points[row][column+1].Identifier.Operation<IsLessThan>(self))); points[row][column].Neighbours.Add(points[row][column].Right); } points[row][column].LowerNeighbourCount = result; } } Console.WriteLine("Make fields having correct number of neighbours"); for(int row=0;row<height-1;++row){ for(int column=0;column<width-1;++column){ if(row == 0){ solver.Operation<Disjunction>(points[row][column].Neighbours.ToArray()).Operation<MaterialImplication>(points[row][column].LowerNeighbourCount.Operation<IsLessOrEqual>(solver.FromConstant(1))).Set<Equal>(solver.FromConstant(1)); }else{ solver.Operation<Disjunction>(points[row][column].Neighbours.ToArray()).Operation<MaterialImplication>(points[row][column].LowerNeighbourCount.Operation<IsEqual>(solver.FromConstant(1))).Set<Equal>(solver.FromConstant(1)); } } } solver.Operation<Addition>(points[0].Select(p => p.LowerNeighbourCount.Operation<IsEqual>(solver.FromConstant(0))).ToArray()).Set<Equal>(solver.FromConstant(1)); solver.AddGoal("g", solver.FromConstant(0)); solver.Solve();

First, we define the input. 0 indicates empty field, 1 indicates circle, 2 indicates cross.

We define binary variables for each field indicating what we choose for given field. We then translate existing board in lines 18-25.

Next, we block squares to have the same characters. This is done in lines 27-37 where we make sure that sum is at least one and at most three. If there were four the same characters we’d end up with sum equal to zero or four.

Next, we start building the path. We create variables in lines 39-47, then we make sure neighbours see the same assignments (49-55), and then we ban forks (lines 57-64). Notice that we create crossing points only in the middle of the board, not on the edges. Then we want to make sure that path starts at the top and enters the bottom only once (lines 66-68) and doesn’t touch side edges (70-72).

Next, we want to separate characters. If two fields are separated by a path then they must have different characters (77-80), otherwise they must be the same (82-85).

Finally, we start building the BFS graph to make sure there is only one path. So we propagate identifiers (lines 89-116), and then calculate lower neighbours (lines 118-142) as always.

Finally, we need to make sure that fields in the middle have exactly one lower neighbour, and in the top row there is exactly one point with no lower neighbours. The point would have one edge going to the top (and touching top side of the board) and one other outgoing edge. Since there is no point above the point then the up edge will be ignored, effectively ending with one neighbour only.

]]>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):

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:

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:

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:

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:

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:

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.

]]>This is the seventieth sixth 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 are going to solve Ślepe zaułki (Dead ends). We have a board with shapes. We need to choose shapes to build a one-lane road from start to end, going through all circles. Shapes with triangles must be selected as well but they must be in dead ends.

Data model:

public class Field{ public IVariable SelectedInPath {get;set;} public IVariable SelectedShape {get;set;} public IVariable Up {get;set;} public IVariable Down {get;set;} public IVariable Left {get;set;} public IVariable Right {get;set;} public IVariable[] AllDirections { get { return new IVariable[]{Up, Down, Left, Right}; } } public IVariable DirectionsCount {get;set;} public IVariable Identifier {get;set;} public IVariable LowerNeighbourCount {get;set;} }

`SelectedInPath`

indicates whether given field is on the path from start to end. `SelectedShape`

indicates whether this field is in a selected shape. Next, we have couple variables for directions (as usual). `Identifier`

and `LowerNeighbourCount`

are for building a connected graph representing the path.

Solution:

// Start - 1 // End - 2 // Circle - 3 // Triangle - 4 var board = new int[][]{ new int[]{1,0,0,0,0,3}, new int[]{0,0,0,0,0,0}, new int[]{0,3,0,0,0,0}, new int[]{4,0,0,0,0,0}, new int[]{0,0,4,0,0,0}, new int[]{0,0,0,0,0,2} }; var shapes = new int[][]{ new int[]{1,1,2,3,3,3}, new int[]{4,2,2,5,6,3}, new int[]{4,7,7,5,6,3}, new int[]{8,9,9,10,11,12}, new int[]{8,13,10,10,11,12}, new int[]{13,13,14,14,11,12} }; var height = board.Length; var width = board[0].Length; // Create fields var fields = board.Select(r => r.Select(c => new Field{ SelectedInPath = solver.CreateAnonymous(Domain.BinaryInteger), SelectedShape = solver.CreateAnonymous(Domain.BinaryInteger), Up = solver.CreateAnonymous(Domain.BinaryInteger), Down = solver.CreateAnonymous(Domain.BinaryInteger), Left = solver.CreateAnonymous(Domain.BinaryInteger), Right = solver.CreateAnonymous(Domain.BinaryInteger), Identifier = solver.CreateAnonymous(Domain.PositiveOrZeroInteger) }).ToArray()).ToArray(); // Block invalid directions for(int row=0;row<height;++row){ for(int column=0;column<width;++column){ if(row == 0)fields[row][column].Up.Set<Equal>(solver.FromConstant(0)); if(row == height - 1)fields[row][column].Down.Set<Equal>(solver.FromConstant(0)); if(column == 0)fields[row][column].Left.Set<Equal>(solver.FromConstant(0)); if(column == width - 1)fields[row][column].Right.Set<Equal>(solver.FromConstant(0)); } } // Match direction neighbours for(int row=0;row<height;++row){ for(int column=0;column<width;++column){ if(row > 0)fields[row][column].Up.Set<Equal>(fields[row-1][column].Down); if(row < height - 1)fields[row][column].Down.Set<Equal>(fields[row+1][column].Up); if(column > 0)fields[row][column].Left.Set<Equal>(fields[row][column-1].Right); if(column < height - 1)fields[row][column].Right.Set<Equal>(fields[row][column+1].Left); } } // Set Selected properly for(int row=0;row<height;++row){ for(int column=0;column<width;++column){ fields[row][column].SelectedInPath.Set<Equal>(solver.Operation<Disjunction>(fields[row][column].AllDirections)); } } // Calculate lower neighbours for(int row=0;row<height;++row){ for(int column=0;column<width;++column){ var result = solver.FromConstant(0); var self = fields[row][column].Identifier; if(row > 0)result = result.Operation<Addition>(fields[row][column].Up.Operation<Conjunction>(fields[row-1][column].Identifier.Operation<IsLessThan>(self))); if(row < height - 1)result = result.Operation<Addition>(fields[row][column].Down.Operation<Conjunction>(fields[row+1][column].Identifier.Operation<IsLessThan>(self))); if(column > 0)result = result.Operation<Addition>(fields[row][column].Left.Operation<Conjunction>(fields[row][column-1].Identifier.Operation<IsLessThan>(self))); if(column < width - 1)result = result.Operation<Addition>(fields[row][column].Right.Operation<Conjunction>(fields[row][column+1].Identifier.Operation<IsLessThan>(self))); fields[row][column].LowerNeighbourCount = result; } } // Make fields having correct number of neighbours for(int row=0;row<height;++row){ for(int column=0;column<width;++column){ if(board[row][column] == 1){ fields[row][column].LowerNeighbourCount.Set<Equal>(solver.FromConstant(0)); }else{ fields[row][column].SelectedInPath.Operation<MaterialImplication>(fields[row][column].LowerNeighbourCount.Operation<IsEqual>(solver.FromConstant(1))).Set<Equal>(solver.FromConstant(1)); } } } // Propagate identifiers for(int row=0;row<height;++row){ for(int column=0;column<width;++column){ List<Tuple<IVariable, IVariable>> neighbours = new List<Tuple<IVariable, IVariable>>(); Field self = fields[row][column]; if(row > 0)neighbours.Add(Tuple.Create(fields[row-1][column].Identifier, self.Up)); if(row < height - 1)neighbours.Add(Tuple.Create(fields[row+1][column].Identifier, self.Down)); if(column > 0)neighbours.Add(Tuple.Create(fields[row][column-1].Identifier, self.Left)); if(column < width - 1)neighbours.Add(Tuple.Create(fields[row][column+1].Identifier, self.Right)); foreach(var n in neighbours){ var difference = self.Identifier.Operation<Subtraction>(n.Item1).Operation<AbsoluteValue>(); n.Item2.Operation<MaterialImplication>(difference.Operation<IsEqual>(solver.FromConstant(1))).Set<Equal>(solver.FromConstant(1)); } foreach(var n1 in neighbours){ foreach(var n2 in neighbours){ if(n1.Item1 == n2.Item1)continue; var difference = n1.Item1.Operation<Subtraction>(n2.Item1).Operation<AbsoluteValue>(); n1.Item2.Operation<Conjunction>(n2.Item2).Operation<MaterialImplication>(difference.Operation<IsEqual>(solver.FromConstant(2))).Set<Equal>(solver.FromConstant(1)); } } } } // Make sure shape is filled foreach(var shape in shapes.SelectMany(r => r).Distinct()){ List<IVariable> shapeFields = new List<IVariable>(); for(int row=0;row<height;++row){ for(int column=0;column<width;++column){ if(shapes[row][column] == shape){ shapeFields.Add(fields[row][column].SelectedShape); } } } solver.Operation<Addition>( solver.Operation<Conjunction>(shapeFields.ToArray()), solver.Operation<Disjunction>(shapeFields.ToArray()).Operation<BinaryNegation>() ).Set<Equal>(solver.FromConstant(1)); } // Make sure S, G, circles and traingles are selected for(int row=0;row<height;++row){ for(int column=0;column<width;++column){ if(board[row][column] > 0){ fields[row][column].SelectedShape.Set<Equal>(solver.FromConstant(1)); fields[row][column].SelectedInPath.Set<Equal>(solver.FromConstant(board[row][column] == 4 ? 0 : 1)); } } } // Path can be on selected shapes only for(int row=0;row<height;++row){ for(int column=0;column<width;++column){ fields[row][column].SelectedInPath.Set<LessOrEqual>(fields[row][column].SelectedShape); } } // Make sure path is "one lane only" for(int row=0;row<height-1;++row){ for(int column=0;column<width-1;++column){ solver.Operation<Addition>( fields[row][column].SelectedShape, fields[row][column+1].SelectedShape, fields[row+1][column].SelectedShape, fields[row+1][column+1].SelectedShape ).Set<LessOrEqual>(solver.FromConstant(3)); } } solver.Solve(); for(int row=0;row<height;++row){ for(int column=0;column<width;++column){ Console.Write(board[row][column] + ","); } Console.WriteLine(); } Console.WriteLine(); for(int row=0;row<height;++row){ for(int column=0;column<width;++column){ Console.Write(solver.GetValue(fields[row][column].SelectedInPath) + ","); } Console.WriteLine(); } Console.WriteLine(); for(int row=0;row<height;++row){ for(int column=0;column<width;++column){ Console.Write(solver.GetValue(fields[row][column].SelectedShape) + ","); } Console.WriteLine(); } Console.WriteLine();

In lines 5-23 we define an input.

In lines 26-34 we create variables representing the model.

Next, we make sure directions for the path are propagated okay (lines 36-44, 46-54, 56-61).

Next, we calculate lower neighbours. We propagate an identifier through the graph (distance from the source) and here we just check directions and calculate neighbours with lower identifier. Next, in lines 77-86 we make sure the source has no lower neighbours and other fields on the path do. This is to make sure the path is connected.

Next, in 88-115 we propagate identifiers for the path. We make sure they differ by one accordingly.

Finally, we encode some rules for the task specifically. We make sure that shape is selected as one piece (lines 117-133). We then make sure that start, end, circles and triangles are selected and are on the path (or, for triangles, they are not on the path). We then make sure the path goes through selected shapes only (145-150). And at the end we guarantee that the path is “one lane” (lines 152-162).

Finally, solution:

Tried aggregator 3 times. MIP Presolve eliminated 4975 rows and 1893 columns. MIP Presolve modified 5978 coefficients. Aggregator did 2787 substitutions. Reduced MIP has 7501 rows, 4384 columns, and 22839 nonzeros. Reduced MIP has 3939 binaries, 445 generals, 0 SOSs, and 0 indicators. Presolve time = 0.08 sec. (69.66 ticks) Probing fixed 10 vars, tightened 3 bounds. Probing changed sense of 10 constraints. Probing time = 0.00 sec. (2.95 ticks) Tried aggregator 1 time. MIP Presolve eliminated 87 rows and 45 columns. MIP Presolve modified 31 coefficients. Reduced MIP has 7414 rows, 4339 columns, and 22585 nonzeros. Reduced MIP has 3894 binaries, 445 generals, 0 SOSs, and 0 indicators. Presolve time = 0.01 sec. (16.60 ticks) Probing fixed 222 vars, tightened 107 bounds. Probing changed sense of 19 constraints. Probing time = 0.02 sec. (3.01 ticks) Cover probing fixed 0 vars, tightened 4 bounds. Clique table members: 8099. MIP emphasis: balance optimality and feasibility. MIP search method: dynamic search. Parallel mode: deterministic, using up to 8 threads. Root relaxation solution time = 0.08 sec. (55.88 ticks) Nodes Cuts/ Node Left Objective IInf Best Integer Best Bound ItCnt Gap 0 0 0.0000 284 0.0000 839 0 0 0.0000 183 Cuts: 150 938 0 0 0.0000 147 Cuts: 157 1028 Repeating presolve. Tried aggregator 3 times. MIP Presolve eliminated 5111 rows and 3003 columns. MIP Presolve modified 251 coefficients. Aggregator did 19 substitutions. Reduced MIP has 2284 rows, 1317 columns, and 6858 nonzeros. Reduced MIP has 1147 binaries, 170 generals, 0 SOSs, and 0 indicators. Presolve time = 0.05 sec. (21.57 ticks) Probing time = 0.00 sec. (1.94 ticks) Tried aggregator 1 time. Reduced MIP has 2284 rows, 1317 columns, and 6858 nonzeros. Reduced MIP has 1147 binaries, 170 generals, 0 SOSs, and 0 indicators. Presolve time = 0.02 sec. (2.85 ticks) Represolve time = 0.06 sec. (31.84 ticks) Probing fixed 2 vars, tightened 0 bounds. Probing time = 0.02 sec. (1.65 ticks) Clique table members: 1357. MIP emphasis: balance optimality and feasibility. MIP search method: dynamic search. Parallel mode: deterministic, using up to 8 threads. Root relaxation solution time = 0.01 sec. (13.26 ticks) Nodes Cuts/ Node Left Objective IInf Best Integer Best Bound ItCnt Gap 0 0 0.0000 97 0.0000 1408 0 0 0.0000 52 Cuts: 56 1449 0 0 0.0000 73 Cuts: 70 1502 0 0 0.0000 98 Cuts: 10 1510 * 0+ 0 0.0000 0.0000 1510 0.00% 0 0 cutoff 0.0000 0.0000 1510 0.00% Elapsed time = 1.30 sec. (606.96 ticks, tree = 0.00 MB, solutions = 1) GUB cover cuts applied: 28 Clique cuts applied: 5 Cover cuts applied: 27 Implied bound cuts applied: 74 Zero-half cuts applied: 38 Gomory fractional cuts applied: 29 Root node processing (before b&c): Real time = 1.31 sec. (607.25 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) = 1.31 sec. (607.25 ticks) 1,0,0,0,0,3, 0,0,0,0,0,0, 0,3,0,0,0,0, 4,0,0,0,0,0, 0,0,4,0,0,0, 0,0,0,0,0,2, 1,0,0,1,1,1, 1,0,0,1,0,1, 1,1,1,1,0,1, 0,0,0,0,0,1, 0,0,0,0,0,1, 0,0,0,0,0,1, 1,1,0,1,1,1, 1,0,0,1,0,1, 1,1,1,1,0,1, 1,0,0,1,0,1, 1,0,1,1,0,1, 0,0,0,0,0,1,]]>

This is the seventieth fifth 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 are going to solve 100 and 13. We have a board with numbers and we need to find a path with given amount of numbers, with some specific sum, and where all numbers are different. First, data model:

public class Field{ public int Value {get;set;} public IVariable Selected {get;set;} public IVariable Up {get;set;} public IVariable Down {get;set;} public IVariable Left {get;set;} public IVariable Right {get;set;} public IVariable[] AllDirections { get { return new IVariable[]{Up, Down, Left, Right}; } } public IVariable DirectionsCount {get;set;} public IVariable Identifier {get;set;} }

And now the solution:

var board = new int[][]{ new int[]{2, 5, 11, 4, 6}, new int[]{10, 14, 9, 15, 6}, new int[]{7, 15, 9, 8, 1}, new int[]{8, 11, 1, 3, 10}, new int[]{3, 14, 13, 5, 2} }; var sum = 100; var length = 13; var height = board.Length; var width = board[0].Length; // Create fields var fields = board.Select(r => r.Select(c => new Field{ Value = c, Selected = solver.CreateAnonymous(Domain.BinaryInteger), Up = solver.CreateAnonymous(Domain.BinaryInteger), Down = solver.CreateAnonymous(Domain.BinaryInteger), Left = solver.CreateAnonymous(Domain.BinaryInteger), Right = solver.CreateAnonymous(Domain.BinaryInteger), Identifier = solver.CreateAnonymous(Domain.PositiveOrZeroInteger) }).ToArray()).ToArray(); // Block invalid directions for(int row=0;row<height;++row){ for(int column=0;column<width;++column){ if(row == 0)fields[row][column].Up.Set<Equal>(solver.FromConstant(0)); if(row == height - 1)fields[row][column].Down.Set<Equal>(solver.FromConstant(0)); if(column == 0)fields[row][column].Left.Set<Equal>(solver.FromConstant(0)); if(column == width - 1)fields[row][column].Right.Set<Equal>(solver.FromConstant(0)); } } // Match direction neighbours for(int row=0;row<height;++row){ for(int column=0;column<width;++column){ if(row > 0)fields[row][column].Up.Set<Equal>(fields[row-1][column].Down); if(row < height - 1)fields[row][column].Down.Set<Equal>(fields[row+1][column].Up); if(column > 0)fields[row][column].Left.Set<Equal>(fields[row][column-1].Right); if(column < height - 1)fields[row][column].Right.Set<Equal>(fields[row][column+1].Left); } } // Set Selected properly for(int row=0;row<height;++row){ for(int column=0;column<width;++column){ fields[row][column].Selected.Set<Equal>(solver.Operation<Disjunction>(fields[row][column].AllDirections)); } } // Set path continuous for(int row=0;row<height;++row){ for(int column=0;column<width;++column){ fields[row][column].DirectionsCount = solver.Operation<Addition>(fields[row][column].AllDirections); fields[row][column].DirectionsCount.Set<LessOrEqual>(solver.FromConstant(2)); } } // Set path with start an end solver.Operation<Addition>(fields.SelectMany(r => r).Select(f => f.DirectionsCount).ToArray()).Set<Equal>(solver.FromConstant((length - 2)*2 + 2)); // Set path length solver.Operation<Addition>(fields.SelectMany(r => r).Select(f => f.Selected).ToArray()).Set<Equal>(solver.FromConstant(length)); // Set path sum solver.Operation<Addition>(fields.SelectMany(r => r).Select(f => solver.Operation<Condition>(f.Selected, solver.FromConstant(f.Value), solver.FromConstant(0))).ToArray()).Set<Equal>(solver.FromConstant(sum)); // Set different values on the path { IVariable[] parts = fields.SelectMany(r => r).Select(f => solver.Operation<Condition>(f.Selected, solver.FromConstant(f.Value), solver.CreateAnonymous(Domain.PositiveOrZeroInteger).Set<GreaterOrEqual>(solver.FromConstant(100)).Set<LessOrEqual>(solver.FromConstant(width*height - length + 100)))).ToArray(); solver.Set<AllDifferent>(parts[0], parts.Skip(1).ToArray()); } // Propagate identifiers for(int row=0;row<height;++row){ for(int column=0;column<width;++column){ List<Tuple<IVariable, IVariable>> neighbours = new List<Tuple<IVariable, IVariable>>(); Field self = fields[row][column]; if(row > 0)neighbours.Add(Tuple.Create(fields[row-1][column].Identifier, self.Up)); if(row < height - 1)neighbours.Add(Tuple.Create(fields[row+1][column].Identifier, self.Down)); if(column > 0)neighbours.Add(Tuple.Create(fields[row][column-1].Identifier, self.Left)); if(column < width - 1)neighbours.Add(Tuple.Create(fields[row][column+1].Identifier, self.Right)); foreach(var n in neighbours){ var difference = self.Identifier.Operation<Subtraction>(n.Item1).Operation<AbsoluteValue>(); n.Item2.Operation<MaterialImplication>(difference.Operation<IsEqual>(solver.FromConstant(1))).Set<Equal>(solver.FromConstant(1)); } foreach(var n1 in neighbours){ foreach(var n2 in neighbours){ if(n1.Item1 == n2.Item1)continue; var difference = n1.Item1.Operation<Subtraction>(n2.Item1).Operation<AbsoluteValue>(); n1.Item2.Operation<Conjunction>(n2.Item2).Operation<MaterialImplication>(difference.Operation<IsEqual>(solver.FromConstant(2))).Set<Equal>(solver.FromConstant(1)); } } } } solver.Solve(); for(int row=0;row<height;++row){ for(int column=0;column<width;++column){ Console.Write(solver.GetValue(fields[row][column].Selected) + ","); } Console.WriteLine(); } Console.WriteLine();

First, we define the riddle in lines 1-9, and couple helper variables in 12-13.

Next, we create fields in lines 16-24. `Value`

is just the number in the field. `Selected`

indicates whether the field is chosen to the final result. `Up`

and other directions are for indicting whether path continues in some specific direction from this field. `Identifier`

is to propagate numbers throughout the path to make sure we have only one path on the board.

First, we block invalid directions in lines 26-34. We want to make sure that we don’t step out of the board.

Next, in lines 36-44 we make sure that if some field thinks that path is going in some direction then the neighbour thinks the same.

Next, we make sure that if there is a path in the field then it must be selected (lines 46-51).

Next, we make sure the path is continuous (lines 53-59). This means that it cannot fork so it can go in at most two directions in given field.

Next, we indicate the path has a start and an end (line 62). This is actually not needed as we block forks but better safe than sorry.

We set path length (line 65) and sum of the numbers (line 68).

Next, we make sure that all numbers on the path are different. In lines 71-74 we take all variables and check their `SelectedIdentifier`

to set this constraint, given field must differ from its neighbours by one, and its neighbours must differ by two. This effectively makes creating loops impossible.

Finally, solution:

Tried aggregator 2 times. MIP Presolve eliminated 2694 rows and 888 columns. MIP Presolve modified 8814 coefficients. Aggregator did 2484 substitutions. Reduced MIP has 7203 rows, 3808 columns, and 21521 nonzeros. Reduced MIP has 3415 binaries, 393 generals, 0 SOSs, and 0 indicators. Presolve time = 0.09 sec. (48.76 ticks) Probing fixed 70 vars, tightened 50 bounds. Probing changed sense of 325 constraints. Probing time = 0.05 sec. (9.71 ticks) Cover probing fixed 0 vars, tightened 50 bounds. Tried aggregator 2 times. MIP Presolve eliminated 304 rows and 193 columns. MIP Presolve modified 1900 coefficients. Aggregator did 300 substitutions. Reduced MIP has 6599 rows, 3315 columns, and 19898 nonzeros. Reduced MIP has 2922 binaries, 393 generals, 0 SOSs, and 0 indicators. Presolve time = 0.05 sec. (20.59 ticks) Probing time = 0.05 sec. (4.77 ticks) Tried aggregator 1 time. MIP Presolve eliminated 100 rows and 50 columns. Reduced MIP has 6499 rows, 3265 columns, and 19598 nonzeros. Reduced MIP has 2872 binaries, 393 generals, 0 SOSs, and 0 indicators. Presolve time = 0.05 sec. (13.46 ticks) Probing time = 0.03 sec. (3.85 ticks) Clique table members: 13256. MIP emphasis: balance optimality and feasibility. MIP search method: dynamic search. Parallel mode: deterministic, using up to 8 threads. Root relaxation solution time = 0.06 sec. (54.46 ticks) Nodes Cuts/ Node Left Objective IInf Best Integer Best Bound ItCnt Gap 0 0 0.0000 61 0.0000 913 0 0 0.0000 212 Cuts: 509 1471 0 0 0.0000 265 Cuts: 554 1990 Repeating presolve. Tried aggregator 1 time. MIP Presolve eliminated 2554 rows and 1527 columns. Reduced MIP has 3945 rows, 1738 columns, and 11809 nonzeros. Reduced MIP has 1473 binaries, 265 generals, 0 SOSs, and 0 indicators. Presolve time = 0.02 sec. (10.44 ticks) Probing time = 0.02 sec. (2.67 ticks) Tried aggregator 1 time. Reduced MIP has 3945 rows, 1738 columns, and 11809 nonzeros. Reduced MIP has 1473 binaries, 265 generals, 0 SOSs, and 0 indicators. Presolve time = 0.02 sec. (4.85 ticks) Represolve time = 0.06 sec. (23.96 ticks) Probing time = 0.01 sec. (2.67 ticks) Clique table members: 7703. MIP emphasis: balance optimality and feasibility. MIP search method: dynamic search. Parallel mode: deterministic, using up to 8 threads. Root relaxation solution time = 0.08 sec. (47.44 ticks) Nodes Cuts/ Node Left Objective IInf Best Integer Best Bound ItCnt Gap 0 0 0.0000 76 0.0000 2868 0 0 0.0000 142 Cuts: 459 3911 0 0 0.0000 141 Cuts: 84 4355 0 2 0.0000 22 0.0000 4355 Elapsed time = 4.05 sec. (1387.34 ticks, tree = 0.01 MB, solutions = 0) 15 17 0.0000 194 0.0000 9806 * 32+ 15 0.0000 0.0000 18968 0.00% GUB cover cuts applied: 33 Clique cuts applied: 144 Cover cuts applied: 20 Implied bound cuts applied: 287 Mixed integer rounding cuts applied: 8 Zero-half cuts applied: 41 Gomory fractional cuts applied: 8 Root node processing (before b&c): Real time = 4.05 sec. (1385.51 ticks) Parallel b&c, 8 threads: Real time = 0.89 sec. (441.92 ticks) Sync time (average) = 0.56 sec. Wait time (average) = 0.00 sec. ------------ Total (root+branch&cut) = 4.94 sec. (1827.43 ticks) 1,1,1,1,1, 1,0,0,0,0, 1,1,1,0,0, 0,0,1,0,0, 1,1,1,0,0,]]>

This is the sixth part of the Types and Programming Languages series. For your convenience you can find other parts in the table of contents in Part 1 — Do not return in finally

Today couple words about Liskov substitution princple (LSP for short). It is often misunderstood which leads to unclear directions how to apply it. I know there are multiple articles on the Internet explaining LSP, however, they often focus on incorrect examples and spread misinformation. In this post I’ll briefly cover the principle and common misconceptions I often see.

It’s important to understand where the principle comes from. Many programmers learned LSP by example with `Rectangle`

and `Square`

types which at some point was almost everywhere on the Internet. For instance, you could find it on Wikipedia as a “typical violation”, in discussion on Stack Overflow, or even in Robert C. Martin’s article.

The example is typically stated as follows: we have a class `Rectangle`

with two setters for setting side lengths. Then we derive with class `Square`

which modifies both sides’ lengths at once when any of the setters is called. Explanation typically says that modifying both sides at once violates the LSP.

This example is flawed and completely misses the point. To see why we need to understand main point of LSP.

The principle comes from A Behavioral Notion of Subtyping paper from 1994. It considers issues with covariance, contravariance, subtyping, contracts, invariants, and history. I assume you understand what these things are as I won’t be defining them here. Important part is this requirement:

Subtype Requirement: Let be a property provable about objects of type . The should be true for objects of type where is a subtype of .

In short, we have a set of properties which we can prove for a base type. We require the same properties being provable for any instance of the subtype. This sounds simple but may actually be very misleading. Let’s take the following C# code:

class BaseType{ public virtual void Foo(){ Console.WriteLine("Literal"); } }

I compiled it using Sharplab and this is the machine code I obtained:

BaseType.Foo() L0000: mov ecx, [0x19d27e88] L0006: call System.Console.WriteLine(System.String) L000b: ret

Let’s get back to the Subtype Requirement. I can define for of type `BaseType`

as . Can I prove it for instances of type `BaseType`

? Yes, I can. So according to the Subtype Requirement, all subtypes should define `Foo`

in a way that it generates exactly the same machine code. Effectively this means that there is no use in overriding the method as it must work in the same way.

Clearly, this is very limiting as it effectively blocks logic overriding in subtypes. Is that what LSP says?

LSP is all about the contract we define. The paper discusses invariants, pre- and post- conditions, and history principle which all define how the type should behave. Important thing to understand here is that contract is given explicitly. It’s not something we assume because “it’s reasonable” or “it’s logical”. It must be clearly defined so users know what to expect.

Coming back to the Rectangle Example. The way it’s stated makes sense, Square modifies both sides at once so it “clearly” violates the principle. Let’s now consider a little more realistic example.

We have a type `Component`

which represents an element of a user interface. It has two dimensions: width and height. Now, let’s introduce a `RatioPreservingComponent`

which shows an image but preserves the ratio (e.g. 4:3) so when we modify one side then the other gets updated as well. Does it break LSP? If you answered “no” then explain how is it different from Rectangle Example? If you answered “yes” then think for a sec how to model your components so you don’t break the principle.

Just to be clear: `RatioPreservingComponent`

does not break LSP nor does `Square`

. But it’s not because “changing two fields instead of one doesn’t break LSP”. It’s because LSP considers contract only, not something which makes or doesn’t make sense. It covers contracts which are well defined and explicit. Even Robert C. Martin makes this mistake when discussing LSP as he says:

Was the programmer who wrote that function justified in assuming that changing the width of a Rectangle leaves its height unchanged?

Clearly, the programmer of g made this very reasonable assumption.

But the whole point of LSP is to not make “reasonable assumptions” but to define contracts. Otherwise we may assume that subtypes’ methods should have exactly the same machine code. If you think that it’s illogical (I agree) then how do we define which assumptions are “just right” and which should be ignored?

Now, how do we fix the Rectangle Example? We need to explicitly define the contract saying that setter for one side should never modify the other. Yes, that’s all we need, just one line of comment and then the contract is specified explicitly and we don’t need to wonder if “programmer made a reasonable assumption”.

And just to clarify one thing. I’m not saying that `RationPreservingComponent`

is the right way to model that constraint nor that it’s a nice design. All I’m saying is that it doesn’t break LSP.

How can we specify a contract? That depends a lot on the technology you use and what your platform supports. Let’s cover couple of mechanisms.

Probably the most powerful mechanism and most commonly used. By specifying variables’ types we can specify the contract (and often enforce it at the same time). For instance, if method of your type returns `String`

then subtypes’ methods should return `String`

as well (up to some covariance, obviously). Depending on the language it may not be possible to change return type in the subtype or it may be allowed with some techniques like bridge methods.

Some languages support dependent types and allow to define requirements even more. For instance, in Idris you can specify that returned list has the same number of elements as the list provided in the input parameter. Even nullability can be supported this way, for instance by using non-null types or type annotations.

Type system has yet another powerful feature. It is typically verified by the compiler which means that it’s “impossible” to generate a program which breaks the type system. We typically don’t even think about that as an LSP but that’s exactly it: an explicit specification of a type is a contract which must not be violated.

It may sound like the same thing as in previous section but it includes couple more things. A signature of a function is something which uniquely identifies it in the codebase. Typically it includes function name and parameters. Depending on the language we may say that it includes returned type as well. However, it is much broader.

Signature specifies a way to call the method. If we have two methods matching the same signature then we may need some external rules to deciding which one to call (for instance depending on the operator overloading) but generally signature uniquely tells which method to call and how to do it. So it must include other things as well: calling convention (where to put parameters and how), marshalling (how to encode values), cleanup rules (who removes parameters from the stack), parameter mangling (whether to encode parameter types in the name), who deallocates memory and many more. This even covers the thing which we take for granted now — that the subtype even has the method. Something which wasn’t obvious in early days.

If all you do is call one Java method from another Java method then you don’t need to think about this at all as JVM takes care of that. But once you enter interop world and call native code from managed one then you need to think about all these little parts.

Yes, this is yet another way to define a contract. This could be anything, unstructured comment in the codebase, javadoc, official documentation, internal wiki, or even well-known convention that functions like these in this org do something in this specific way.

This method may be a little brittle as we know that five hours of debugging can save you five minutes of reading the documentation. It typically cannot be verified automatically, not to mention that programmers often don’t even know where to find it. However, this is actually THE way to define the contract as most languages don’t allow to define all the requirements we may have using type system or with things verified by the compiler.

Yet another way to define a contract. Actually, according to “clean code” principles, unit tests can serve as documentation, tutorial, and automated way to verify contracts.

But how do you verify contracts with unit tests? One of the techniques is to tests your mocks with unit tests for production code. Imagine that you have a repository class which encapsulates some data collection. It may be backed by database or whatever else if you wish (I’m not going to discuss what repository is and whether it should have generic methods, not this time). At the same time you may have fake repository in your unit tests which holds objects in memory. Since it should mimic the real one, you should be able to test it with your tests as well.

So we can define unit tests to verify contracts. Coming back to Rectangle Example — we can actually write a unit test to verify that calling one setter doesn’t change the other side length and then run this test on all subtypes in our codebase.

There are multiple things which we would like to hold but we can’t verify easily. This may include Coq proofs (like in Idris), declarative constraints (as in ILP), or consistency protocols (with TLA+). This is often used in a little different context, we typically have the contract and want to make sure it’s not violated by the type in any situation. It’s not like in unit tests where we verify examples. With formal proofs we want to provide guarantees that it works for all abstraction classes uniformly.

There is one more thing which we need to cover here. It’s called Hyrum’s Law and it says that once we have enough users then it doesn’t matter what we put in the contract as all observable behavior will be taken as a dependency at some point.

While this law is obviously correct, it’s beyond the scope of LSP. In theory we can always say that things not included in contracts can be changed freely. However, once our application is mature enough it may be just too expensive to change behavior which someone depends upon. Again, this is beyond the scope of LSP as Liskov principle talks about explicit contracts.

And now couple misconceptions.

First, already mentioned Rectangle Example. Once again, it’s about saying explicitly that one setter cannot change the length of the other side.

Remove operation in java.util.List. It explicitly says that `remove`

is an `optional operation`

. It throws `UnsupportedOperationException`

in that case which is clearly specified in the documentation. Some programmers think that if a subtype of List doesn’t implement removal operation then it breaks LSP. It’s exactly the opposite. Once again, LSP is not about some “reasonable assumptions”, it’s about explicitly defined contracts. Actually, similar thing is with putting to map as it is an optional operation.

Objects’ equality. We may try implementing `equals`

as a mix of casting another variable to our type and comparing fields. However, this won’t work because it must be symmetric, so `x.equals(y)`

must return `true`

if and only if `y.equals(x)`

does. However, even if we check inheritance hierarchy and cast to the base type, we break the transitivity. `equals`

with subtypes adding more state must always return `false`

.

It looks slightly different with equality operator for floating point numbers based on IEEE754. It is defined that `NaN == NaN`

returns false. `Equals`

and `==`

are different.

Hash code implementation. We need to remember that hash code is not required to return distinct value when called on unequal objects. Specifically, hash code returning the same constant value for all instances is perfectly fine, even though not very practical. Another misconception is that hash code must be constant over object’s lifetime. It’s not true, hash code is allowed to change when fields used for equality change.

Another misconception is that interfaces do not take part in LSP as they don’t define any implementation so there is nothing to be broken in the subtype. I believe after reading this post it’s obvious how far from truth it is.

]]>