• Gurobi Staff

To solve this problem using Gurobi in Python, you need to formulate it as a linear optimization problem. Here's a general outline of how you might structure your model:

1. Decision Variables:

• x[t]: Amount of electricity stored at hour t.
• buy[t]: Amount of electricity bought at hour t.
• sell[t]: Amount of electricity sold at hour t.
• delta_x[t]: Change in storage from hour t-1 to t.
2. Objective Function:

• Maximize the total profit over 5 hours. Profit at each hour is calculated as (sell price * amount sold) - (buy price * amount bought).
3. Constraints:

• Storage limits: 0 <= x[t] <= 8.
• Change in storage limits: -5 <= delta_x[t] <= 5.
• Storage update: x[t] = x[t-1] + delta_x[t] (for t > 0).
• Transaction constraints: delta_x[t] = buy[t] - sell[t].
• Non-negativity: buy[t] >= 0, sell[t] >= 0.

Here's a basic example of how you might write this in Python using Gurobi:

from gurobipy import Model, GRB

# Define the data
buy_prices = [2, 2, 5, 5, 2]
sell_prices = [1, 1, 3, 3, 1]
hours = range(5)

# Create a new model
m = Model("electricity_storage")

# Create variables
x = m.addVars(hours, vtype=GRB.CONTINUOUS, name="storage")
sell = m.addVars(hours, vtype=GRB.CONTINUOUS, name="sell")
delta_x = m.addVars(hours, vtype=GRB.CONTINUOUS, name="delta_x")

# Set the objective
m.setObjective(sum((sell_prices[t] * sell[t] - buy_prices[t] * buy[t]) for t in hours), GRB.MAXIMIZE)

m.addConstrs((0 <= x[t] <= 8 for t in hours), "StorageLimits")
m.addConstrs((-5 <= delta_x[t] <= 5 for t in hours), "DeltaLimits")
m.addConstrs((x[t] == (x[t-1] + delta_x[t]) for t in hours if t > 0), "StorageUpdate")
m.addConstrs((delta_x[t] == buy[t] - sell[t] for t in hours), "Transaction")

# Solve the model
m.optimize()

# Print the results
if m.status == GRB.OPTIMAL:
for t in hours:
print(f"Hour {t}: Δx = {delta_x[t].X}, Spend/Gain = {sell[t].X * sell_prices[t] - buy[t].X * buy_prices[t]}, Total Storage = {x[t].X}")

print("Total Gain over 5 hours:", m.objVal)

This code sets up and solves the problem as described. After running this model, you'll get the optimal delta_x at each moment, the spend or gain at each moment, and the total gain over 5 hours. Note that this is a simplified model and might need adjustments based on additional real-world considerations.

thank you for your help. But the code met one error in constraints 2.“ gurobipy.GurobiError: Constraint has no bool value (are you trying "lb <= expr <= ub"?) ”

• Gurobi Staff

The error in the Python code arises from the use of the m.addConstrs method with a generator expression that includes a double inequality. In Gurobi, you cannot directly use the syntax 0 <= x[t] <= 8 within m.addConstrs as it does not translate to a valid linear constraint format recognized by Gurobi.

To fix this, you should split the double inequality into two separate constraints for each variable t. Here's the corrected part of the code:

m.addConstrs((x[t] >= 0 for t in hours), "StorageLowerLimits")
m.addConstrs((x[t] <= 8 for t in hours), "StorageUpperLimits")
m.addConstrs((delta_x[t] >= -5 for t in hours), "DeltaLowerLimits")
m.addConstrs((delta_x[t] <= 5 for t in hours), "DeltaUpperLimits")

The code ran successfully, and the model was solved optimally by Gurobi. However, the optimal objective value is 0, which might be unexpected. This could be due to the specific data and constraints in the model, which might not allow for any profitable transactions under the given conditions.

Here are the results of the optimization:

results = [
{'Hour': 0, 'Delta_x': 0.0, 'Spend_Gain': 0.0, 'Total_Storage': 0.0},
{'Hour': 1, 'Delta_x': 0.0, 'Spend_Gain': 0.0, 'Total_Storage': 0.0},
{'Hour': 2, 'Delta_x': 0.0, 'Spend_Gain': 0.0, 'Total_Storage': 0.0},
{'Hour': 3, 'Delta_x': 0.0, 'Spend_Gain': 0.0, 'Total_Storage': 0.0},
{'Hour': 4, 'Delta_x': 0.0, 'Spend_Gain': 0.0, 'Total_Storage': 0.0}
]
total_gain = 0.

It seems that the model determined the best strategy was not to engage in any buying or selling transactions, as indicated by the zero values for Delta_x and Spend_Gain for each hour.

You may want to review the data or the constraints to ensure they reflect the scenario you're trying to model. If you have specific goals or conditions you're trying to achieve, adjusting the model accordingly might yield more interesting results.

Why would the result be 0? If I buy 5 at moment 0 and sell 5 at moment 2, then I can earn 3 * 5-2 * 5=5. Is it the result of a logical error in the code that caused the error

• Gurobi Staff

Hi 汉涛 田 ,

I think Gurobot has had a big week.  Gurobi 11.0.1 was just released and Gurobot was up late celebrating and has given some suboptimal advice.

Note that variables have default lower bounds of zero, unless explicitly changed.  And indeed if the delta[x].lb values are not changed from the default then the result is an optimal solution where all variables are zero.

Instead of trying to set variable bounds with constraints, it is better to do it at variable creation, with lb and ub properties.  So instead of this:

m.addConstrs((x[t] >= 0 for t in hours), "StorageLowerLimits")
m.addConstrs((x[t] <= 8 for t in hours), "StorageUpperLimits")
m.addConstrs((delta_x[t] >= -5 for t in hours), "DeltaLowerLimits")
m.addConstrs((delta_x[t] <= 5 for t in hours), "DeltaUpperLimits")

do this:

x = m.addVars(hours, ub=8, vtype=GRB.CONTINUOUS, name="storage") # default lb of 0
delta_x = m.addVars(hours, lb=-5, ub=5, vtype=GRB.CONTINUOUS, name="delta_x")

It's cleaner, and we don't end up with a default lower bound of 0 on the delta variables.

Running the code with these changes gives you the following output:

Optimal objective  2.900000000e+01
Hour 0: Δx = -5.0, Spend/Gain = 5.0, Total Storage = 8.0
Hour 1: Δx = 0.0, Spend/Gain = 0.0, Total Storage = 8.0
Hour 2: Δx = -5.0, Spend/Gain = 15.0, Total Storage = 3.0
Hour 3: Δx = -3.0, Spend/Gain = 9.0, Total Storage = 0.0
Hour 4: Δx = 0.0, Spend/Gain = 0.0, Total Storage = 0.0
Total Gain over 5 hours: 29.0

- Riley

Thank you for your reply, but I still feel some confusion.

"X=m. addVars (hours, ub=8, vtype=GRB. CONTANUOUS, name=" storage ")" The upper limit of storage has been set to 8 in the formula,

So how to explain "Hour 0: Δ In the result of "x=-5.0, Spend/Main=5.0, Total Storage=8.0", after the operation of -5, the storage is still 8?

• Gurobi Staff

Ah well spotted.  I was celebrating with Gurobot too.  Let's look at the following constraint:

m.addConstrs((x[t] == (x[t-1] + delta_x[t]) for t in hours if t > 0), "StorageUpdate")

What about when t = 0?  The above constraint won't work because we would have x[-1] but we do need something for t = 0.  We can either add the following:

m.addConstr(x[0] == delta_x[0], "StorageUpdate_0")

or we replace the first set of constraints above with:

m.addConstrs((x[t] == x.get(t-1,0) + delta_x[t] for t in hours), "StorageUpdate")

where we use dict.get to return a default value of 0 if the key t-1 doesn't exist, and don't worry about restricting t > 0.

- Riley

thank you for your help. But I still met a problem.

⬇ ⬇ ⬇
def gurobi(time, ESS):

buy_prices = [0.27, 0.27, 0.27, 0.27, 0.27, 0.27, 0.27, 0.49, 0.49, 0.49, 0.83, 0.83, 0.83, 0.49, 0.49, 0.49, 0.49,
0.49, 0.83, 0.83, 0.83, 0.49, 0.49, 0.27]
sell_prices = [0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.38, 0.38, 0.38, 0.65, 0.65, 0.65, 0.38, 0.38, 0.38, 0.38,
0.38, 0.65, 0.65, 0.65, 0.38, 0.38, 0.13]
hours = range(time, 24)

# Create a new model
m = Model("electricity_storage")

# Create variables
x = m.addVars(hours, ub=500, vtype=GRB.CONTINUOUS, name="storage") # 储能容量
sell = m.addVars(hours, vtype=GRB.CONTINUOUS, name="sell")
delta_x = m.addVars(hours, lb=-50, ub=50, vtype=GRB.CONTINUOUS, name="delta_x")

# Set the objective
m.setObjective(sum((sell_prices[t] * sell[t] - buy_prices[t] * buy[t]) for t in hours), GRB.MAXIMIZE)

m.addConstrs((delta_x[t] == buy[t] - sell[t] for t in hours), "Transaction")
⬇ ⬇ ⬇
m.addConstrs((x[t] == x.get(t - 1, ESS) + delta_x[t] for t in hours), "StorageUpdate")

# Solve the model
m.optimize()

# Print the results
if m.status == GRB.OPTIMAL:
print("Total Gain over 24 hours:", m.objVal)
objective_value = m.objVal
return objective_value
for i in range(rounds):
ESS = 0
v_c = 0
⬇ ⬇ ⬇
for j in range(24):
s = np.zeros(state_dim)
s[0] = j
s[1] = ESS
s[3] = sell_prices[j]
a=ddpg.choose_action(np.reshape(s, (1, state_dim)))
P_ESS_delta = a * 50

if ESS + P_ESS_delta < 0 or ESS + P_ESS_delta > 500:
v_c = 1
else:
v_c = 0

# 计算reward
if v_c == 0:
if j != 23:
⬇ ⬇ ⬇
r_n = gurobi(j + 1, ESS+ P_ESS_delta[0][0] )
else:
if ESS + P_ESS_delta == 0:
r_n = P_ESS_delta * sell_prices[j]
else:
r_n = 0
if v_c == 1:
r_n = 0

#更新状态
x_c[i][j][0] = ESS + P_ESS_delta
x_c[i][j][1] = P_ESS_delta
x_c[i][j][2] = r_n
ESS = + P_ESS_delta
s_=np.zeros(state_dim)

s_[0]=j + 1
s_[1]= ESS
if j == 23:
s_[3] = sell_prices[0]
else:
s_[2] = buy_prices[j + 1]
s_[3] = sell_prices[j + 1]

#存储
memory=ddpg.store_transition(s, a, r_n, s_)

if ddpg.pointer > MEMORY_CAPACITY:
var *= 0.99995
ddpg.learn()
s=s_

WARNING:tensorflow:Calling GradientTape.gradient on a persistent tape inside its context is significantly less efficient than calling it outside the context (it causes the gradient ops to be recorded on the tape, leading to increased CPU and memory usage). Only call GradientTape.gradient inside the context if you actually want to trace the gradient in order to compute higher order derivatives.
D:\Pycharm\EHGmaddpg\venv\lib\site-packages\keras\engine\training.py:2470: UserWarning: Model.state_updates will be removed in a future version. This property should not be used in TensorFlow 2.0, as updates are applied automatically.
warnings.warn('Model.state_updates will be removed in a future version. '
Using license file C:\Users\TianHanTao\gurobi.lic
Gurobi Optimizer version 9.1.2 build v9.1.2rc0 (win64)
Thread count: 10 physical cores, 16 logical processors, using up to 16 threads
Optimize a model with 46 rows, 92 columns and 137 nonzeros
Coefficient statistics:
Matrix range     [1e+00, 1e+00]
Objective range  [1e-01, 8e-01]
Bounds range     [5e+01, 5e+02]
RHS range        [3e+00, 3e+00]
Presolve removed 1 rows and 1 columns
Presolve time: 0.00s
Presolved: 45 rows, 91 columns, 135 nonzeros

Iteration    Objective       Primal Inf.    Dual Inf.      Time
0    6.8880000e+31   4.600000e+31   6.888000e+01      0s
63    1.1497383e+02   0.000000e+00   0.000000e+00      0s

Solved in 63 iterations and 0.00 seconds
Optimal objective  1.149738281e+02
Total Gain over 24 hours: 114.97382809638977
Traceback (most recent call last):
File "D:\Pycharm\EHGmaddpg\ECD-DDPG-6G.py", line 202, in <module>
r_n = gurobi(j + 1, ESS+ P_ESS_delta[0][0] )
File "D:\Pycharm\EHGmaddpg\ECD-DDPG-6G.py", line 42, in gurobi
m.addConstrs((x[t] == x.get(t - 1, ESS) + delta_x[t] for t in hours), "StorageUpdate")
File "src\gurobipy\model.pxi", line 3473, in gurobipy.Model.addConstrs
File "src\gurobipy\model.pxi", line 3359, in gurobipy.Model.addConstr
File "src\gurobipy\linexpr.pxi", line 471, in gurobipy.LinExpr.__sub__
File "src\gurobipy\linexpr.pxi", line 450, in gurobipy.LinExpr.__add__
File "src\gurobipy\linexpr.pxi", line 183, in gurobipy.LinExpr.add
gurobipy.GurobiError: Unsupported type (<class 'numpy.ndarray'>) for LinExpr addition argument

• Gurobi Staff

What is the value of ESS when you receive this error?  Use a try/except block to find out.  My guess from the error message is that it is a numpy array.

- Riley