I am somewhat familiar with Gurobi, but transitioning to Gekko since the latter appears to have some advantages. I am running into one issue though, which I will illustrate using my imaginary apple orchard. The 5-weeks harvest period (#horizon: T=5
) is upon us, and my - very meagre - produce will be:
[3.0, 7.0, 9.0, 5.0, 4.0]
Some apples I keep for myself [2.0, 4.0, 2.0, 4.0, 2.0]
, the remaining produce I will sell in the farmer's market at the following prices: [0.8, 0.9, 0.5, 1.2, 1.5]
. I have storage space with room for 6 apples, so I can plan ahead and sell apples at the most optimal moments, hence maximizing my revenue. I try to determine the optimal schedule with the following model:
m = GEKKO()
m.time = np.linspace(0,4,5)
orchard = m.Param([3.0, 7.0, 9.0, 5.0, 4.0])
demand = m.Param([2.0, 4.0, 2.0, 4.0, 2.0])
price = m.Param([0.8, 0.9, 0.5, 1.2, 1.5])
### manipulated variables
# selling on the market
sell = m.MV(lb=0)
sell.DCOST = 0
sell.STATUS = 1
# saving apples
storage_out = m.MV(value=0, lb=0)
storage_out.DCOST = 0
storage_out.STATUS = 1
storage_in = m.MV(lb=0)
storage_in.DCOST = 0
storage_in.STATUS = 1
### storage space
storage = m.Var(lb=0, ub=6)
### constraints
# storage change
m.Equation(storage.dt() == storage_in - storage_out)
# balance equation
m.Equation(sell + storage_in + demand == storage_out + orchard)
# Objective: argmax sum(sell[t]*price[t]) for t in [0,4]
m.Maximize(sell*price)
m.options.IMODE=6
m.options.NODES=3
m.options.SOLVER=3
m.options.MAX_ITER=1000
m.solve()
For some reason this is unfeasible (error code = 2). Interestingly, if set demand[0] to 3.0, instead of 2.0
(i.e. equal to orchard[0]
, the model does produce a succesful solution.
- Why is this the case?
- Even the "succesful" output values are bit weird: the storage space is not used a single time, and
storage_out
is not properly constrained in the last timestep. Clearly, I am not formulating the constraints correctly. What should I do to get realistic results, which are comparable to the gurobi output (see code below)?
output = {'sell' : list(sell.VALUE),
's_out' : list(storage_out.VALUE),
's_in' : list(storage_in.VALUE),
'storage' : list(storage.VALUE)}
df_gekko = pd.DataFrame(output)
df_gekko.head()
> sell s_out s_in storage
0 0.0 0.000000 0.000000 0.0
1 3.0 0.719311 0.719311 0.0
2 7.0 0.859239 0.859239 0.0
3 1.0 1.095572 1.095572 0.0
4 26.0 24.124924 0.124923 0.0
Gurobi model solved for with demand = [3.0, 4.0, 2.0, 4.0, 2.0]
. Note that gurobi also produces a solution with demand = [2.0, 4.0, 2.0, 4.0, 2.0]
. This only has a trivial impact on the outcome: n apples sold at t=0 becomes 1
.
T = 5
m = gp.Model()
### horizon (five weeks)
### supply, demand and price data
orchard = [3.0, 7.0, 9.0, 5.0, 4.0]
demand = [3.0, 4.0, 2.0, 4.0, 2.0]
price = [0.8, 0.9, 0.5, 1.2, 1.5]
### manipulated variables
# selling on the market
sell = m.addVars(T)
# saving apples
storage_out = m.addVars(T)
m.addConstr(storage_out[0] == 0)
storage_in = m.addVars(T)
# storage space
storage = m.addVars(T)
m.addConstrs((storage[t]<=6) for t in range(T))
m.addConstrs((storage[t]>=0) for t in range(T))
m.addConstr(storage[0] == 0)
# storage change
#m.addConstr(storage[0] == (0 - storage_out[0]*delta_t + storage_in[0]*delta_t))
m.addConstrs(storage[t] == (storage[t-1] - storage_out[t] + storage_in[t]) for t in range(1, T))
# balance equation
m.addConstrs(sell[t] + demand[t] + storage_in[t] == (storage_out[t] + orchard[t]) for t in range(T))
# Objective: argmax sum(a_sell[t]*a_price[t] - b_buy[t]*b_price[t])
obj = gp.quicksum((price[t]*sell[t]) for t in range(T))
m.setObjective(obj, gp.GRB.MAXIMIZE)
m.optimize()
output:
sell storage_out storage_in storage
0 0.0 0.0 0.0 0.0
1 3.0 0.0 0.0 0.0
2 1.0 0.0 6.0 6.0
3 1.0 0.0 0.0 6.0
4 8.0 6.0 0.0 0.0