From 34302a6407f88f2ba8f9adf09937e9fa7682d81a Mon Sep 17 00:00:00 2001 From: davidusb-geek Date: Wed, 10 Jul 2024 01:01:11 +0200 Subject: [PATCH] Fixed thermal model condition and added a unittest --- src/emhass/optimization.py | 73 +++++++++++++++++++------------------- tests/test_optimization.py | 49 ++++++++++++++++++------- 2 files changed, 74 insertions(+), 48 deletions(-) diff --git a/src/emhass/optimization.py b/src/emhass/optimization.py index 85b62d2d..3a646fb7 100644 --- a/src/emhass/optimization.py +++ b/src/emhass/optimization.py @@ -402,42 +402,43 @@ def create_matrix(input_list, n): rhs = 0) for i in set_I}) - elif "thermal_config" in self.optim_conf["def_load_config"][k]: - # Special case of a thermal deferrable load - def_load_config = self.optim_conf['def_load_config'][k] - if def_load_config and 'thermal_config' in def_load_config: - hc = def_load_config["thermal_config"] - start_temperature = hc["start_temperature"] - cooling_constant = hc["cooling_constant"] - heating_rate = hc["heating_rate"] - overshoot_temperature = hc["overshoot_temperature"] - outdoor_temperature_forecast = data_opt['outdoor_temperature_forecast'] - desired_temperatures = hc["desired_temperatures"] - sense = hc.get('sense', 'heat') - predicted_temp = [start_temperature] - for I in set_I: - if I == 0: - continue - predicted_temp.append( - predicted_temp[I-1] - + (P_deferrable[k][I-1] * (heating_rate * self.timeStep / self.optim_conf['P_deferrable_nom'][k])) - - (cooling_constant * (predicted_temp[I-1] - outdoor_temperature_forecast[I-1]))) - if len(desired_temperatures) > I and desired_temperatures[I]: - constraints.update({"constraint_defload{}_temperature_{}".format(k, I): - plp.LpConstraint( - e = predicted_temp[I], - sense = plp.LpConstraintGE if sense == 'heat' else plp.LpConstraintLE, - rhs = desired_temperatures[I], - ) - }) - constraints.update({"constraint_defload{}_overshoot_temp_{}".format(k, I): - plp.LpConstraint( - e = predicted_temp[I], - sense = plp.LpConstraintLE if sense == 'heat' else plp.LpConstraintGE, - rhs = overshoot_temperature, - ) - for I in set_I}) - predicted_temps[k] = predicted_temp + elif "def_load_config" in self.optim_conf.keys(): + if "thermal_config" in self.optim_conf["def_load_config"][k]: + # Special case of a thermal deferrable load + def_load_config = self.optim_conf['def_load_config'][k] + if def_load_config and 'thermal_config' in def_load_config: + hc = def_load_config["thermal_config"] + start_temperature = hc["start_temperature"] + cooling_constant = hc["cooling_constant"] + heating_rate = hc["heating_rate"] + overshoot_temperature = hc["overshoot_temperature"] + outdoor_temperature_forecast = data_opt['outdoor_temperature_forecast'] + desired_temperatures = hc["desired_temperatures"] + sense = hc.get('sense', 'heat') + predicted_temp = [start_temperature] + for I in set_I: + if I == 0: + continue + predicted_temp.append( + predicted_temp[I-1] + + (P_deferrable[k][I-1] * (heating_rate * self.timeStep / self.optim_conf['P_deferrable_nom'][k])) + - (cooling_constant * (predicted_temp[I-1] - outdoor_temperature_forecast[I-1]))) + if len(desired_temperatures) > I and desired_temperatures[I]: + constraints.update({"constraint_defload{}_temperature_{}".format(k, I): + plp.LpConstraint( + e = predicted_temp[I], + sense = plp.LpConstraintGE if sense == 'heat' else plp.LpConstraintLE, + rhs = desired_temperatures[I], + ) + }) + constraints.update({"constraint_defload{}_overshoot_temp_{}".format(k, I): + plp.LpConstraint( + e = predicted_temp[I], + sense = plp.LpConstraintLE if sense == 'heat' else plp.LpConstraintGE, + rhs = overshoot_temperature, + ) + for I in set_I}) + predicted_temps[k] = predicted_temp else: diff --git a/tests/test_optimization.py b/tests/test_optimization.py index 637439dd..a2a1bf7e 100644 --- a/tests/test_optimization.py +++ b/tests/test_optimization.py @@ -7,6 +7,7 @@ import numpy as np import pathlib import pickle +import random from datetime import datetime, timezone from emhass.retrieve_hass import RetrieveHass @@ -265,20 +266,44 @@ def test_perform_naive_mpc_optim(self): self.df_input_data_dayahead, self.P_PV_forecast, self.P_load_forecast, prediction_horizon, soc_init=soc_init, soc_final=soc_final, def_total_hours=def_total_hours, def_start_timestep=def_start_timestep, def_end_timestep=def_end_timestep) self.assertAlmostEqual(self.opt_res_dayahead.loc[self.opt_res_dayahead.index[-1],'SOC_opt'], soc_final) + + def test_thermal_load_optim(self): + self.df_input_data_dayahead = self.fcst.get_load_cost_forecast(self.df_input_data_dayahead) + self.df_input_data_dayahead = self.fcst.get_prod_price_forecast(self.df_input_data_dayahead) + self.df_input_data_dayahead['outdoor_temperature_forecast'] = [random.normalvariate(10.0, 3.0) for _ in range(48)] + runtimeparams = { + 'def_load_config': [ + {}, + {'thermal_config': { + 'heating_rate': 5.0, + 'cooling_constant': 0.1, + 'overshoot_temperature': 24.0, + 'start_temperature': 20, + 'desired_temperatures': [21]*48, + } + } + ] + } + self.optim_conf["def_load_config"] = runtimeparams['def_load_config'] + 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, emhass_conf, logger) + unit_load_cost = self.df_input_data_dayahead[self.opt.var_load_cost].values # €/kWh + unit_prod_price = self.df_input_data_dayahead[self.opt.var_prod_price].values # €/kWh + self.opt_res_dayahead = self.opt.perform_optimization(self.df_input_data_dayahead, + self.P_PV_forecast.values.ravel(), + self.P_load_forecast.values.ravel(), + unit_load_cost, unit_prod_price) + self.assertIsInstance(self.opt_res_dayahead, type(pd.DataFrame())) + self.assertIsInstance(self.opt_res_dayahead.index, pd.core.indexes.datetimes.DatetimeIndex) + self.assertIsInstance(self.opt_res_dayahead.index.dtype, pd.core.dtypes.dtypes.DatetimeTZDtype) + self.assertTrue('cost_fun_'+self.costfun in self.opt_res_dayahead.columns) + self.assertTrue(self.opt.optim_status == 'Optimal') - - def run_penalty_test_forecast(self): - 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, - emhass_conf, - logger, - ) + 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, emhass_conf, logger) def_total_hours = [5 * self.retrieve_hass_conf["freq"].seconds / 3600.0] def_start_timestep = [0] def_end_timestep = [0]