0

I want to generate a 20x38 binary matrix based on some constraints that I have using a dpcplex model. Some of the matrix cells are predefind as below (row,col,parameter):

[(8,3,0),(14,0,0),(14,2,0),(16,0,0),(16,1,0),(12,0,0),(10,0,0),(10,8,0),(10,9,0),(17,7,0),(17,8,0),(8,0,0),(13,8,0),(13,9,0),(1,0,1),(15,19,0)]

I need to fill out the other matrix cells with some constraints:

  • sum of columns must equal to 10
  • sum of rows must equal to 19
  • last 4 cells of each row must be alternative: just 1010 or 0101 is allowed
  • no more that 2 consecutive 0s or 1s
  • sum of every 5 cells in each row must be in range [2,3] : no 11011 or 00100
  • sum of pair of consecutive 0s must be <=3 : in each row we are not allowed to have more than 3 pair of 00 and 3 pairs of 11

The problem is that my model doesn't return any solutions. I am not sure if my model is correct.

here is my code:

from docplex.mp.model import Model

cond=[[8,3,0],[1,37,0],[6,9,0]]

model = Model("MatrixComple")

R = [i for i in range(20)]
R1=[i for i in range(38)]
R2=[34,35,36,37]
R3=[i for i in range(36)]
R4=[i for i in range(34)]
R5=[i for i in range(37)]
idx = [(i, j) for i in R for j in R1 ]

x = model.binary_var_dict(idx,name= "x")

"""pre-defined cells"""
for i in R:
    for j in R1:
        for item in cond:
            i1,i2,i3=item
            model.add_constraint(x[i1, i2] == i3)

"""sum of columns must be equal to 10   """    
model.add_constraints(model.sum(x[i, j] for i in R) == 10 for j in R2)

"""sum of rows must be equal to 19  """
model.add_constraints(model.sum(x[i, j] for j in R1) == 19 for i in R)

"""(apply to all rows)last 4 cells of each row must be alternative: just 1010 or 0101 is allowed"""
model.add_constraints(model.sum(x[(i, j)] for j in R2 ) == 2 for i in R  )
model.add_constraints(x[(i, 34)] ==x[(i, 36)]  for i in R  )


"""no more that 2 consecutive 0s or 1s : 110 or 001 or 101 or 010
this rule can not be applied to pre-defined cells. For example if we have 000 or 111 in pre-defined conditions,
we need to apply this rule for the rest of matrix not the pre-defined cells
""" 
model.add_constraints(model.sum(x[i, j]+x[i,j+1]+x[i,j+2] for j in R3) <=2 for i in R)
model.add_constraints(model.sum(x[i, j]+x[i,j+1]+x[i,j+2] for j in R3) >=1 for i in R)


""" (apply to all rows) sum of every 5 cells in each row must be in range [2,3] : no 11011 or 00100 is allowed """
model.add_constraints(model.sum(x[i, j]+x[i,j+1]+x[i,j+2]+x[i,j+3]+x[i,j+4]for j in R4) <=3 for i in R)
model.add_constraints(model.sum(x[i, j]+x[i,j+1]+x[i,j+2]+x[i,j+3]+x[i,j+4]for j in R4) >=2 for i in R)

""" (apply to all rows) sum of pair of consecutive 0s must be <=3 : in each row we are not allowed to have 
more than 3 pair of 00 """

for i in R:
    s=0
    for j in R5:
            if x[i, j]==x[i,j+1]==0:
                    s+=1
    model.add_constraint(s<= 3)

""" (apply to all rows) sum of pair of consecutive 1s must be <=3 : in each row we are not allowed to have 
more than 3 pair of 11 """
for i in R:
    s=0
    for j in R5:
            if x[i, j]==x[i,j+1]==1:
                    s+=1
    model.add_constraint(s<= 3)

solution = model.solve()
print(solution)
Sana.Nz
  • 81
  • 11

3 Answers3

1

I don't have time to solve the entire problem, but I can spot serious issues in the last two blocks (consecutive values constraints). First, the code as it is given raises a TypeError:

TypeError: Cannot use == to test expression equality, try using Python is operator or method equals: x_0_0 == x_0_1

This is for good reasons: in DOcplex, the '==' operator between variables is overloaded to build a constraint, that is, an object of the model. This object has no Python truth value and cannot be used in a Python if statement.

In addition, the variable s you are using is not a DOcplex variables, so it cannot be used to post a constraint.

To implement your constraint, here is a possible approach in Docplex: for each (i, j) cell, define a constraint, which is satisfied when (i,j) and (i,j+1) are both equal to zero and store all constraints of a line in a list:

for i in R
   twozs = [x[i,j] + x[i, j+1] == 0 for j in R5]

Note that these constraints are NOT added to the model, as we don't care which ones are satisfied or not, we just want that the sum of satisfied constraints (per line) is less than 3. AS constraints can be converted to binary variables seamlessly, you can use them as plain expressions, and we have to add that the sum of these constraints is less than 3: this means that at most three of them will be satisfied. The full code for this block is:

for i in R
   twozs = [x[i,j] + x[i, j+1] == 0 for j in R5]
   model.add(model.sum(twozs) <= 3)

You can easily figure out how to fix the second block for the "two cells equal 1" constraint in a similar manner.

However, the final model does not solve.

To investigate infeasible models, here are two general tricks in Docplex:

  1. give names to your constraints
  2. use a Relaxer object and try to relax the problem: the relaxer will relax some constraints are tell you which ones he had to relax to reach a feasible solution:
    solution = model.solve()
    if solution is None:
        from docplex.mp.relaxer import Relaxer
        rx = Relaxer()
        rs = rx.relax(model)
        rx.print_information()

Hope this helps.

Philippe Couronne
  • 826
  • 1
  • 5
  • 6
1

I found out what makes the model impossible: The constraint "no more than two consecutive 0s or 1s" was incorrect (and so is the constraint about 5 consecutive cells. You should not sum over the whole row, but pot one range per cell:

"""no more that 2 consecutive 0s or 1s : 110 or 001 or 101 or 010
this rule can not be applied to pre-defined cells. For example if we have 000 or 111 in pre-defined conditions,
we need to apply this rule for the rest of matrix not the pre-defined cells
"""
for i in R:
    for j in R3:
        s3ij = x[i, j]+x[i,j+1]+x[i,j+2]
        model.add_range(1, s3ij, 2, f"range3_{i}_{j}")

Similarly, the constraint on five consecutive cells can be written as:

for i in R:
for j in R4:
    s5ij = x[i, j]+x[i,j+1]+x[i,j+2] +x[i,j+3] +x[i,j+4]
    model.add_range(2, s5ij, 3, f"range5_{i}_{j}")

With these changes, the model becomes feasible.

Hope this helps.

Philippe.

Philippe Couronne
  • 826
  • 1
  • 5
  • 6
  • Thank you Philippe. The only issue left is that when I try to generate the solution pool some of the solutions have rows with more than 3 pairs of consecutive 0s or 1s – Sana.Nz Apr 28 '20 at 14:10
  • OK, sorry, I was too quick. My solution with ranges is incorrect: it looks at each "local" triplet and constrains it to be between 1 and 2, but you need a _global_ constraint on each row. – Philippe Couronne May 04 '20 at 08:02
  • Well, I don't understand where is the problem. The first constraint with range seems right to me. Can you be more specific please? – Sana.Nz May 04 '20 at 11:43
  • The range solution is local : it ensures each consecutive triplet contains 1 or 2 ones. What you need is a _global_ constraint that posts that the total number of consecutive equal cells in each row is less than 3. For a given cell, the constraint x[i,j] ==x[i,j+1] is true when the cell and its next are equal. Now, the sum of these constraints (actually, their truth values), summed over the whole row, must be less than 3. The trick here is a constraint can be used freely as an expression (actually its truth value) – Philippe Couronne May 04 '20 at 14:50
0

The corrected code for the "no more than 3 consecutive 1 0r 0s" constraint:

for i in r_rows:
    all_consecs = (x[i,j] == x[i,j+1] for j in range(n_cols-1))
    model.add(model.sum(all_consecs) <= 2, f"no_more_than_2_consecs_{i}")

The main point of interest here is how logical constraints can be used as expressions. A logical constraint can be true or false, and its truth value is actually stored in a hidden binary variable, which can be used freely in expressions (as in Model.sum() here)

Philippe Couronne
  • 826
  • 1
  • 5
  • 6
  • By replacing ``` for i in R: for j in R3: s3ij = x[i, j]+x[i,j+1]+x[i,j+2] model.add_range(1, s3ij, 2, f"range3_{i}_{j}") ``` with your latest script the solution is still infeasible. It returns rows like: ``` [0., 1., 0., 1., 0., 0., 1., 0., 1., 1., 0., 0., 0., 1., 1., 1., 0., 0., 1., 0., 1., 0., 1., 0., 1., 1., 0., 1., 0., 0., 1., 1., 0., 1., 0., 1., 0., 1.] ``` – Sana.Nz May 04 '20 at 18:47
  • The problem with older script was that there were more than 3 pairs of 11 or 00 and now with the new code the previouse problem still remains – Sana.Nz May 04 '20 at 18:50