Skip to content

Commit 1a32631

Browse files
Use GEOPHIRES PPA pricing model instead of escalation-based approximation
1 parent 806d688 commit 1a32631

File tree

7 files changed

+147
-113
lines changed

7 files changed

+147
-113
lines changed

docs/SAM-Economic-Models.md

Lines changed: 19 additions & 20 deletions
Large diffs are not rendered by default.

src/geophires_x/Economics.py

Lines changed: 7 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import math
2-
import os
32
import sys
43
import numpy as np
54
import numpy_financial as npf
65
import geophires_x.Model as Model
76
from geophires_x import EconomicsSam
87
from geophires_x.EconomicsSam import calculate_sam_economics
8+
from geophires_x.EconomicsUtils import BuildPricingModel
99
from geophires_x.OptionList import Configuration, WellDrillingCostCorrelation, EconomicModel, EndUseOptions, PlantType, \
1010
_WellDrillingCostCorrelationCitation
1111
from geophires_x.Parameter import intParameter, floatParameter, OutputParameter, ReadParameter, boolParameter, \
@@ -165,37 +165,6 @@ def BuildPTCModel(plantlifetime: int, duration: int, ptc_price: float,
165165
return Price
166166

167167

168-
def BuildPricingModel(plantlifetime: int, StartPrice: float, EndPrice: float,
169-
EscalationStartYear: int, EscalationRate: float, PTCAddition: list) -> list:
170-
"""
171-
BuildPricingModel builds the price model array for the project lifetime. It is used to calculate the revenue
172-
stream for the project.
173-
:param plantlifetime: The lifetime of the project in years
174-
:type plantlifetime: int
175-
:param StartPrice: The price in the first year of the project in $/kWh
176-
:type StartPrice: float
177-
:param EndPrice: The price in the last year of the project in $/kWh
178-
:type EndPrice: float
179-
:param EscalationStartYear: The year the price escalation starts in years (not including construction years) in years
180-
:type EscalationStartYear: int
181-
:param EscalationRate: The rate of price escalation in $/kWh/year
182-
:type EscalationRate: float
183-
:param PTCAddition: The PTC addition array for the project in $/kWh
184-
:type PTCAddition: list
185-
:return: Price: The price model array for the project in $/kWh
186-
:rtype: list
187-
"""
188-
Price = [0.0] * plantlifetime
189-
for i in range(0, plantlifetime, 1):
190-
Price[i] = StartPrice
191-
if i >= EscalationStartYear:
192-
Price[i] = Price[i] + ((i - EscalationStartYear) * EscalationRate)
193-
if Price[i] > EndPrice:
194-
Price[i] = EndPrice
195-
Price[i] = Price[i] + PTCAddition[i]
196-
return Price
197-
198-
199168
def CalculateTotalRevenue(plantlifetime: int, ConstructionYears: int, CAPEX: float, OPEX: float, AnnualRev):
200169
"""
201170
CalculateRevenue calculates the revenue stream for the project. It is used to calculate the revenue
@@ -2761,13 +2730,13 @@ def Calculate(self, model: Model) -> None:
27612730
self.HeatEscalationStart.value, self.HeatEscalationRate.value,
27622731
self.PTCHeatPrice)
27632732
self.CoolingPrice.value = BuildPricingModel(model.surfaceplant.plant_lifetime.value,
2764-
self.CoolingStartPrice.value, self.CoolingEndPrice.value,
2765-
self.CoolingEscalationStart.value, self.CoolingEscalationRate.value,
2766-
self.PTCCoolingPrice)
2733+
self.CoolingStartPrice.value, self.CoolingEndPrice.value,
2734+
self.CoolingEscalationStart.value, self.CoolingEscalationRate.value,
2735+
self.PTCCoolingPrice)
27672736
self.CarbonPrice.value = BuildPricingModel(model.surfaceplant.plant_lifetime.value,
2768-
self.CarbonStartPrice.value, self.CarbonEndPrice.value,
2769-
self.CarbonEscalationStart.value, self.CarbonEscalationRate.value,
2770-
self.PTCCarbonPrice)
2737+
self.CarbonStartPrice.value, self.CarbonEndPrice.value,
2738+
self.CarbonEscalationStart.value, self.CarbonEscalationRate.value,
2739+
self.PTCCarbonPrice)
27712740

27722741
# do the additional economic calculations first, if needed, so the summaries below work.
27732742
if self.DoAddOnCalculations.value:

src/geophires_x/EconomicsSam.py

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,13 @@
2626

2727
from geophires_x import Model as Model
2828
from geophires_x.EconomicsSamCashFlow import _calculate_sam_economics_cash_flow
29+
from geophires_x.EconomicsUtils import BuildPricingModel
2930
from geophires_x.GeoPHIRESUtils import is_float, is_int
3031
from geophires_x.OptionList import EconomicModel, EndUseOptions
3132
from geophires_x.Parameter import Parameter
3233
from geophires_x.Units import convertible_unit
3334

3435
_SAM_CASH_FLOW_PROFILE_KEY = 'Cash Flow'
35-
_GEOPHIRES_TO_SAM_PRICING_MODEL_RATE_CONVERSION_CONSTANT = 0.745
3636

3737

3838
def validate_read_parameters(model: Model):
@@ -129,6 +129,7 @@ def get_sam_cash_flow_profile_tabulated_output(model: Model, **tabulate_kw_args)
129129
'floatfmt': ',.2f',
130130
**tabulate_kw_args
131131
}
132+
132133
# fmt:on
133134

134135
def get_entry_display(entry: Any) -> str:
@@ -222,15 +223,13 @@ def pct(econ_value: Parameter) -> float:
222223
geophires_ptr_tenths = Decimal(econ.PTR.value)
223224
ret['property_tax_rate'] = float(geophires_ptr_tenths * Decimal(100))
224225

225-
ret['ppa_price_input'] = [econ.ElecStartPrice.value]
226-
# Approximation of GEOPHIRES rate model into SAM's percent inflation model (TODO - could probably be improved)
227-
ppa_escalation_rate_percent = round(
228-
econ.ElecEscalationRate.value
229-
/ econ.ElecStartPrice.value
230-
* _GEOPHIRES_TO_SAM_PRICING_MODEL_RATE_CONVERSION_CONSTANT
231-
* 100.0
226+
ret['ppa_price_input'] = _ppa_pricing_model(
227+
model.surfaceplant.plant_lifetime.value,
228+
econ.ElecStartPrice.value,
229+
econ.ElecEndPrice.value,
230+
econ.ElecEscalationStart.value,
231+
econ.ElecEscalationRate.value,
232232
)
233-
ret['ppa_escalation'] = ppa_escalation_rate_percent
234233

235234
# Debt/equity ratio ('Fraction of Investment in Bonds' parameter)
236235
ret['debt_percent'] = pct(econ.FIB)
@@ -249,6 +248,17 @@ def pct(econ_value: Parameter) -> float:
249248
return ret
250249

251250

251+
def _ppa_pricing_model(
252+
plant_lifetime: int, start_price: float, end_price: float, escalation_start_year: int, escalation_rate: float
253+
) -> list:
254+
# See relevant comment in geophires_x.EconomicsUtils.BuildPricingModel re:
255+
# https://github.com/NREL/GEOPHIRES-X/issues/340?title=Price+Escalation+Start+Year+seemingly+off+by+1.
256+
# We use the same utility method here for the sake of consistency despite technical incorrectness.
257+
return BuildPricingModel(
258+
plant_lifetime, start_price, end_price, escalation_start_year, escalation_rate, [0] * plant_lifetime
259+
)
260+
261+
252262
def _get_max_net_generation_MW(model: Model) -> float:
253263
return np.max(model.surfaceplant.NetElectricityProduced.value)
254264

src/geophires_x/EconomicsUtils.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
def BuildPricingModel(plantlifetime: int, StartPrice: float, EndPrice: float,
2+
EscalationStartYear: int, EscalationRate: float, PTCAddition: list) -> list:
3+
"""
4+
BuildPricingModel builds the price model array for the project lifetime. It is used to calculate the revenue
5+
stream for the project.
6+
:param plantlifetime: The lifetime of the project in years
7+
:type plantlifetime: int
8+
:param StartPrice: The price in the first year of the project in $/kWh
9+
:type StartPrice: float
10+
:param EndPrice: The price in the last year of the project in $/kWh
11+
:type EndPrice: float
12+
:param EscalationStartYear: The year the price escalation starts in years (not including construction years) in years
13+
:type EscalationStartYear: int
14+
:param EscalationRate: The rate of price escalation in $/kWh/year
15+
:type EscalationRate: float
16+
:param PTCAddition: The PTC addition array for the project in $/kWh
17+
:type PTCAddition: list
18+
:return: Price: The price model array for the project in $/kWh
19+
:rtype: list
20+
"""
21+
Price = [0.0] * plantlifetime
22+
for i in range(0, plantlifetime, 1):
23+
Price[i] = StartPrice
24+
if i >= EscalationStartYear:
25+
# TODO: This is arguably an unwanted/incorrect interpretation of escalation start year, see
26+
# https://github.com/NREL/GEOPHIRES-X/issues/340?title=Price+Escalation+Start+Year+seemingly+off+by+1
27+
Price[i] = Price[i] + ((i - EscalationStartYear) * EscalationRate)
28+
if Price[i] > EndPrice:
29+
Price[i] = EndPrice
30+
Price[i] = Price[i] + PTCAddition[i]
31+
return Price

src/geophires_x/SBTEconomics.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import sys, math
22
import numpy as np
33
import geophires_x.Model as Model
4-
from .Economics import Economics, calculate_cost_of_one_vertical_well, BuildPTCModel, BuildPricingModel, \
5-
CalculateRevenue, CalculateFinancialPerformance, CalculateLCOELCOHLCOC
4+
from .Economics import Economics, calculate_cost_of_one_vertical_well, BuildPTCModel, CalculateRevenue, CalculateFinancialPerformance, CalculateLCOELCOHLCOC
5+
from .EconomicsUtils import BuildPricingModel
66
from .OptionList import Configuration, WellDrillingCostCorrelation, PlantType
77
from geophires_x.Parameter import floatParameter
88
from geophires_x.Units import *

0 commit comments

Comments
 (0)