diff --git a/mpcpy/exodata.py b/mpcpy/exodata.py index c478aaa..a2c0bf6 100755 --- a/mpcpy/exodata.py +++ b/mpcpy/exodata.py @@ -165,8 +165,10 @@ The price variable names should be chosen from the following list: - pi_e - electrical energy price -- pi_d - electrical demand price -- P_est - estimated peak power consumption +- pi_d - electrical demand price for multi-period +- P_est - estimated peak power consumption for multi-period +- pi_d_c - electrical demand price for coincedent. Must be constant for all time. +- P_est_c - estimated peak power consumption for coincedent. Must be constant for all time. Classes ======= diff --git a/mpcpy/optimization.py b/mpcpy/optimization.py index 3f1fbea..633d05a 100755 --- a/mpcpy/optimization.py +++ b/mpcpy/optimization.py @@ -66,11 +66,7 @@ class Optimization(utility._mpcpyPandas, utility._Measurements): ``exodata`` constraint object data attribute. demand_periods : int, optional, but required if problem_type includes demand. Maximum number of different demand periods expected to be represented in price data. - coincident : list or tuple of length twp, optional, only used if problem_type includes demand. - Information about coincedent demand. - [0] is price in $/W - [1] is estimated peak power in W. - + This should include coincident demand if needed. Attributes ---------- @@ -99,10 +95,6 @@ def __init__(self, Model, problem_type, package_type, objective_variable, **kwar raise TypeError('Demand period needs to be an integer value.') else: self.demand_periods = 0; - if 'coincident' in kwargs: - self.coincident = kwargs['coincident'] - else: - self.coincident = None self.objective_variable = objective_variable; self._create_slack_variables() self._problem_type = problem_type(); @@ -552,11 +544,9 @@ def _setup_jmodelica(self, JModelica, Optimization): # Add demand periods for period in range(Optimization.demand_periods): JModelica.extra_inputs['z_hat_{0}'.format(period)] = []; - if Optimization.coincident: - JModelica.extra_inputs['z_hat_c'] = []; # Write mop file JModelica._initalize_mop(Optimization); - JModelica._write_control_mop(Optimization, demand_periods=Optimization.demand_periods, coincident = Optimization.coincident); + JModelica._write_control_mop(Optimization, demand_periods=Optimization.demand_periods); JModelica._compile_transfer_problem(); class _ParameterEstimate(_Problem): @@ -655,11 +645,10 @@ def _energyplusdemandcostmin(self, Optimization, **kwargs): ''' - # Treat demand limiting - coincident = Optimization.coincident + # Get price data price_data = kwargs['price_data']; self.other_inputs['pi_e'] = price_data['pi_e']; - # Set demand charge + # Handle multiple demand periods ts_pi_d = price_data['pi_d'].get_base_data().loc[Optimization.start_time_utc:Optimization.final_time_utc]; ts_P_est = price_data['P_est'].get_base_data().loc[Optimization.start_time_utc:Optimization.final_time_utc]; # Detect when change and check @@ -683,10 +672,38 @@ def _energyplusdemandcostmin(self, Optimization, **kwargs): self.demand_df[period] = self.demand_df[period].mask(self.demand_df['pi_d']==val,P_est) # Create other_input variable for demand constraint ts = self.demand_df[period] - unit = price_data['pi_d'].get_base_unit(); + unit = price_data['P_est'].get_base_unit(); + var = variables.Timeseries('z_hat_{0}'.format(i), ts, unit); + self.other_inputs['z_hat_{0}'.format(i)] = var; + # Set price parameter in model + print('Setting pi_d_{0} as {1}'.format(i, val)) + self.opt_problem.set('pi_d_{0}'.format(i), val); + # Increment to next demand period + i = i + 1 + # Handle coincident demand period if exists + if 'pi_d_c' in price_data.keys(): + ts_pi_d_c = price_data['pi_d_c'].get_base_data().loc[Optimization.start_time_utc:Optimization.final_time_utc]; + ts_P_est_c = price_data['P_est_c'].get_base_data().loc[Optimization.start_time_utc:Optimization.final_time_utc]; + # Detect when change and check + uni_pi_d_c = ts_pi_d_c.unique() + uni_P_est_c = ts_P_est_c.unique() + if len(uni_pi_d_c) != 1: + raise ValueError('The coicident price data is not constant.'); + if len(uni_P_est_c) != 1: + raise ValueError('The coicident estimated peak power data is not constant.'); + val = uni_pi_d_c[0] + P_est = uni_P_est_c[0] + # Mark period + period = 'period_{0}'.format(i) + # Define all periods with demand limit + self.demand_df[period] = P_est + # Create other_input variable for demand constraint + ts = self.demand_df[period] + unit = price_data['P_est_c'].get_base_unit(); var = variables.Timeseries('z_hat_{0}'.format(i), ts, unit); self.other_inputs['z_hat_{0}'.format(i)] = var; # Set price parameter in model + print('Setting pi_d_{0} as {1}'.format(i, val)) self.opt_problem.set('pi_d_{0}'.format(i), val); # Increment to next demand period i = i + 1 @@ -702,22 +719,14 @@ def _energyplusdemandcostmin(self, Optimization, **kwargs): var = variables.Timeseries('z_hat_{0}'.format(i+j), ts, unit); self.other_inputs['z_hat_{0}'.format(i+j)] = var; # Set price parameter in model + print('Setting pi_d_{0} as 0'.format(i+j)) self.opt_problem.set('pi_d_{0}'.format(i+j), 0); - if coincident: - # Create demand limit for coincident demand - index_new = [Optimization.start_time_utc, - Optimization.final_time_utc]; - data_new = [coincident[1], coincident[1]]; - ts = pd.Series(index=index_new, data=data_new); - unit = units.W; - var = variables.Timeseries('z_hat_c'.format(i), ts, unit); - self.other_inputs['z_hat_c'.format(i)] = var; - self.opt_problem.set('pi_d_c'.format(i), coincident[0]); + print(self.demand_df) # Solve optimization problem self._simulate_initial(Optimization); self._solve(Optimization); self._get_control_results(Optimization, **kwargs); - + def _parameterestimate(self, Optimization, measurement_variable_list): '''Perform the parameter estimation. @@ -774,7 +783,7 @@ def _initalize_mop(self, Optimization): # Save the model path of the initialization and optimziation models self.mopmodelpath = self.Model.modelpath.split('.')[0] + '.' + self.Model.modelpath.split('.')[-1]; - def _write_control_mop(self, Optimization, demand_periods=None, coincident=None): + def _write_control_mop(self, Optimization, demand_periods=None): '''Complete the mop file for a control optimization problem. ''' @@ -787,8 +796,6 @@ def _write_control_mop(self, Optimization, demand_periods=None, coincident=None) self.mopfile.write(' optimization ' + self.Model.modelpath.split('.')[-1] + '_optimize (objective = (J(finalTime) + z_0*pi_d_0') for period in range(demand_periods-1): self.mopfile.write(' + z_{0}*pi_d_{0}'.format(period+1)); - if coincident: - self.mopfile.write(' + z_c*pi_d_c') self.mopfile.write('), startTime=start_time, finalTime=final_time)\n'); # Instantiate optimization model self.mopfile.write(' extends ' + self.Model.modelpath.split('.')[-1] + '_initialize;\n'); @@ -800,9 +807,6 @@ def _write_control_mop(self, Optimization, demand_periods=None, coincident=None) for period in range(demand_periods): self.mopfile.write(' parameter Real z_{0}(free=true, min=0)=1e8;\n'.format(period)); self.mopfile.write(' parameter Real pi_d_{0};\n'.format(period)); - if coincident: - self.mopfile.write(' parameter Real z_c(free=true, min=0)=1e8;\n'.format(period)); - self.mopfile.write(' parameter Real pi_d_c;\n'.format(period)); # Remove control variables from input_names for optimization self.opt_input_names = []; for key in self._init_input_names: @@ -846,8 +850,6 @@ def _write_control_mop(self, Optimization, demand_periods=None, coincident=None) if demand_periods: for period in range(demand_periods): self.mopfile.write(' mpc_model.' + Optimization.objective_variable + ' <= ' + 'z_{0} + z_hat_{0}'.format(period) + ';\n'); - if coincident: - self.mopfile.write(' mpc_model.' + Optimization.objective_variable + ' <= ' + 'z_c + z_hat_c'.format(period) + ';\n'); # End optimization portion of package.mop self.mopfile.write(' end ' + self.Model.modelpath.split('.')[-1] + '_optimize;\n'); # End package.mop and save diff --git a/unittests/resources/optimization/SimpleRC_Prices.csv b/unittests/resources/optimization/SimpleRC_Prices.csv index 4225442..325a2f6 100644 --- a/unittests/resources/optimization/SimpleRC_Prices.csv +++ b/unittests/resources/optimization/SimpleRC_Prices.csv @@ -1,21 +1,21 @@ -Time,energy[cents/kWh],energy,demand,peak_power -01/01/17 00:00:00,360000000,1,100000, -01/01/17 14:00:00,360000000,1,100000, -01/01/17 15:00:00,180000000000,500,100000, -01/01/17 18:00:00,180000000000,500,100000, -01/01/17 19:00:00,360000000,1,100000, -01/02/17 00:00:00,,2.41E-08,0.0054,1000 -01/02/17 01:00:00,,2.41E-08,0.0054,1000 -01/02/17 11:00:00,,2.41E-08,0.0054,1000 -01/02/17 11:59:00,,2.41E-08,0.0054,1000 -01/02/17 12:00:00,,3.23E-08,0.01965,2000 -01/02/17 14:59:00,,3.23E-08,0.01965,2000 -01/02/17 15:00:00,,4.46E-08,0.01965,2000 -01/02/17 15:59:00,,4.46E-08,0.01965,2000 -01/02/17 16:00:00,,4.46E-08,0.04,5000 -01/02/17 18:00:00,,4.46E-08,0.04,5000 -01/02/17 18:59:00,,4.46E-08,0.04,5000 -01/02/17 19:00:00,,4.46E-08,0.01965,2000 -01/02/17 20:00:00,,4.46E-08,0.01965,2000 -01/02/17 20:59:00,,4.46E-08,0.01965,2000 -01/02/17 21:00:00,,4.46E-08,0.0054,1000 +Time,energy[cents/kWh],energy,demand,peak_power,demand_coincident,peak_power_coincident +01/01/17 00:00:00,360000000,1,100000,,, +01/01/17 14:00:00,360000000,1,100000,,, +01/01/17 15:00:00,180000000000,500,100000,,, +01/01/17 18:00:00,180000000000,500,100000,,, +01/01/17 19:00:00,360000000,1,100000,,, +01/02/17 00:00:00,,2.41E-08,0.0054,1000,0.01774,3050 +01/02/17 01:00:00,,2.41E-08,0.0054,1000,0.01774,3050 +01/02/17 11:00:00,,2.41E-08,0.0054,1000,0.01774,3050 +01/02/17 11:59:00,,2.41E-08,0.0054,1000,0.01774,3050 +01/02/17 12:00:00,,3.23E-08,0.01965,2000,0.01774,3050 +01/02/17 14:59:00,,3.23E-08,0.01965,2000,0.01774,3050 +01/02/17 15:00:00,,4.46E-08,0.01965,2000,0.01774,3050 +01/02/17 15:59:00,,4.46E-08,0.01965,2000,0.01774,3050 +01/02/17 16:00:00,,4.46E-08,0.04,5000,0.01774,3050 +01/02/17 18:00:00,,4.46E-08,0.04,5000,0.01774,3050 +01/02/17 18:59:00,,4.46E-08,0.04,5000,0.01774,3050 +01/02/17 19:00:00,,4.46E-08,0.01965,2000,0.01774,3050 +01/02/17 20:00:00,,4.46E-08,0.01965,2000,0.01774,3050 +01/02/17 20:59:00,,4.46E-08,0.01965,2000,0.01774,3050 +01/02/17 21:00:00,,4.46E-08,0.0054,1000,0.01774,3050 diff --git a/unittests/test_optimization.py b/unittests/test_optimization.py index d244646..454a949 100755 --- a/unittests/test_optimization.py +++ b/unittests/test_optimization.py @@ -653,12 +653,14 @@ def test_energyplusdemandcostmin(self): optimization.JModelica, \ 'q_flow', \ constraint_data = self.constraints.data, - demand_periods=3, coincident=(0.01774,3050)); + demand_periods=4); # Gather prices price_csv_filepath = os.path.join(self.get_unittest_path(), 'resources', 'optimization', 'SimpleRC_Prices.csv'); price_variable_map = {'energy' : ('pi_e', units.dol_J), 'demand' : ('pi_d', units.dol_W), - 'peak_power' : ('P_est', units.W)}; + 'peak_power' : ('P_est', units.W), + 'demand_coincident' : ('pi_d_c', units.dol_W), + 'peak_power_coincident' : ('P_est_c', units.W)}; price = exodata.PriceFromCSV(price_csv_filepath, price_variable_map); price.collect_data(self.start_time, self.final_time); opt_problem.optimize(self.start_time, self.final_time, price_data = price.data) @@ -694,12 +696,14 @@ def test_energyplusdemandcostmin_excessdemandperiods(self): optimization.JModelica, \ 'q_flow', \ constraint_data = self.constraints.data, - demand_periods=5, coincident=(0.01774,3050)); + demand_periods=7); # Gather prices price_csv_filepath = os.path.join(self.get_unittest_path(), 'resources', 'optimization', 'SimpleRC_Prices.csv'); price_variable_map = {'energy' : ('pi_e', units.dol_J), 'demand' : ('pi_d', units.dol_W), - 'peak_power' : ('P_est', units.W)}; + 'peak_power' : ('P_est', units.W), + 'demand_coincident' : ('pi_d_c', units.dol_W), + 'peak_power_coincident' : ('P_est_c', units.W)}; price = exodata.PriceFromCSV(price_csv_filepath, price_variable_map); price.collect_data(self.start_time, self.final_time); opt_problem.optimize(self.start_time, self.final_time, price_data = price.data) @@ -735,12 +739,14 @@ def test_energyplusdemandcostmin_slack(self): optimization.JModelica, \ 'q_flow', \ constraint_data = self.constraints.data, - demand_periods=3, coincident=(0.01774,3050)); + demand_periods=4); # Gather prices price_csv_filepath = os.path.join(self.get_unittest_path(), 'resources', 'optimization', 'SimpleRC_Prices.csv'); price_variable_map = {'energy' : ('pi_e', units.dol_J), 'demand' : ('pi_d', units.dol_W), - 'peak_power' : ('P_est', units.W)}; + 'peak_power' : ('P_est', units.W), + 'demand_coincident' : ('pi_d_c', units.dol_W), + 'peak_power_coincident' : ('P_est_c', units.W)}; price = exodata.PriceFromCSV(price_csv_filepath, price_variable_map); price.collect_data(self.start_time, self.final_time); opt_problem.optimize(self.start_time, self.final_time, price_data = price.data)