I want to constrain binary integer variables, x
, with indicator constraints to express a change in a binary integer variable over a specific iterator, t
.
I thought this could be nice since the different cases are obvious:
x[t-1] - x[t] = 0 - 0 = 0 # no change
x[t-1] - x[t] = 1 - 0 = 1 # shutting down
x[t-1] - x[t] = 0 - 1 = -1 # starting up
x[t-1] - x[t] = 1 - 1 = 0 # no change
However, it seems that there is something I don’t understand from the solver or the handling of the indicator constraints in JuMP. I wrote:
@constraint(m, x_up[n,t] => {x[n,t-1] - x[n,t] == -1})
And I was returned a zero matrix.
If I negated the indicator (NB: the constraints are applied for every unit, n
, in the system):
@constraint(m, !x_up[n,t] => {x[n,t-1] - x[n,t] == -1})
I was returned something that would express x
“going down” instead of “up”.
So, since I had something concrete other than a zero matrix, I was happy. I recalled that solvers like to work with tolerances and I then expressed my indicator constraints like:
@constraint(m, !x_up[n,t] => {x[n,t-1] - x[n,t] >= 0-atol})
@constraint(m, !x_down[n,t] => {x[n,t-1] - x[n,t] <= 0+atol})
And it works. I get what I expected but it does not work without the negation (It does not work either when taking the tolerances into account). In other words
# This returns a zero matrix:
@constraint(m, x_up[n,t] => {x[n,t-1] - x[n,t] <= 0+atol}) # up is 1 when -1
@constraint(m, x_down[n,t] => {x[n,t-1] - x[n,t] >= 0-atol}) # down is 1 when 1
# This returns the expected:
@constraint(m, !x_up[n,t] => {x[n,t-1] - x[n,t] >= 0-atol}) # up is 0 when ">=0"
@constraint(m, !x_down[n,t] => {x[n,t-1] - x[n,t] <= 0+atol}) # down is 0 when "<=0"
Question: What is the reason behind this and is this expected behavior? The logic becomes more cryptic.
My operating code can be found in my github
NB: Cbc forced exited constraining binary integers with indicator constraints. CPLEX has no problems. I have not tried Gurobi yet.
This is the terminal when using Cbc (using atom as IDE):
command line - Cbc_C_Interface -solve -quit (default strategy 1)
Continuous objective value is 6.11352e+07 - 0.01 seconds
Cgl0003I 0 fixed, 648 tightened bounds, 203 strengthened rows, 1613 substitutions
Cgl0003I 0 fixed, 0 tightened bounds, 81 strengthened rows, 1642 substitutions
Cgl0003I 0 fixed, 0 tightened bounds, 47 strengthened rows, 1615 substitutions
Cgl0003I 0 fixed, 0 tightened bounds, 22 strengthened rows, 1615 substitutions
Cgl0003I 0 fixed, 0 tightened bounds, 7 strengthened rows, 1615 substitutions
Cgl0003I 0 fixed, 0 tightened bounds, 1 strengthened rows, 1615 substitutions
Cgl0003I 0 fixed, 0 tightened bounds, 0 strengthened rows, 1615 substitutions
Cgl0003I 0 fixed, 0 tightened bounds, 0 strengthened rows, 1615 substitutions
Cgl0003I 0 fixed, 0 tightened bounds, 0 strengthened rows, 1481 substitutions
Cgl0004I processed model has 6461 rows, 6348 columns (3560 integer (3560 of which binary)) and 19298 elements
Cbc0045I Fixing only non-zero variables.
Cbc0045I MIPStart solution provided values for 710 of 3560 integer variables, 65 variables are still fractional.
Cbc0038I Full problem 6461 rows 6348 columns, reduced to 1440 rows 1368 columns
Cbc0038I Probing was tried 0 times and created 0 cuts of which 0 were active after adding rounds of cuts (0.000 seconds)
Cbc0045I Mini branch and bound defined values for remaining variables in 0.68 seconds.
Cbc0045I MIPStart provided solution with cost 6.11679e+07
Julia has exited.
Press Enter to start a new session.