pythonlinear-programminggekkomixed-integer-programming

How to use layered conditional constraints in Gekko


I'm trying to implement conditional logic in Gekko using "if3" but am unsure how to successfully layer 2 conditions at different levels of granularity.

"x1" is vector of binary values (0/1) that controls when an alternative rhs value should be used on element i to constrain x2 and x3.

"x2" is a vector of floats where I want to make the lower and upper bounds dynamic for element i based on the binary values in "x1" above. If the value for x1 for element i is = 1, I want to use "window_lnuc_min_promo_price" (vector of same length) as the lower bound and "window_lnuc_max_promo_price" as the upper bound. If the value for x1 for element i is = 0, I want to use "min_promo_price" as the lower bound and "max_promo_price" as the upper bound.

Similarly, "x3" is a vector of floats where I want to apply the same logic but just to the lower bound using values from "window_lnuc" when element i in x1 is = 1 and values from "lnuc" when it is = 0.

Lastly, I want to limit how many times X1 can be = 1 (4 in the example below). This would mean the alternative values are limited to 4 occurrences in total.

The problem I think I'm having is that because x1 is a variable with a 0-1 range, the optimizer is changing the default "0" values in "lnuc_weeks" (which I don't want it to do). I want the optimizer to basically keep anything that is 0 in "lnuc_weeks" as is and only select a maximum of 4 values from the elements in "lnuc_weeks" that are = 1 initially.

There is probably a better way to write this, but any help/feedback is appreciated. The full solution is a bit long to display for reproducibility of output, but hopefully the above/below sufficiently describe the problem.

x1 = m.Array(m.Var,(n), integer=True) #LNUC weeks

i = 0
for xi in x1:
    xi.value = lnuc_weeks[i]
    xi.lower = 0
    xi.upper = 1
    i += 1

x2 = m.Array(m.Var,(n)) #Blended SRP

i = 0
for xi in x2:
    xi.value = blended_srp[i]
    xi.lower = m.if3((x1[i]) - 1, min_promo_price[i], window_lnuc_min_promo_price[i])
    xi.upper = m.if3((x1[i]) - 1, max_promo_price[i], window_lnuc_max_promo_price[i])
    i += 1

x3 = m.Array(m.Var,(n)) #Blended NUC

i = 0
for xi in x3:
    xi.value = blended_nuc[i]
    xi.lower = m.if3((x1[i]) - 1, lnuc[i], window_lnuc[i])
    xi.upper = 10
    i += 1


#Limit max lnuc weeks
m.Equation(sum(x1)<=4)

Solution

  • The .lower and .upper bounds are defined when the model is initialized and do not change to reflect newly optimized values. To implement these, use an inequality expression. Use a switching point of 0.5 instead of 1 to avoid numerical issues with an integer value >1 or >=1. The solver tolerance is 1e-6 by default so a value of 0.999999 is considered the same as 1.000001 for convergence of equations.

    i = 0
    for xi in x2:
        xi.value = blended_srp[i]
        m.Equation(xi >= m.if3((x1[i]) - 0.5, min_promo_price[i], window_lnuc_min_promo_price[i]))
        m.Equation(xi <= m.if3((x1[i]) - 0.5, max_promo_price[i], window_lnuc_max_promo_price[i]))
        i += 1
    

    and

    i = 0
    for xi in x3:
        xi.value = blended_nuc[i]
        m.Equation(xi >= m.if3((x1[i]) - 0.5, lnuc[i], window_lnuc[i]))
        xi.upper = 10
        i += 1
    

    The select of any four elements with m.Equation(sum(x1)<=4) is correct.