Skip to content

Commit

Permalink
derUtilityCost: Added resilience outage plots, ad-hoc consumer BESS c…
Browse files Browse the repository at this point in the history
…ost estimates, and fixed critical load input file
  • Loading branch information
astronobri committed Oct 23, 2024
1 parent 526861f commit 8d9dbbc
Show file tree
Hide file tree
Showing 4 changed files with 8,873 additions and 20 deletions.
6 changes: 3 additions & 3 deletions omf/models/derConsumer.py
Original file line number Diff line number Diff line change
Expand Up @@ -620,9 +620,9 @@ def work(modelDir, inputDict):
## Calculate total DER compensation amount (including any subsidies)
total_compensation = total_economic_benefit + total_der_compensation - total_energy_cost + subsidy_amount - demand_cost

print(f"Total Energy Cost: ${total_energy_cost:.2f}")
print(f"Total DER Compensation: ${total_der_compensation:.2f}")
print(f"Total Compensation: ${total_compensation:.2f}")
#print(f"Total Energy Cost: ${total_energy_cost:.2f}")
#print(f"Total DER Compensation: ${total_der_compensation:.2f}")
#print(f"Total Compensation: ${total_compensation:.2f}")

## Add variables to outData
outData['energyCost'] = list(np.asarray(outData['energyCost']) + monthly_energy_cost)
Expand Down
18 changes: 14 additions & 4 deletions omf/models/derUtilityCost.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@
$(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('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']) || {});
});
</script>
</head>
Expand Down Expand Up @@ -233,7 +235,7 @@
<input type="text" id="resistance" name="resistance" value="{{allInputDataDict.resistance}}" pattern="^\d+\.?\d*?$" required="required">
</div>
<div class="shortInput">
<label class="tooltip">COP<span class="classic">Coefficient of power of each device. Must be between 1 and 3.5</span></label>
<label class="tooltip">COP<span class="classic">Coefficient of performance of each device. Must be between 1 and 3.5</span></label>
<input type="text" id="cop" name="cop" value="{{allInputDataDict.cop}}" pattern="^\d+\.?\d*?$" required="required">
</div>
<div class="shortInput">
Expand Down Expand Up @@ -278,12 +280,20 @@
<p class="reportTitle" style="page-break-before:always">Thermal Battery Power Profile</p>
<div id="thermalBatPowerPlot" class="tightContent" style="width: 1000px; height: 600px;"></div>

<!--
<p class="reportTitle" style="page-break-before:always">Thermal Device Temperature Profile</p>
<div id="thermalDevicePlotData" class="tightContent" style="width: 1000px; height: 600px;"></div>
-->

<p class="reportTitle" style="page-break-before:always">Battery Charge Percentage</p>
<div id="batteryChargePlotly" class="tightContent" style="width: 1000px; height: 600px;"></div>

<p class="reportTitle" style="page-break-before:always">Resilience Overview</p>
<div id="resiliencePlotly" class="tightContent"></div>

<p class="reportTitle" style="page-break-before:always">Outage Survival Probability</p>
<div id="resilienceProbPlotly" class="tightContent"></div>

<p class="reportTitle">Monthly Cost Comparison</p>
<div id="levelizedCostReport" class="tightContent">
<div id="levelizedCostTableDiv" style="display:inline-block; width:1000px">
Expand Down Expand Up @@ -331,8 +341,8 @@
insertMetric("monthlySummaryTable","Total Cost ($)", allOutputData.totalCost)
insertMetric("monthlySummaryTable","Adjusted Total Cost ($)", allOutputData.totalCostAdjusted)
insertMetric("monthlySummaryTable","Utility Savings ($)", allOutputData.savings)
insertMetric("monthlySummaryTable","Average 1kW Consumer Savings ($)", allOutputData.savingsSmallConsumer)
insertMetric("monthlySummaryTable","Average 10kW Consumer Savings ($)", allOutputData.savingsLargeConsumer)
insertMetric("monthlySummaryTable","1 kW Consumer Savings ($)", allOutputData.savingsSmallConsumer)
insertMetric("monthlySummaryTable","10 kW Consumer Savings ($)", allOutputData.savingsLargeConsumer)
</script>
</div>
</div>
Expand Down
110 changes: 97 additions & 13 deletions omf/models/derUtilityCost.py
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,7 @@ def work(modelDir, inputDict):
## Change directory to large derConsumer and run that case
os.chdir(newDir_largederConsumer)
derConsumerInputDict['demandCurve'] = largeConsumerLoadString
derConsumerInputDict['number_devices'] = '2'
largeConsumerOutput = derConsumer.work(newDir_largederConsumer,derConsumerInputDict)

## Change directory back to derUtilityCost
Expand Down Expand Up @@ -564,21 +565,106 @@ def work(modelDir, inputDict):
outData['batteryChargeData'] = json.dumps(fig.data, cls=plotly.utils.PlotlyJSONEncoder)
outData['batteryChargeLayout'] = json.dumps(fig.layout, cls=plotly.utils.PlotlyJSONEncoder)



## Add REopt resilience plot (adapted from omf/models/microgridDesign.py) ########################################################################################################################
#helper function for generating output graphs
def makeGridLine(x,y,color,name):
plotLine = go.Scatter(
x = x,
y = y,
line = dict( color=(color)),
name = name,
hoverlabel = dict(namelength = -1),
showlegend=True,
stackgroup='one',
mode='none'
)
return plotLine
#Set plotly layout ---------------------------------------------------------------
plotlyLayout = go.Layout(
width=1000,
height=375,
legend=dict(
x=0,
y=1.25,
orientation="h")
)
x = list(range(len(reoptResults['ElectricUtility']['electric_to_load_series_kw'])))
plotData = []
#x_values = pd.to_datetime(x, unit = 'h', origin = pd.Timestamp(f'{year}-01-01'))
x_values = timestamps
powerGridToLoad = makeGridLine(x_values,reoptResults['ElectricUtility']['electric_to_load_series_kw'],'blue','Load met by Grid')
plotData.append(powerGridToLoad)

if (inputDict['outage']): ## TODO: condense this code if possible
outData['resilience'] = reoptResultsResilience['resilience_by_time_step']
outData['minOutage'] = reoptResultsResilience['resilience_hours_min']
outData['maxOutage'] = reoptResultsResilience['resilience_hours_max']
outData['avgOutage'] = reoptResultsResilience['resilience_hours_avg']
outData['survivalProbX'] = reoptResultsResilience['outage_durations']
outData['survivalProbY'] = reoptResultsResilience['probs_of_surviving']

plotData = []
resilience = go.Scatter(
x=x,
y=outData['resilience'],
line=dict( color=('red') ),
)
plotData.append(resilience)
plotlyLayout['yaxis'].update(title='Longest Outage survived (Hours)')
plotlyLayout['xaxis'].update(title='Start Hour')
outData['resilienceData'] = json.dumps(plotData, cls=plotly.utils.PlotlyJSONEncoder)
outData['resilienceLayout'] = json.dumps(plotlyLayout, cls=plotly.utils.PlotlyJSONEncoder)

plotData = []
survivalProb = go.Scatter(
x=outData['survivalProbX'],
y=outData['survivalProbY'],
line=dict( color=('red') ),
name='Probability of Surviving Outage of a Given Duration')
plotData.append(survivalProb)
plotlyLayout['yaxis'].update(title='Probability of Meeting Critical Load')
plotlyLayout['xaxis'].update(title='Outage Length (Hours)')
outData['resilienceProbData' ] = json.dumps(plotData, cls=plotly.utils.PlotlyJSONEncoder)
outData['resilienceProbLayout'] = json.dumps(plotlyLayout, cls=plotly.utils.PlotlyJSONEncoder)

#####################################################################################################################################################################################################
## Compensation rate to member-consumer
compensation_rate = float(inputDict['rateCompensation'])
compensationRate = float(inputDict['rateCompensation'])
subsidy = float(inputDict['subsidy'])
electricityCost = float(inputDict['electricityCost'])

if inputDict['BESS'] == 'Yes': ## BESS
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)]

monthlyDemand_smallConsumer = np.asarray([sum(smallConsumerLoad[s:f]) for s, f in monthHours])
monthlyDemand_largeConsumer = np.asarray([sum(largeConsumerLoad[s:f]) for s, f in monthHours])
monthlyDemandCost_smallConsumer = monthlyDemand_smallConsumer * electricityCost
monthlyDemandCost_largeConsumer = monthlyDemand_largeConsumer * electricityCost


BESS_smallConsumer = smallConsumerOutput['ElectricStorage']['storage_to_load_series_kw']
BESS_largeConsumer = largeConsumerOutput['ElectricStorage']['storage_to_load_series_kw']
monthlyBESS_smallConsumer = np.asarray([sum(BESS_smallConsumer[s:f]) for s, f in monthHours])
monthlyBESS_largeConsumer = np.asarray([sum(BESS_largeConsumer[s:f]) for s, f in monthHours])
monthlyBESSCost_smallConsumer = monthlyBESS_smallConsumer * compensationRate
monthlyBESSCost_largeConsumer = monthlyBESS_largeConsumer * compensationRate

print('Small Consumer demand cost w/o BESS: ${:,.2f}'.format(np.sum(monthlyDemandCost_smallConsumer)))
print('Small Consumer savings with BESS: ${:,.2f} \n'.format(np.sum(monthlyBESSCost_smallConsumer)))
print('Large Consumer demand cost w/o BESS: ${:,.2f}'.format(np.sum(monthlyDemandCost_largeConsumer)))
print('Large Consumer savings with BESS: ${:,.2f}'.format(np.sum(monthlyBESSCost_largeConsumer)))


if 'ElectricStorage' in reoptResults: ## BESS
BESS = reoptResults['ElectricStorage']['storage_to_load_series_kw']
BESS_compensated_to_consumer = np.sum(BESS)*compensation_rate+subsidy
print('Compensation rate for BESS: ${:,.2f}'.format(BESS_compensated_to_consumer))
BESS_bought_from_grid = np.sum(BESS)*float(inputDict['electricityCost'])
print('BESS energy if bought from grid: ${:,.2f}'.format(BESS_bought_from_grid))
BESS_compensated_to_consumer = np.sum(BESS)*compensationRate+subsidy
print('--------------------------------------------------------')
print('Utility"s total compensation rate for consumer BESS: ${:,.2f}'.format(BESS_compensated_to_consumer))
BESS_bought_from_grid = np.sum(BESS) * electricityCost
print('Electricity Cost of BESS: ${:,.2f}'.format(BESS_bought_from_grid))
print('Difference (electricity cost - compensated cost): ${:,.2f}'.format(BESS_bought_from_grid-BESS_compensated_to_consumer))


# Model operations typically ends here.
# Stdout/stderr.
outData['stdout'] = 'Success'
Expand All @@ -591,8 +677,7 @@ def new(modelDir):
demand_curve = f.read()
with open(pJoin(__neoMetaModel__._omfDir,'static','testFiles','utility_CO_2018_temperatures.csv')) as f:
temp_curve = f.read()
## TODO: Change the critical load to utility scale critical load instead of residential
with open(pJoin(__neoMetaModel__._omfDir,'static','testFiles','residential_critical_load.csv')) as f:
with open(pJoin(__neoMetaModel__._omfDir,'static','testFiles','utility_2018_kW_critical_load.csv')) as f:
criticalLoad_curve = f.read()

defaultInputs = {
Expand All @@ -611,8 +696,7 @@ def new(modelDir):
'tempFileName': 'utility_CO_2018_temperatures.csv',
'demandCurve': demand_curve,
'tempCurve': temp_curve,
## ODO: Change criticalLoadFileName to utility load instead of residential
'criticalLoadFileName': 'residential_critical_load.csv', ## critical load here = 50% of the daily demand
'criticalLoadFileName': 'utility_2018_kW_critical_load.csv', ## critical load here = 50% of the daily demand
'criticalLoad': criticalLoad_curve,
'criticalLoadSwitch': 'Yes',
'criticalLoadFactor': '0.50',
Expand Down
Loading

0 comments on commit 8d9dbbc

Please sign in to comment.