Formulating the problem

Define the variables

After reading your problem and getting a strong grasp of the goal, you should start with defining the variables. What would the decision look like? Is it continuous, integer, binary? Does it have bounds? Technically this is a constraint but it may be helpful to think about this in the beginning so you don’t forget to add it later.

For this puzzle, the decision is simply: what number should go into a given cell?

My variable name is going to be x, it’s going to be binary, and it is going to be indexed by three values:

v is the value of the number from 1 to 7 that we are considering

r is the row index from 1 to 7

c is the column index from 1 to 7

In math notation, this is

The resulting solution will then tell us which number should go into which cell. For instance, if the solution specifies x_{1, 1, 1}=1, then that means number 1 goes into cell at location (1, 1). If x_{3, 4, 7}=0, then that means number 3 does not go into cell at location (4, 7). We will have a solution when we know all of these 1s and 0s for each number and cell combination.

This is going to create 7•7•7 = 343 binary variables. Binary variables will always be more difficult to solve than continuous-valued variables for LP problems. Fortunately this is a small number of variables nowadays, and much larger problems with hundreds of thousands of variables can be solved by solvers nowadays. You should have no problem solving this problem locally.

Define the objective

This problem is unusual — there is no objective! Set it to a constant, such as 0. Our goal is just to find a solution that meets all constraints; there is no measure of one solution being better than another. As long as the result is feasible, we will have achieved the goal. Thus we don’t need an objective function.

Define the constraints

Here’s where the heavy-lifting comes in. Let’s tackle them one at a time.

Each cell can only contain one number or it can be blank.

For a given cell, if the sum of the binary variables for v=1,2,…7 equals zero, then I’ll interpret it as the cell is blank. We should use an inequality in the constraint to allow for these null cells, and set the upper threshold to 1 because we don’t want more than one number per cell.

Number of occurrences of each value v (not including the null) must equal the value v. This ensures that the number of fives in the whole grid is five, the number of sixes is six, etc. We know the value of the number since it is equal to the index v, so that will be the right-hand-side of the constraint.

Each row has 4 numbers. Also, each column has 4 numbers. So just sum over all the columns (rows) and all the possible values. This must be less 4 because there must be 4 numbers.

The sum of numbers in each row should be 20. Also each column’s sum should be 20. This is similar to the constraint above except we need to multiply by the value of the number, v.

Every 2x2 sub-square must contain at least one empty cell. Thus not every cell can be filled, so there should be 3 or less numbers in a sub-square.

Need to check that each subsquare (yellow region) cannot have at most 3 numbers.

The result should have a connected region. This also means that a number should not be surrounded by nulls.

The question mark should not contain a value if the surrounding cells are empty.

This is achieved if we look at at the cells adjacent to a given location (think of drawing a box around a number). If the region is entirely full of nulls, then the sum of the binary variables for that border will be zero. In that case, we then want to force the center number to be null too in order to satisfy the rules. If the count of non-null values in the border is greater than zero, then a number in the center is allowed.

The constraints need to be written depending on where they appear in the grid. For the points where we can draw a 3x3 box (see image below) around the cell, the following equation will apply

The equation above applies to these cells.

This equation is modified to accommodate the remaining areas of the grid. For instance, for the cells in the top row, the equation will be modified for the rows to range from 1 to 2 (since there is no 0 row).

The rest of the equations can be similarly modified for the first and last columns and the cells in the corner.

The start of the row/column should start/end with a particular number. For the case of row 2 needs to start with 3, then it means that the first column in row 2 can be null or 3. After applying that, then if we move one-by-one across row 2, then a cell can only have a number if the cells to its left had a 3.

The remaining start/end constraints follow a similar formulation.

That’s it! Those are all the steps needed in order to define the problem. Great work and glad you’re still with me. Now, the next step is to code it up.