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!
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)