Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions client/client.gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 8 additions & 1 deletion openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,8 @@ paths:
$ref: "#/components/schemas/OptimizationInput"
example:
batteries:
- s_min: 5000
- s_capacity: 52000
s_min: 5000
s_max: 50000
s_initial: 15000
s_goal: [0, 0, 40000, 0, 0, 0]
Expand Down Expand Up @@ -207,6 +208,12 @@ components:
- True: The battery can discharge to the grid at any time. The actual decision is
subject to the optimization.
- False: (default) The battery cannot be discharged while power is exported to the grid.
s_capacity:
type: number
minimum: 0
description: |
The capacity at 100% SOC in Wh. If not specified s_capacity will be set to s_max.
s_initial must be less or equal s_capacity, otherwise the optimization will return an error.
s_min:
type: number
minimum: 0
Expand Down
2 changes: 2 additions & 0 deletions src/optimizer/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ def handle_validation_error(error):
battery_config_model = api.model('BatteryConfig', {
'charge_from_grid': fields.Boolean(required=False, description='Controls whether the battery can be charged from the grid.'),
'discharge_to_grid': fields.Boolean(required=False, description='Controls whether the battery can discharge to grid.'),
's_capacity': fields.Float(required=False, description='Battery capacity at 100% state of charge (Wh)'),
Comment thread
andig marked this conversation as resolved.
Outdated
's_min': fields.Float(required=True, description='Minimum state of charge (Wh)'),
's_max': fields.Float(required=True, description='Maximum state of charge (Wh)'),
's_initial': fields.Float(required=True, description='Initial state of charge (Wh)'),
Expand Down Expand Up @@ -156,6 +157,7 @@ def post(self):
batteries.append(BatteryConfig(
charge_from_grid=bat_data.get('charge_from_grid', False),
discharge_to_grid=bat_data.get('discharge_to_grid', False),
s_capacity=bat_data.get('s_capacity', bat_data['s_max']),
s_min=bat_data['s_min'],
s_max=bat_data['s_max'],
s_initial=bat_data['s_initial'],
Expand Down
26 changes: 22 additions & 4 deletions src/optimizer/optimizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ class GridConfig:
class BatteryConfig:
charge_from_grid: bool
discharge_to_grid: bool
s_capacity: float
s_min: float
s_max: float
s_initial: float
Expand Down Expand Up @@ -83,6 +84,7 @@ def __init__(self, strategy: OptimizationStrategy, grid: GridConfig, batteries:
# scaling for penalty parameters. Make sure goal_penalty is always positive
self.prc_e_goal_pen = np.min([self.max_import_price, 0.1e-3]) * 10e1
self.prc_p_goal_pen = np.min([self.max_import_price, 0.1e-3]) * np.max(self.time_series.dt) / 3600 * 10e1
self.prc_soc_exc_pen = np.min([self.max_import_price, 0.1e-3]) * 10e2

# penalty for exceeding grid import limit. Result shall not become infeasible but report the violation
# with helpful information
Expand Down Expand Up @@ -136,7 +138,7 @@ def _setup_variables(self):
self.variables['s'] = {}
for i, bat in enumerate(self.batteries):
self.variables['s'][i] = [
pulp.LpVariable(f"s_{i}_{t}", lowBound=bat.s_min, upBound=bat.s_max)
pulp.LpVariable(f"s_{i}_{t}", lowBound=0, upBound=bat.s_capacity)
for t in self.time_steps
]

Expand All @@ -157,6 +159,10 @@ def _setup_variables(self):
for t in self.time_steps:
self.variables['p_demand_pen'][i][t] = pulp.LpVariable(f"p_demand_pen_{i}_{t}", lowBound=0)

# 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))]
self.variables['s_min_pen'] = [[pulp.LpVariable(f"s_min_pen_{i}_{t}", lowBound=0) for t in self.time_steps] for i in range(len(self.batteries))]

# Grid import/export variables [Wh]
self.variables['n'] = [pulp.LpVariable(f"n_{t}", lowBound=0) for t in self.time_steps]
self.variables['e'] = [pulp.LpVariable(f"e_{t}", lowBound=0) for t in self.time_steps]
Expand Down Expand Up @@ -248,6 +254,12 @@ def _setup_target_function(self):
if self.is_grid_demand_rate_active:
objective += - self.grid.prc_p_exc_imp * self.variables['p_max_imp_exc']

############################################################################
# Penalties for exceeding battery SOC limits at start
for i, bat in enumerate(self.batteries):
for t in self.time_steps:
objective += - self.prc_soc_exc_pen * (self.variables['s_max_pen'][i][t] + self.variables['s_min_pen'][i][t])

############################################################################
# Penalties for goals that cannot be met
for i, bat in enumerate(self.batteries):
Expand Down Expand Up @@ -275,7 +287,8 @@ def _setup_target_function(self):
# penalty for exceeding the grid export limit
if self.grid.p_max_exp is not None:
# negative target function contribution in a maximizing optimization
objective += - self.prc_e_grid_exp_pen * self.variables['e_exp_lim_exc'][t]
# decrease penalty slightly over time to push limit exceeding to late times
objective += - self.prc_e_grid_exp_pen * (1.0 - t * 1e-5) * self.variables['e_exp_lim_exc'][t]

#############################################################################
# Secondary strategies to implement preferences without impact to actual cost
Expand Down Expand Up @@ -311,8 +324,6 @@ def _add_energy_balance_constraints(self):
Add constraints related to the energy balance to the model.
"""

self.time_steps = range(self.T)

# Constraint (2): Power balance for each time step:
# - solar yield
# - household consumption
Expand Down Expand Up @@ -396,6 +407,13 @@ def _add_battery_constraints(self):
"""
Add constraints related to battery behavior to the model.
"""
# constraint for the max and min SOC. If the battery starts with an initial SOC
# greater than the maximum SOC or lesser than min SOC, maximum discharging is forced until the max.
# SOC is reached or max. charing will be forced until min SOC is reached.
for i, bat in enumerate(self.batteries):
for t in range(0, self.T):
self.problem += (self.variables['s_max_pen'][i][t] >= self.variables['s'][i][t] - bat.s_max)
self.problem += (self.variables['s_min_pen'][i][t] >= bat.s_min - self.variables['s'][i][t])

# Constraint (3): Battery dynamics
for i, bat in enumerate(self.batteries):
Expand Down
Loading
Loading