gekko

How to optimize indices for list slices using gekko


If I have a list of strings like in python like so: my_list = ["x", "y", "x", "x", "x", "x", "y"] and I want to maximize the amount of "x" while also maximizing the amount of "y" outside the two-indices how do I do so using GEKKO?

Here's what I came up with:

from gekko import GEKKO
m = GEKKO(remote=False)

def count_x(lst):
    return sum([1 if item == 'x' else 0 for item in lst])

def count_y(lst):
    return len(lst) - count_x(lst)

my_list = ["x", "y", "x", "x", "x", "x", "y"]
def loss_function(x) -> int:
        x_0 = int(str(x[0].value))
        x_1 = int(str(x[1].value))
        return -1*count_y(my_list[:x_0]) - count_x(my_list[x_0:x_1]) - count_y(my_list[x_1:])

x = m.Array(m.Var, 2, lb=0, ub=len(my_list),integer=True)
m.Minimize(loss_function(x))
m.Equation(x[0] <= x[1])
m.solve()
print(x)

When I take out the x_0 and x_1 I get this error:

TypeError: slice indices must be integers or None or have an __index__ method

and when I leave x_0 and x_1 I get this output:

WARNING: objective equation 2 has no variables ss.Eqn(2) 0 = -4

What I expect to get is [[2.0] [6.0]] but what I instead get is [[0.0] [0.0]]

Thanks in advance for your help!


Solution

  • Gekko builds the model once and then passes it to a Mixed Integer Nonlinear Programming solver (APOPT). There are no call-backs to the Python functions. Here is a way to solve the problem using binary variables to indicate a section that Maximizes x and Minimizes y in the selection.

    from gekko import GEKKO
    import numpy as np
    
    m = GEKKO(remote=False)
    
    def opt_seg(z):
        nz = len(z)
        zb = [1 if i=='x' else 0 for i in z]
    
        b = m.Array(m.Var,nz,value=1,lb=0,ub=1,integer=True)
        [m.Maximize(zb[i]*b[i]) for i in range(nz)]
        [m.Minimize(2*(1-zb[i])*b[i]) for i in range(nz)]
    
        d = [m.Intermediate(b[i]-b[i-1]) for i in range(1,nz)]
        m.Equation(m.sum(d)==0)
    
        m.options.SOLVER = 1
        m.solve(disp=False)
    
        return (np.argmax(d)+1,np.argmin(d)+1)
        
    z = ["x", "y", "x", "x", "x", "x", "y"]
    print('-'*10)
    print(z)
    print(opt_seg(z))
    
    z = ["x", "y", "y", "x", "x", "y", "y"]
    print('-'*10)
    print(z)
    print(opt_seg(z))
    
    z = ["y", "x", "x", "x", "y", "x", "y", "y", "x", "x", "y"]
    print('-'*10)
    print(z)
    print(opt_seg(z))
    

    The solution is post-processed to get the range. Here is the output with a couple different test conditions:

    ----------
    ['x', 'y', 'x', 'x', 'x', 'x', 'y']
    (2, 6)
    ----------
    ['x', 'y', 'y', 'x', 'x', 'y', 'y']
    (3, 5)
    ----------
    ['y', 'x', 'x', 'x', 'y', 'x', 'y', 'y', 'x', 'x', 'y']
    (1, 4)