3

I've been trying to learn how to use Gekko and am currently trying to model optimal battery charge/discharge based in input/output prices (taking inspiration from here and here.

Below is my following code.

from gekko import Gekko
import numpy as np
m = Gekko()

electricity_price_in = np.random.uniform(low=0.1, high=1, size=50)
electrictiy_price_out = np.random.uniform(low=0.3, high=3, size=50)


E_battery = m.Var(lb=0, ub=366.2, value=0) #energy in battery at time t, battery size 366 MWh
Pc = m.Var(lb=0, ub=50) #charge power, 50 MW max
Pd = m.Var(lb=0, ub=36.6)  #discharge power, max 36 MW
E_price_in = m.Param(electricity_price_in)
E_price_out = m.Param(electrictiy_price_out)
m.time = np.linspace(0,49, 50)

Revenue =  m.Intermediate(Pd*E_price_out)
Cost = m.Intermediate(Pc*E_price_in)

m.Equation(E_battery.dt() == Pc-Pd)
m.Equation(E_battery >= 0)

m.Maximize(Revenue - Cost)
m.options.IMODE = 6
m.solve()

This appears to work ok. However, I get very different results if I include the following code:

isCharging = m.if3(-Pc,1,0)
isDischarging = m.if3(-Pd,1,0)

What I believe I am doing is creating two variables. As they are not constraint equations and are not included in the objective function, I would have expected the inclusion of this to make no changes to my results, however my code gives very different results (all variables turn into a list of zeroes).

Why would adding these two variables change my results?

Nicholas
  • 33
  • 4
  • 1
    Check out the [`if3` docs](https://gekko.readthedocs.io/en/latest/model_methods.html#logical-functions). It looks like convergence of `isCharging`, etc. are taken into account by the solver. I'd bet the odd solutions are coming from the fact that the "if" flips right when `Pc`/`Pd` are at their lower bounds (0), which can throw the rest of the objectives away from convergence. If gekko provides convergence stats, I'd recommend scrutinizing those for the superfluous variables case. – webelo Jul 27 '22 at 03:59

1 Answers1

1

The m.if3() function changes the default solver from IPOPT to APOPT so that it can solve the Mixed Integer Optimization problem. APOPT solver found a different (worse) local solution so you were seeing a difference.

Also, the random number generator needs a seed value to give the same results each time such as np.random.seed(1). The additional variables and equations associated with the m.if3() function can also require more iterations. Try using a warm-start for APOPT that is initialized with the IPOPT solution.

This script gives the same answer whether the m.if3() statements are present or not.

from gekko import Gekko
import numpy as np
m = Gekko()

np.random.seed(1)
electricity_price_in = np.random.uniform(low=0.1, high=1, size=50)
electrictiy_price_out = np.random.uniform(low=0.3, high=3, size=50)

E_battery = m.Var(lb=0, ub=366.2, value=0) #energy in battery at time t, battery size 366 MWh
Pc = m.Var(lb=0, ub=50) #charge power, 50 MW max
Pd = m.Var(lb=0, ub=36.6)  #discharge power, max 36 MW
E_price_in = m.Param(electricity_price_in)
E_price_out = m.Param(electrictiy_price_out)
m.time = np.linspace(0,49, 50)

Revenue =  m.Intermediate(Pd*E_price_out)
Cost = m.Intermediate(Pc*E_price_in)

m.Equation(E_battery.dt() == Pc-Pd)
m.Equation(E_battery >= 0)

m.Maximize(Revenue - Cost)

isCharging = m.if3(-Pc,1,0)
isDischarging = m.if3(-Pd,1,0)

m.options.IMODE = 6

m.options.SOLVER = 3
m.solve(disp=False)
print('Objective (IPOPT)', m.options.objfcnval)

m.options.TIME_SHIFT = 0
m.options.SOLVER = 1
m.solve()
print('Objective (APOPT)', m.options.objfcnval)
John Hedengren
  • 12,068
  • 1
  • 21
  • 25