0

I am trying to recreate an integer linear optimization problem using cvxpy that I have outlined in Excel - Excel screenshot. Note that this is a dummy example, the actual dataset will have thousands of variables. Please ignore the solution in cell K5 of the spreadsheet, as Excel Solver isn't able to provide integer solutions.

Consider that the 9 variables are split into 3 buckets. Note my goal with constraints 1-3 is that either there are at least 2 out of 3 1's for a bucket of variables, or all of the values are 0. For example, a,b,c should be either 1,1,1 or 1, 1, 0 or 1,0,1 or 0, 1, 1, or 0, 0, 0.

import numpy as np
import cvxpy as cp
import cvxopt 

coefs= np.array([0.7, 0.95, 0.3, 2, 1.05, 2.2, 4, 1, 3])

dec_vars = cp.Variable(len(coefs), boolean = True)

constr1 = np.array([1,1,1,0,0,0,0,0,0]) @ dec_vars == 2 * max(dec_vars[0:3]) 
constr2 = np.array([0,0,0,1,1,1,0,0,0]) @ dec_vars == 2 * max(dec_vars[3:6])
constr3 = np.array([0,0,0,0,0,0,1,1,1]) @ dec_vars == 2 * max(dec_vars[6:9])
constr4 = np.ones(len(coefs)) @ dec_vars >= 2

When I run up to here, I get a NotImplementedError: Strict inequalities are not allowed. error

matsuo_basho
  • 2,833
  • 8
  • 26
  • 47

1 Answers1

1

The core issue is your usage of python's max which is tried to be evaluated before reaching cvxpy. You cannot use just any python-native function on cvxpy-objects. max(cvx_vars) is not supported as is abs(cvx_vars) and much more.

There is max-function in cvxpy, namely: cp.max(...), but i don't get what you are trying to do or how you would achieve this by exploiting max. See below...

Note my goal with constraints 1-3 is that either there are at least 2 out of 3 1's for a bucket of variables, or all of the values are 0. For example, a,b,c should be either 1,1,1 or 1, 1, 0 or 1,0,1 or 0, 1, 1, or 0, 0, 0.

This, in general, needs some kind of disjunctive reasoning.

Approach A

The general approach would be using a binary indicator variable together with a big-M based expression:

is_zero = binary aux-var

sum(dec_vars) <= 3 * is_zero
sum(dec_vars) >= 2 * is_zero

Approach B

Alternatively, one could also model this by (without aux-vars):

a -> b || c
b -> a || c
c -> a || b

meaning: if there is a non-zero, there is at least one more non-zero needed. This would look like:

(1-a) + b + c >= 1
(1-b) + a + c >= 1
(1-c) + a + b >= 1
sascha
  • 32,238
  • 6
  • 68
  • 110
  • Approach B works. For approach A, can you elaborate on the syntax `binary aux-var`. – matsuo_basho Dec 23 '20 at 15:29
  • 1
    `is_zero` is just a new variable like `aux_var = cp.Variable(1, boolean = True)` (probably one for each of your buckets though). This binary-variable *decides on: all-zero or the other case* (the constraints then impose 0's everywhere or limits on the sum) – sascha Dec 23 '20 at 15:37
  • This is mainly because Approach A seems to be much more elegant. Consider that I actually have thousands of variables, and Approach B requires me to write a rule for every single one, as opposed to a general rule for the bucket – matsuo_basho Dec 23 '20 at 15:38
  • Yes, these approaches differ in their use-cases. – sascha Dec 23 '20 at 15:38
  • What I'm not clear on is we now have a separate variable for each variable, but also a variable for whether the bucket is 0 or not. This seems redundant and more complex than necessary. I guess approach B doesn't have this issue, but the issue with B is having to program a separate constraint for each of the 15K variables – matsuo_basho Dec 23 '20 at 15:54
  • 1
    You have *one auxiliary / helper / seperate* variable *for each disjunctive reasoning you need* (not for each variable!). If you have `n` elements / buckets / whatever (consisting of `m` binary-variables inside) where you need to enforce `0 or bounds on the sum of m_of_n`, you will need `n` *aux-vars*. Both approaches are just linearizations of some disjunctive space which is needed no matter what. One uses additional variables, the other not, but scales worse in terms of constraints for some cases. – sascha Dec 23 '20 at 15:59