This notebook executes several analyses to assess the feasibility of purchasing batteries for electricity storage. Annual electricity costs are reduced by opting into a time-of-use pricing plan and purchasing electricity during less expensive off-peak hours.
Created by Justin Elszasz.
# Full descriptions of these models can be found in the docstrings for each module.
#TOU_pricing.py contains functions that determine the season, period (peak, off-peak, intermediate),
#and cost of electricity for each hour of a timestampd dataframe containing hourly residential
#electricity consumption.
import TOU_pricing
#storage_logic.py contains functions that implement a control algorithm for the purchase of electricity
#based on available storage and period of the day (peak, off-peak).
import storage_logic
#calculations.py contains functions which return key metrics for analyzing the model output.
import calculations
import pandas as pd
import numpy as np
import time
/Users/Justin/Library/Enthought/Canopy_64bit/User/lib/python2.7/site-packages/pandas/io/excel.py:626: UserWarning: Installed openpyxl is not supported at this time. Use >=1.6.1 and <2.0.0. .format(openpyxl_compat.start_ver, openpyxl_compat.stop_ver))
# Reload modules during development
reload(TOU_pricing)
reload(storage_logic)
reload(calculations)
<module 'calculations' from 'calculations.pyc'>
This section analyzes the performance of the model based on the size of batteries in the system in kilowatt-hours.
Define efficiencies of inverter and battery.
def inverter_efficiency(direction):
if direction == 'charging':
eff = .85
elif direction == 'discharging':
eff = .85
return eff
def battery_efficiency(direction):
if direction == 'charging':
eff = .85
elif direction == 'discharging':
eff = .85
return eff
Create array of all the battery sizes to be analyzed.
battery_sizes = [1., 2., 5., 7.5, 10., 15., 20., 25., 30.]
max_dod = .2
Calculate the output and annual cost with normal (static) BGE electricity prices.
R_output = TOU_pricing.main('R',False)
R_annual_cost = np.sum(R_output['USAGE'] * R_output['cost'])
print R_annual_cost
729.88896351
This loop determines the model output for each battery size and the relevant metrics for each output. If save_results is True, a csv for each output (all hourly flows for one year) is saved for each battery size along with a csv containing the metrics for each battery size.
save_results = False
t0 = time.time()
for i, bat_size in enumerate(battery_sizes):
system_param = {
'Inverter Cost' : 1500., # ballpark
'Storage Cost' : 200. / 1.2, # cost per kilowatt-hour based on link above
'Storage Size' : bat_size, # storage size in kilowatt-hours
'Max Charge Rate' : bat_size / 8.,
'Max DOD' : max_dod, # DOD
'Bat Depleted' : max_dod * bat_size,
'Inverter Efficiency' : inverter_efficiency,
'Battery Efficiency' : battery_efficiency,
}
results = storage_logic.main(TOU_pricing.main('EV', False), system_param)
filename = 'results/storage_size/output_%s_kWh.csv'%bat_size
if save_results == True: results.to_csv(filename)
metrics = calculations.calc_metrics(results, system_param)
if i == 0: all_metrics = pd.DataFrame(index=battery_sizes, columns=metrics.keys())
all_metrics['Total kWh Purchased'][bat_size] = metrics['Total kWh Purchased']
all_metrics['% Purchased During Peak'][bat_size] = metrics['% Purchased During Peak']
all_metrics['% Purchased During Off-Peak'][bat_size] = metrics['% Purchased During Off-Peak']
all_metrics['Hours on Battery Only'][bat_size] = metrics['Hours on Battery Only']
all_metrics['% Peak Demand Battery'][bat_size] = metrics['% Peak Demand Battery']
all_metrics['Hours Battery Full'][bat_size] = metrics['Hours Battery Full']
all_metrics['Hours Battery Depleted'][bat_size] = metrics['Hours Battery Depleted']
all_metrics['Annual System Eff'][bat_size] = metrics['Annual System Eff']
all_metrics['Annual Var Cost'][bat_size] = metrics['Annual Var Cost']
all_metrics['Initial Cost'][bat_size] = metrics['Initial Cost']
all_metrics['Peak kWh Shaved'][bat_size] = metrics['Peak kWh Shaved']
all_metrics['% Peak kWh Shaved'][bat_size] = metrics['% Peak kWh Shaved']
all_metrics['PBP'][bat_size] = metrics['PBP']
all_metrics['Annual Savings'][bat_size] = metrics['Annual Savings']
all_metrics['% Annual Cost Savings'][bat_size] = metrics['% Annual Cost Savings']
if save_results == True: all_metrics.to_csv('results/storage_size/all_metrics_storage_size.csv')
t1 = time.time()
print 'Time for model run:',round(t1-t0,1),'s'
Time for model run: 151.5 s
# We now have a Pandas DataFrame containing metrics that describe the system performance
# indexed by battery size.
all_metrics.head()
PBP | Annual Var Cost | % Peak kWh Shaved | Total kWh Purchased | % Purchased During Off-Peak | Peak kWh Shaved | % Purchased During Peak | % Annual Cost Savings | % Peak Demand Battery | Annual Savings | Initial Cost | Hours Battery Full | Annual System Eff | Hours Battery Depleted | Hours on Battery Only | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
1.0 | 18.55998 | 640.09 | 0.1353311 | 8566.259 | 0.8183945 | 243.4825 | 0.1816055 | 0.123031 | 0.1957039 | 89.79896 | 1666.667 | 3914 | 0.9739731 | 2224 | 32 |
2.0 | 16.92688 | 621.58 | 0.2706621 | 8789.213 | 0.8507037 | 486.965 | 0.1492963 | 0.148391 | 0.367376 | 108.309 | 1833.333 | 3914 | 0.9492666 | 1799 | 457 |
5.0 | 14.52985 | 569.3 | 0.6551423 | 9423.424 | 0.9341582 | 1178.707 | 0.06584177 | 0.2200183 | 0.7265188 | 160.589 | 2333.333 | 1 | 0.8853795 | 795 | 1461 |
7.5 | 15.39935 | 551.31 | 0.8048847 | 9673.588 | 0.9637111 | 1448.118 | 0.03628891 | 0.2446659 | 0.8335197 | 178.579 | 2750 | 2 | 0.8624831 | 382 | 1874 |
10.0 | 17.0978 | 544.68 | 0.8771617 | 9792.536 | 0.9774312 | 1578.156 | 0.02256882 | 0.2537495 | 0.8975529 | 185.209 | 3166.667 | 1 | 0.8520066 | 248 | 2008 |
Plot the simple payback period as a function of battery size.
save_fig = True
a = 6.
fig = plt.figure(figsize=[1.3*a,a])
ax = subplot(111)
# turn off square border around plot
ax.spines["top"].set_visible(False)
ax.spines["bottom"].set_visible(False)
ax.spines["right"].set_visible(False)
ax.spines["left"].set_visible(False)
# ensure only ticks on bottom and left, not top and right (unnecessary)
#ax.get_xaxis().tick_bottom()
#ax.get_yaxis().tick_left()
all_metrics['PBP'].plot(grid='off',color='#FF3300',linewidth=2,marker='.',markersize=14)
title('Payback Period in Years',fontsize=16)
xlabel('Storage Size (kWh)',fontsize=14)
xticks(fontsize=14)
yticks(fontsize=14)
# turn off tick marks
plt.tick_params(axis="both", which="both", bottom="off", top="off",
labelbottom="on", left="off", right="off", labelleft="on")
ylabel('Years',fontsize=14)
if save_fig == True: fig.savefig('results/storage_size/PBP.png',bbox_inches='tight')
Plot the cost savings ($ and %) compared to a normal BGE plan without time-of-use pricing.
save_fig = True
a = 6.
fig = plt.figure(figsize=[1.3*a,a])
ax1 = plt.subplot(111)
# Ticks
ticks_left = np.array(range(10,30,2))/100.
ticks_right_ymin = min(ticks_left) * R_annual_cost
ticks_right_ymax = max(ticks_left) * R_annual_cost
ticks_right = np.array(range(80,200,20))
# turn off square border around plot
ax1.spines["top"].set_visible(False)
ax1.spines["bottom"].set_visible(False)
ax1.spines["right"].set_visible(False)
ax1.spines["left"].set_visible(False)
title('Annual Electricity Bill Cost Savings',fontsize=16)
xlabel('Storage Size (kWh)',fontsize=14)
xticks(fontsize=14)
ax1.plot(all_metrics.index, all_metrics['% Annual Cost Savings'],color='#FF3300',linewidth=2,marker='.',markersize=14)
ax1.set_ylabel('% Annual Savings', fontsize=14)
ax2 = ax1.twinx()
ax2.set_ylim(min(all_metrics['Annual Savings']), max(all_metrics['Annual Savings']))
ax2.set_yticklabels(ticks_right, fontsize=14)
ax2.set_ylabel('Annual Savings (USD)', fontsize=14)
# turn off ticks
ax1.tick_params(axis="both", which="both", bottom="off", top="off",
labelbottom="on", left="off", right="off", labelleft="on",labelsize=14)
ax2.tick_params(axis="y", which="both", bottom="off", top="off",
labelbottom="on", left="off", right="off", labelleft="off",labelsize=14)
if save_fig == True: fig.savefig('results/storage_size/cost_savings.png',bbox_inches='tight')
Number/percent of kilowatt-hours no longer purchased during peak hours compared to normal BGE cost plan and no storage.
save_fig = True
a = 6.
fig = plt.figure(figsize=[1.3*a,a])
ax1 = plt.subplot(111)
# Ticks
ticks_right = np.array(range(0,2000,200))
# turn off square border around plot
ax1.spines["top"].set_visible(False)
ax1.spines["bottom"].set_visible(False)
ax1.spines["right"].set_visible(False)
ax1.spines["left"].set_visible(False)
title('Demand Shaved From Peak Hours',fontsize=16)
xlabel('Storage Size (kWh)',fontsize=14)
xticks(fontsize=14)
ax1.plot(all_metrics.index, all_metrics['% Peak kWh Shaved'],color='#FF3300',linewidth=2,marker='.',markersize=14)
ax1.set_ylabel('% Peak Demand Shaved', fontsize=14)
ax1.set_ylim(0,1.1)
ax2 = ax1.twinx()
ax2.set_yticks(ticks_right)
ax2.set_yticklabels(ticks_right, fontsize=14)
ax2.set_ylim(0,1.1*max(all_metrics['Peak kWh Shaved']))
ax2.set_ylabel('Peak Demand Shaved (kWh)', fontsize=14)
# turn off ticks
ax1.tick_params(axis="both", which="both", bottom="off", top="off",
labelbottom="on", left="off", right="off", labelleft="on",labelsize=14)
ax2.tick_params(axis="y", which="both", bottom="off", top="off",
labelbottom="on", left="off", right="off", labelleft="off",labelsize=14)
if save_fig == True: fig.savefig('results/storage_size/peak_shaved_stor_cap.png',bbox_inches='tight')
Plot of total annual kilowatt-hours purchased and the resulting overall system efficiency.
save_fig = True
a = 6.
fig = plt.figure(figsize=[1.3*a,a])
ax1 = plt.subplot(111)
# Ticks
ticks_right = np.array(range(0,2000,200))
# turn off square border around plot
ax1.spines["top"].set_visible(False)
ax1.spines["bottom"].set_visible(False)
ax1.spines["right"].set_visible(False)
ax1.spines["left"].set_visible(False)
title('Annual System Efficiency',fontsize=16)
xlabel('Storage Size (kWh)',fontsize=14)
xticks(fontsize=14)
ax1.plot(all_metrics.index, all_metrics['Total kWh Purchased'],color='#FF3300',linewidth=2,marker='.',markersize=14)
ax1.set_ylabel('Total kWh Purchased', fontsize=14, color='#FF3300')
#ax1.set_ylim(0,1.1)
ax2 = ax1.twinx()
ax2.plot(all_metrics.index, all_metrics['Annual System Eff'],color='#00CCFF',linewidth=2,marker='.',markersize=14)
#ax2.set_yticks([0,.25,.5,1])
#ax2.set_yticklabels(, fontsize=14)
ax2.set_ylim(0,1)
ax2.set_ylabel('System Efficiency', fontsize=14, color='#00CCFF')
# turn off ticks
ax1.tick_params(axis="both", which="both", bottom="off", top="off",
labelbottom="on", left="off", right="off", labelleft="on",labelsize=14)
ax2.tick_params(axis="y", which="both", bottom="off", top="off",
labelbottom="on", left="off", right="off", labelleft="off",labelsize=14)
if save_fig == True: fig.savefig('results/storage_size/sys_eff_stor_cap.png',bbox_inches='tight')