Skip to content

Commit

Permalink
Merge pull request #190 from lbl-srg/issue189_coincidentDemandFix
Browse files Browse the repository at this point in the history
Closes #189.
  • Loading branch information
dhblum authored Jul 19, 2019
2 parents 26e5dd9 + 83f54ff commit ed00247
Show file tree
Hide file tree
Showing 4 changed files with 74 additions and 64 deletions.
6 changes: 4 additions & 2 deletions mpcpy/exodata.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
=======
Expand Down
72 changes: 37 additions & 35 deletions mpcpy/optimization.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
----------
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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.
Expand Down Expand Up @@ -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.
'''
Expand All @@ -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');
Expand All @@ -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:
Expand Down Expand Up @@ -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
Expand Down
42 changes: 21 additions & 21 deletions unittests/resources/optimization/SimpleRC_Prices.csv
Original file line number Diff line number Diff line change
@@ -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
18 changes: 12 additions & 6 deletions unittests/test_optimization.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down

0 comments on commit ed00247

Please sign in to comment.