Skip to content
42 changes: 27 additions & 15 deletions src/optimizer/optimizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,11 +159,14 @@ def _setup_variables(self):
self.variables['p_demand_pen'] = [[None for t in self.time_steps] for i in range(len(self.batteries))]
# binary variable to allow one out of two alternative constraints
self.variables['z_p_demand'] = [[None for t in self.time_steps] for i in range(len(self.batteries))]
# binary variable to capture when s_max is reached
self.variables['z_s_max_reached'] = [[None for t in self.time_steps] for i in range(len(self.batteries))]
for i, bat in enumerate(self.batteries):
if bat.p_demand is not None:
for t in self.time_steps:
self.variables['p_demand_pen'][i][t] = pulp.LpVariable(f"p_demand_pen_{i}_{t}", lowBound=0)
self.variables['z_p_demand'][i][t] = pulp.LpVariable(f"z_p_demand_{i}_{t}", cat='Binary')
self.variables['z_s_max_reached'][i][t] = pulp.LpVariable(f"z_s_max_reached_{i}_{t}", cat='Binary')

# penalty variable for staying above max SOC and below min SOC
self.variables['s_max_pen'] = [[pulp.LpVariable(f"s_max_pen_{i}_{t}", lowBound=0) for t in self.time_steps] for i in range(len(self.batteries))]
Expand Down Expand Up @@ -448,25 +451,34 @@ def _add_battery_constraints(self):
if bat.p_demand is not None:
for t in self.time_steps:
if bat.p_demand[t] > 0:
# clip required charge to max charging power if needed
# and leave some air to breathe for the optimizer
# clip requested charging power to max charging power if needed
p_demand = min(bat.c_max * self.time_series.dt[t] / 3600., bat.p_demand[t])
# two alternative constraints, only one is active:
# constraint option 1: charge energy tries to reach min charge energy parameter
self.problem += (self.variables['c'][i][t] + self.variables['p_demand_pen'][i][t]
+ self.M * self.variables['z_p_demand'][i][t] >= p_demand)
# constraint option 2: charge energy tries to reach energy to fill the battery to s_max

# introduce z_p_demand to become only 1 if s_max is almost reached and the
# charging with c_min would stop before the end of the time slot
self.problem += ((bat.s_max - self.variables['s'][i][t])
<= (1 - self.variables['z_p_demand'][i][t]) * self.M
+ bat.c_min * self.time_series.dt[t] / 3600.)

# introduce z_s_max_reached to become only 1 if s_max is fully reached
self.problem += ((bat.s_max - self.variables['s'][i][t])
<= (1 - self.variables['z_s_max_reached'][i][t]) * self.M)

# set a "soft" constraint to reach the requested charging rate if possible.
# deactivate it if s_max is already reached
self.problem += (self.variables['c'][i][t] + self.variables['p_demand_pen'][i][t]
+ self.M * (1 - self.variables['z_p_demand'][i][t])
- (self.batteries[i].s_max - self.variables['s'][i][t]) >= 0.)
elif bat.c_min > 0:
# in time steps without given charging demand, apply normal lower bound:
# Lower bound: either 0 or at least c_min
self.problem += (self.variables['c'][i][t] >= bat.c_min * self.time_series.dt[t] / 3600.
* self.variables['z_c'][i][t])
>= (1 - self.variables['z_s_max_reached'][i][t]) * p_demand)
else:
# if there is no p_demand set, make sure z_p_demand is 0 to keep the below c_min constraint effective
self.problem += (self.variables['z_p_demand'][i][t] <= 0)

if bat.c_min > 0:
# set constraints to exclude charging between 0 and c_min if z_p_demand is not 1.
self.problem += (self.variables['c'][i][t] + self.variables['z_p_demand'][i][t] * self.M
>= bat.c_min * self.time_series.dt[t] / 3600. * self.variables['z_c'][i][t])
self.problem += (self.variables['c'][i][t] <= self.M * self.variables['z_c'][i][t])

# Constraint (7): Minimum charge power limits if there is not charge demand
# Constraint (7): Minimum charge power limits if there is no charge demand
elif bat.c_min > 0:
for t in self.time_steps:
# Lower bound: either 0 or at least c_min
Expand Down
28 changes: 14 additions & 14 deletions test_cases/011-infeasible-charge-demand.json
Original file line number Diff line number Diff line change
Expand Up @@ -336,14 +336,14 @@
{
"charging_power": [
2700.3723,
2726.2801,
3775.7398,
4087.0091,
3515.8202,
2656.4201,
1606.9604,
1803.9868,
824.5558,
-7.48207e-12,
-2.4940233e-12,
0.0,
0.0,
0.0,
0.0,
0.0,
Expand Down Expand Up @@ -434,9 +434,9 @@
],
"state_of_charge": [
3847.3351,
6300.9872,
9979.2954,
13143.534,
7245.5009,
10923.809,
14088.047,
15534.312,
17157.9,
17900.0,
Expand Down Expand Up @@ -485,10 +485,10 @@
{
"charging_power": [
0.0,
1049.4597,
0.0,
0.0,
0.0,
1049.4597,
0.0,
0.0,
0.0,
Expand Down Expand Up @@ -544,8 +544,8 @@
552.01445,
689.2983,
326.2485,
7.1183464e-11,
-7.1183464e-11,
0.0,
0.0,
0.0,
0.0,
0.0,
Expand Down Expand Up @@ -583,9 +583,9 @@
],
"state_of_charge": [
1267.2,
2211.7137,
2211.7137,
2211.7137,
1267.2,
1267.2,
1267.2,
2211.7137,
2211.7137,
2211.7137,
Expand Down Expand Up @@ -732,10 +732,10 @@
],
"flow_direction": [
0,
1,
0,
0,
0,
1,
0,
0,
0,
Expand Down
Loading
Loading