Skip to content

Commit

Permalink
derUtilityCost: Removed existing BESS and Thermal Batt Energy Plot
Browse files Browse the repository at this point in the history
astronobri committed Nov 18, 2024
1 parent 109ba83 commit 5f352b5
Showing 2 changed files with 5 additions and 177 deletions.
13 changes: 0 additions & 13 deletions omf/models/derUtilityCost.html
Original file line number Diff line number Diff line change
@@ -8,8 +8,6 @@
$(window).on('pageshow',function(){
Plotly.newPlot('derOverviewData', JSON.parse(allOutputData['derOverviewData']),JSON.parse(allOutputData['derOverviewLayout']));
Plotly.newPlot('batteryChargePlotly', JSON.parse(allOutputData['batteryChargeData']), JSON.parse(allOutputData['batteryChargeLayout']));
//Plotly.newPlot('thermalDevicePlotData', JSON.parse(allOutputData['thermalDevicePlotData']), JSON.parse(allOutputData['thermalDevicePlotLayout']) || {});
Plotly.newPlot('thermalBatEnergyPlot', JSON.parse(allOutputData['thermalBatEnergyPlot']), JSON.parse(allOutputData['thermalBatEnergyPlotLayout']) || {});
Plotly.newPlot('thermalBatPowerPlot', JSON.parse(allOutputData['thermalBatPowerPlot']), JSON.parse(allOutputData['thermalBatPowerPlotLayout']) || {});
Plotly.newPlot('resiliencePlotly', JSON.parse(allOutputData['resilienceData']), JSON.parse(allOutputData['resilienceLayout']) || {});
Plotly.newPlot('resilienceProbPlotly', JSON.parse(allOutputData['resilienceProbData']), JSON.parse(allOutputData['resilienceProbLayout']) || {});
@@ -120,14 +118,6 @@
<p class="inputSectionHeader">Chemical Energy Storage Device Inputs</p>
</div>
<hr style="border-style: solid; border-color: #196b12; margin-top: 10px;">
<div class="shortInput">
<label class="tooltip">Existing Utility-Installed Battery Power Capacity (kW)<span class="classic">Specify the amount of existing battery power capacity (kW) of the utility.</span></label>
<input type="text" id="existing_kw" name="existing_kw" value="{{allInputDataDict.existing_kw}}" required="required"/>
</div>
<div class="shortInput">
<label class="tooltip">Existing Utility-Installed Battery Energy Capacity (kWh)<span class="classic">Specify the amount of existing battery energy capacity (kWh) of the utility.</span></label>
<input type="text" id="existing_kwh" name="existing_kwh" value="{{allInputDataDict.existing_kwh}}" required="required"/>
</div>
<div class="shortInput">
<label class="tooltip">Number of Home Chemical Batteries<span class="classic">Total number of residential chemical batteries to model.</span></label>
<input type="text" id="numberBESS" name="numberBESS" value="{{allInputDataDict.numberBESS}}" required="required">
@@ -265,9 +255,6 @@
<p class="reportTitle" style="page-break-before:always">DER Serving Load Overview</p>
<div id="derOverviewData" class="tightContent" style="width: 1000px; height: 600px;"></div>

<p class="reportTitle" style="page-break-before:always">Thermal Battery Energy Profile</p>
<div id="thermalBatEnergyPlot" class="tightContent" style="width: 1000px; height: 600px;"></div>

<p class="reportTitle" style="page-break-before:always">Thermal Battery Power Profile</p>
<div id="thermalBatPowerPlot" class="tightContent" style="width: 1000px; height: 600px;"></div>

169 changes: 5 additions & 164 deletions omf/models/derUtilityCost.py
Original file line number Diff line number Diff line change
@@ -72,10 +72,10 @@ def work(modelDir, inputDict):

scenario['ElectricStorage'] = {
##TODO: Add options here, if needed
'min_kw': float(inputDict['BESS_kw'])+float(inputDict['existing_kw']),
'max_kw': float(inputDict['BESS_kw'])+float(inputDict['existing_kw']),
'min_kwh': float(inputDict['BESS_kwh'])+float(inputDict['existing_kwh']),
'max_kwh': float(inputDict['BESS_kwh'])+float(inputDict['existing_kwh']),
'min_kw': float(inputDict['BESS_kw']),
'max_kw': float(inputDict['BESS_kw']),
'min_kwh': float(inputDict['BESS_kwh']),
'max_kwh': float(inputDict['BESS_kwh']),
'can_grid_charge': can_grid_charge_bool,
'total_rebate_per_kw': 0,
'macrs_option_years': 0,
@@ -142,24 +142,10 @@ def work(modelDir, inputDict):
outData['cumulativeCashflow'] = reoptResults['Financial']['offtaker_annual_free_cashflows'] #list(accumulate(reoptResults['Financial']['offtaker_annual_free_cashflows']))
outData['netCashflow'] = reoptResults['Financial']['offtaker_discounted_annual_free_cashflows'] #list(accumulate(reoptResults['Financial']['offtaker_annual_free_cashflows'])) ## or alternatively: offtaker_annual_free_cashflows

## Temporarily correct financial outputs for any existing BESS
## (Remove the installation cost of any existing BESS since we are including it in the aggregated BESS: existing utility BESS + proposed consumer BESS)
existing_utility_bess_kw = float(inputDict['existing_kw'])
existing_utility_bess_kwh = float(inputDict['existing_kwh'])
installed_cost_per_kw = float(inputDict['installed_cost_per_kw'])
installed_cost_per_kwh = float(inputDict['installed_cost_per_kwh'])
cost_existing_utility_bess_kw = existing_utility_bess_kw * installed_cost_per_kw
cost_existing_utility_bess_kwh = existing_utility_bess_kwh * installed_cost_per_kwh
totalcost_existing_utility_bess = cost_existing_utility_bess_kw + cost_existing_utility_bess_kwh
outData['NPV'] -= totalcost_existing_utility_bess
outData['cumulativeCashflow'] = [cashflow - totalcost_existing_utility_bess for cashflow in reoptResults['Financial']['offtaker_annual_free_cashflows']]
outData['netCashflow'] = [cashflow - totalcost_existing_utility_bess for cashflow in reoptResults['Financial']['offtaker_annual_free_cashflows']]

## Calculate adjusted initial investment and simply payback period (SPP)
adjusted_initial_investment = reoptResults['Financial']['initial_capital_costs'] - totalcost_existing_utility_bess
adjusted_initial_investment = reoptResults['Financial']['initial_capital_costs']
outData['SPP'] = adjusted_initial_investment / np.sum(outData['netCashflow'])


## NOTE: temporarily comment out the two derConsumer runs to run the code quicker
"""
## Scale down the utility demand to create an ad-hoc small consumer load (1 kW average) and large consumer load (10 kW average)
@@ -396,139 +382,6 @@ def work(modelDir, inputDict):
outData['derOverviewData'] = json.dumps(fig.data, cls=plotly.utils.PlotlyJSONEncoder)
outData['derOverviewLayout'] = json.dumps(fig.layout, cls=plotly.utils.PlotlyJSONEncoder)

## Create Thermal Device Temperature plot object ######################################################################################################################################################
if (inputDict['load_type'] != '0') and (int(inputDict['number_devices'])>0): ## If vbatDispatch is enabled:

fig = go.Figure()

## It seems like the setpoint (interior temp) is fixed for devices except the water heater.
## TODO: If desired, could code this up to extract the interior temperature from the water heater code.
## It does not currently seem to output the changing interior temp.

#if inputDict['load_type'] == '4': ## Water Heater (the only option that evolves interior temperature over time)
# Interior Temperature
#interior_temp = theta.mean(axis=0)
#theta_s_wh #temperature setpoint (interior)
#theta_s_wh +/- deadband

fig.add_trace(go.Scatter(x=timestamps,
y=temperatures,
yaxis='y2',
mode='lines',
line=dict(color='red',width=1),
name='Average Air Temperature',
showlegend=showlegend
))

upper_bound = np.full_like(demand,float(inputDict['setpoint']) + float(inputDict['deadband'])/2)
lower_bound = np.full_like(demand,float(inputDict['setpoint']) - float(inputDict['deadband'])/2)
setpoint = np.full_like(demand, float(inputDict['setpoint'])) ## the setpoint is currently a fixed number

## Plot deadband upper bound
fig.add_trace(go.Scatter(
x=timestamps,
y=upper_bound,
yaxis='y2',
line=dict(color='rgba(0,0,0,1)'), ## color black
name='Deadband upper bound',
showlegend=True
))

## Plot deadband lower bound
fig.add_trace(go.Scatter(
x=timestamps,
y=lower_bound,
yaxis='y2',
mode='lines',
line=dict(color='rgba(0,0,0,0.5)'), ## color black but half opacity = gray color
name='Deadband lower bound',
showlegend=True
))

## Plot the setpoint (interior temperature)
fig.add_trace(go.Scatter(
x=timestamps,
y=setpoint,
yaxis='y2',
mode='lines',
line=dict(color='rgba(0, 27, 255, 1)'), ## color blue
name='Setpoint',
showlegend=True
))

## Plot layout
fig.update_layout(
#title='Residential Data',
xaxis=dict(title='Timestamp'),
yaxis=dict(title='Power (kW)'),
yaxis2=dict(title='degrees Celsius',
overlaying='y',
side='right'
),
legend=dict(
orientation='h',
yanchor='bottom',
y=1.02,
xanchor='right',
x=1
)
)

#fig.show()

## Encode plot data as JSON for showing in the HTML side
outData['thermalDevicePlotData'] = json.dumps(fig.data, cls=plotly.utils.PlotlyJSONEncoder)
outData['thermalDevicePlotLayout'] = json.dumps(fig.layout, cls=plotly.utils.PlotlyJSONEncoder)


## Create Thermal Battery Energy plot object ######################################################################################################################################################
if (inputDict['load_type'] != '0') and (int(inputDict['number_devices'])>0): ## If vbatDispatch is enabled:
fig = go.Figure()
fig.add_trace(go.Scatter(
x=timestamps,
y=vbatMinEnergyCapacity,
yaxis='y1',
mode='lines',
line=dict(color='green', width=1),
name='Minimum Energy Capacity',
showlegend=True
))
fig.add_trace(go.Scatter(
x=timestamps,
y=vbatMaxEnergyCapacity,
yaxis='y1',
mode='lines',
line=dict(color='blue', width=1),
name='Maximum Energy Capacity',
showlegend=True
))
fig.add_trace(go.Scatter(
x=timestamps,
y=vbatEnergy,
yaxis='y1',
mode='lines',
line=dict(color='black', width=1),
name='Actual Energy Capacity',
showlegend=True
))

## Plot layout
fig.update_layout(
xaxis=dict(title='Timestamp'),
yaxis=dict(title='Energy (kWh)'),
legend=dict(
orientation='h',
yanchor='bottom',
y=1.02,
xanchor='right',
x=1
)
)

## Encode plot data as JSON for showing in the HTML side
outData['thermalBatEnergyPlot'] = json.dumps(fig.data, cls=plotly.utils.PlotlyJSONEncoder)
outData['thermalBatEnergyPlotLayout'] = json.dumps(fig.layout, cls=plotly.utils.PlotlyJSONEncoder)

## Create Thermal Battery Power plot object ######################################################################################################################################################
if (inputDict['load_type'] != '0') and (int(inputDict['number_devices'])>0): ## If vbatDispatch is enabled:
fig = go.Figure()
@@ -654,18 +507,8 @@ def work(modelDir, inputDict):
total_kwh_residential_BESS = int(inputDict['numberBESS']) * float(inputDict['BESS_kwh'])
rateCompensation = float(inputDict['rateCompensation'])
total_residential_BESS_compensation = rateCompensation * np.sum(total_kwh_residential_BESS)
total_kw_BESS = total_kw_residential_BESS + float(inputDict['existing_kw'])
total_kwh_BESS = total_kwh_residential_BESS + float(inputDict['existing_kwh'])
BESS = reoptResults['ElectricStorage']['storage_to_load_series_kw']

monthHours = [(0, 744), (744, 1416), (1416, 2160), (2160, 2880),
(2880, 3624), (3624, 4344), (4344, 5088), (5088, 5832),
(5832, 6552), (6552, 7296), (7296, 8016), (8016, 8760)]

#total_kwh_residential_BESS_monthly = np.asarray([sum(total_kwh_residential_BESS[s:f]) for s, f in monthHours])
#total_compensation_residential_BESS_monthly = total_kwh_residential_BESS_monthly * rateCompensation
#print('Total amount paid to member-consumers ($/kWh): ', total_residential_BESS_compensation)

# Model operations typically ends here.
# Stdout/stderr.
outData['stdout'] = 'Success'
@@ -698,8 +541,6 @@ def new(modelDir):

## Chemical Battery Inputs
'numberBESS': '100', ## Number of residential Tesla Powerwall 2 batteries
'existing_kw': '19000.0',
'existing_kwh': '56000.0',
'chemBESSgridcharge': 'Yes',
'installed_cost_per_kw': '20.0', #'300.0', ## (Residential: $1,000-$1,500 per kW, Utility: $300-$700 per kW)
'installed_cost_per_kwh': '60.0', #'480.0', ## Cost per kWh reflecting Powerwall’s installed cost (Residential: $400-$900 per kWh, Utility: $200-$400 per kWh)

0 comments on commit 5f352b5

Please sign in to comment.