Skip to content

Commit

Permalink
Fix - Added implementation an tested new constraints
Browse files Browse the repository at this point in the history
  • Loading branch information
davidusb-geek committed May 26, 2023
1 parent fbc4219 commit 31e23a3
Show file tree
Hide file tree
Showing 6 changed files with 63 additions and 31 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# Changelog

## Unreleased
### Improvement
- Adding new constraints to limit the dynamics (kW/sec) of deferrable loads and battery power. The LP formulation works correctly and a work should be done on integrating the user input parameters to control this functionality.
- Added new constraint to avoid battery discharging to the grid
### Fix
- Bumped version of skforecast from 0.6.0 to 0.8.0. Doing this mainly implies changing how the exogenous data is passed to fit and predict methods.

## [0.4.10] - 2023-05-21
### Fix
- Fixed wrong name of new cost sensor.
Expand Down
4 changes: 4 additions & 0 deletions config_emhass.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ optim_conf:
- lp_solver: 'PULP_CBC_CMD' # set the name of the linear programming solver that will be used
- lp_solver_path: 'empty' # set the path to the LP solver
- set_nocharge_from_grid: False # avoid battery charging from the grid
- set_nodischarge_to_grid: True # avoid battery discharging to the grid
- set_battery_dynamic: False # add a constraint to limit the dynamic of the battery power in power per time unit
- battery_dynamic_max: 0.9 # maximum dynamic positive power variation in percentage of battery maximum power
- battery_dynamic_min: -0.9 # minimum dynamic negative power variation in percentage of battery maximum power

plant_conf:
- P_grid_max: 9000 # The maximum power that can be supplied by the utility grid in Watts
Expand Down
3 changes: 2 additions & 1 deletion src/emhass/command_line.py
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,8 @@ def dayahead_forecast_optim(input_data_dict: dict, logger: logging.Logger,
input_data_dict['df_input_data_dayahead'],
method=input_data_dict['fcst'].optim_conf['load_cost_forecast_method'])
df_input_data_dayahead = input_data_dict['fcst'].get_prod_price_forecast(
df_input_data_dayahead, method=input_data_dict['fcst'].optim_conf['prod_price_forecast_method'])
df_input_data_dayahead,
method=input_data_dict['fcst'].optim_conf['prod_price_forecast_method'])
opt_res_dayahead = input_data_dict['opt'].perform_dayahead_forecast_optim(
df_input_data_dayahead, input_data_dict['P_PV_forecast'], input_data_dict['P_load_forecast'])
# Save CSV file for publish_data
Expand Down
72 changes: 43 additions & 29 deletions src/emhass/optimization.py
Original file line number Diff line number Diff line change
Expand Up @@ -230,28 +230,6 @@ def perform_optimization(self, data_opt: pd.DataFrame, P_PV: np.array, P_load: n
sense = plp.LpConstraintEQ,
rhs = 0)
for i in set_I}

# Optional constraints to avoid charging the battery from the grid and to limit kW/s
if self.optim_conf['set_use_battery']:
if self.optim_conf['set_nocharge_from_grid']:
constraints.update({"constraint_nocharge_from_grid_{}".format(i) :
plp.LpConstraint(
e = P_sto_neg[i] + P_PV[i],
sense = plp.LpConstraintGE,
rhs = 0)
for i in set_I})
set_battery_dynamic = True
dyn_max = 1000
dyn_min = -1000
if set_battery_dynamic:
constraints.update({"constraint_batt_dynamic_max_{}".format(i) :
plp.LpConstraint(e = P_sto_pos[i+1] - P_sto_pos[i],
sense = plp.LpConstraintLE,
rhs = self.timeStep*dyn_max) for i in range(n-1)})
constraints.update({"constraint_batt_dynamic_min_{}".format(i) :
plp.LpConstraint(e = P_sto_pos[i+1] - P_sto_pos[i],
sense = plp.LpConstraintGE,
rhs = self.timeStep*dyn_min) for i in range(n-1)})

# Two special constraints just for a self-consumption cost function
if self.costfun == 'self-consumption':
Expand Down Expand Up @@ -283,17 +261,16 @@ def perform_optimization(self, data_opt: pd.DataFrame, P_PV: np.array, P_load: n
rhs = 0)
for i in set_I})

# Total time of deferrable load
# Treat deferrable loads constraints
for k in range(self.optim_conf['num_def_loads']):
# Total time of deferrable load
constraints.update({"constraint_defload{}_energy".format(k) :
plp.LpConstraint(
e = plp.lpSum(P_deferrable[k][i]*self.timeStep for i in set_I),
sense = plp.LpConstraintEQ,
rhs = def_total_hours[k]*self.optim_conf['P_deferrable_nom'][k])
})

# Treat deferrable load as a semi-continuous variable
for k in range(self.optim_conf['num_def_loads']):
# Treat deferrable load as a semi-continuous variable
if self.optim_conf['treat_def_as_semi_cont'][k]:
constraints.update({"constraint_pdef{}_semicont1_{}".format(k, i) :
plp.LpConstraint(
Expand All @@ -307,9 +284,7 @@ def perform_optimization(self, data_opt: pd.DataFrame, P_PV: np.array, P_load: n
sense=plp.LpConstraintLE,
rhs=0)
for i in set_I})

# Treat the number of starts for a deferrable load
for k in range(self.optim_conf['num_def_loads']):
# Treat the number of starts for a deferrable load
if self.optim_conf['set_def_constant'][k]:
constraints.update({"constraint_pdef{}_start1".format(k) :
plp.LpConstraint(
Expand Down Expand Up @@ -338,6 +313,45 @@ def perform_optimization(self, data_opt: pd.DataFrame, P_PV: np.array, P_load: n

# The battery constraints
if self.optim_conf['set_use_battery']:
# Optional constraints to avoid charging the battery from the grid
if self.optim_conf['set_nocharge_from_grid']:
constraints.update({"constraint_nocharge_from_grid_{}".format(i) :
plp.LpConstraint(
e = P_sto_neg[i] + P_PV[i],
sense = plp.LpConstraintGE,
rhs = 0)
for i in set_I})
# Optional constraints to avoid discharging the battery to the grid
if self.optim_conf['set_nodischarge_to_grid']:
constraints.update({"constraint_nodischarge_to_grid_{}".format(i) :
plp.LpConstraint(
e = P_grid_neg[i] + P_PV[i],
sense = plp.LpConstraintGE,
rhs = 0)
for i in set_I})
# Limitation of power dynamics in power per unit of time
if self.optim_conf['set_battery_dynamic']:
constraints.update({"constraint_pos_batt_dynamic_max_{}".format(i) :
plp.LpConstraint(e = P_sto_pos[i+1] - P_sto_pos[i],
sense = plp.LpConstraintLE,
rhs = self.timeStep*self.optim_conf['battery_dynamic_max']*self.plant_conf['Pd_max'])
for i in range(n-1)})
constraints.update({"constraint_pos_batt_dynamic_min_{}".format(i) :
plp.LpConstraint(e = P_sto_pos[i+1] - P_sto_pos[i],
sense = plp.LpConstraintGE,
rhs = self.timeStep*self.optim_conf['battery_dynamic_min']*self.plant_conf['Pd_max'])
for i in range(n-1)})
constraints.update({"constraint_neg_batt_dynamic_max_{}".format(i) :
plp.LpConstraint(e = P_sto_neg[i+1] - P_sto_neg[i],
sense = plp.LpConstraintLE,
rhs = self.timeStep*self.optim_conf['battery_dynamic_max']*self.plant_conf['Pc_max'])
for i in range(n-1)})
constraints.update({"constraint_neg_batt_dynamic_min_{}".format(i) :
plp.LpConstraint(e = P_sto_neg[i+1] - P_sto_neg[i],
sense = plp.LpConstraintGE,
rhs = self.timeStep*self.optim_conf['battery_dynamic_min']*self.plant_conf['Pc_max'])
for i in range(n-1)})
# Then the classic battery constraints
constraints.update({"constraint_pstopos_{}".format(i) :
plp.LpConstraint(
e=P_sto_pos[i] - self.plant_conf['eta_disch']*self.plant_conf['Pd_max']*E[i],
Expand Down
4 changes: 4 additions & 0 deletions src/emhass/web_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,10 @@ def build_params(params, options, addon):
params['optim_conf'][16]['lp_solver'] = options['lp_solver']
params['optim_conf'][17]['lp_solver_path'] = options['lp_solver_path']
params['optim_conf'][18]['set_nocharge_from_grid'] = options['set_nocharge_from_grid']
params['optim_conf'][19]['set_nodischarge_to_grid'] = options['set_nodischarge_to_grid']
params['optim_conf'][20]['set_battery_dynamic'] = options['set_battery_dynamic']
params['optim_conf'][21]['battery_dynamic_max'] = options['battery_dynamic_max']
params['optim_conf'][22]['battery_dynamic_min'] = options['battery_dynamic_min']
# Updating variables in plant_conf
params['plant_conf'][0]['P_grid_max'] = options['maximum_power_from_grid']
params['plant_conf'][1]['module_model'] = [i['pv_module_model'] for i in options['list_pv_module_model']]
Expand Down
4 changes: 3 additions & 1 deletion tests/test_optimization.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,9 +83,11 @@ def test_perform_dayahead_forecast_optim(self):
now_precise = datetime.now(self.input_data_dict['retrieve_hass_conf']['time_zone']).replace(second=0, microsecond=0)
idx_closest = self.opt_res_dayahead.index.get_indexer([now_precise], method='ffill')[0]
idx_closest = self.opt_res_dayahead.index.get_indexer([now_precise], method='nearest')[0]
# Test the battery
# Test the battery, dynamics and grid exchange contraints
self.optim_conf.update({'set_use_battery': True})
self.optim_conf.update({'set_nocharge_from_grid': True})
self.optim_conf.update({'set_battery_dynamic': True})
self.optim_conf.update({'set_nodischarge_to_grid': True})
self.opt = optimization(self.retrieve_hass_conf, self.optim_conf, self.plant_conf,
self.fcst.var_load_cost, self.fcst.var_prod_price,
self.costfun, root, logger)
Expand Down

0 comments on commit 31e23a3

Please sign in to comment.