9
9
from geophires_x .Units import *
10
10
11
11
12
- def BuildPricingModel (plantlifetime : int , StartYear : int , StartPrice : float , EndPrice : float ,
13
- EscalationStart : int , EscalationRate : float ):
12
+ def BuildPTCModel (plantlifetime : int , duration : int , ptc_price : float ,
13
+ ptc_inflation_adjusted : bool , inflation_rate : float ) -> list :
14
+ """
15
+ BuildPricingModel builds the price model array for the project lifetime. It is used to calculate the revenue
16
+ stream for the project.
17
+ :param plantlifetime: The lifetime of the project in years
18
+ :type plantlifetime: int
19
+ :param duration: The duration of the PTC in years
20
+ :type duration: int
21
+ :param ptc_price: The PTC in $/kWh
22
+ :type ptc_price: float
23
+ :param ptc_inflation_adjusted: Is the PTC is inflation?
24
+ :type ptc_inflation_adjusted: bool
25
+ :param inflation_rate: The inflation rate in %
26
+ :type inflation_rate: float
27
+ :return: Price: The price model array for the PTC in $/kWh
28
+ :rtype: list
29
+ """
30
+ # Build the PTC price model by setting the price to the PTCPrice for the duration of the PTC
31
+ Price = [0.0 ] * plantlifetime
32
+ for year in range (0 , duration , 1 ):
33
+ Price [year ] = ptc_price
34
+ if ptc_inflation_adjusted and year > 0 :
35
+ Price [year ] = Price [year - 1 ] * (1 + inflation_rate )
36
+ return Price
37
+
38
+
39
+ def BuildPricingModel (plantlifetime : int , StartPrice : float , EndPrice : float ,
40
+ EscalationStartYear : int , EscalationRate : float , PTCAddition : list ) -> list :
14
41
"""
15
42
BuildPricingModel builds the price model array for the project lifetime. It is used to calculate the revenue
16
43
stream for the project.
17
44
:param plantlifetime: The lifetime of the project in years
18
45
:type plantlifetime: int
19
- :param StartYear: The year the project starts in years (not including construction years)
20
- :type StartYear: int
21
46
:param StartPrice: The price in the first year of the project in $/kWh
22
47
:type StartPrice: float
23
48
:param EndPrice: The price in the last year of the project in $/kWh
24
49
:type EndPrice: float
25
- :param EscalationStart : The year the price escalation starts in years (not including construction years) in years
26
- :type EscalationStart : int
50
+ :param EscalationStartYear : The year the price escalation starts in years (not including construction years) in years
51
+ :type EscalationStartYear : int
27
52
:param EscalationRate: The rate of price escalation in $/kWh/year
28
53
:type EscalationRate: float
54
+ :param PTCAddition: The PTC addition array for the project in $/kWh
55
+ :type PTCAddition: list
29
56
:return: Price: The price model array for the project in $/kWh
30
57
:rtype: list
31
58
"""
32
- Price = [StartPrice ] * plantlifetime
33
- if StartPrice == EndPrice :
34
- return Price
35
- for i in range (StartYear , plantlifetime , 1 ):
36
- if i >= EscalationStart :
37
- Price [i ] = Price [i ] + ((i - EscalationStart ) * EscalationRate )
59
+ Price = [0.0 ] * plantlifetime
60
+ for i in range (0 , plantlifetime , 1 ):
61
+ Price [i ] = StartPrice
62
+ if i >= EscalationStartYear :
63
+ Price [i ] = Price [i ] + ((i - EscalationStartYear ) * EscalationRate )
38
64
if Price [i ] > EndPrice :
39
65
Price [i ] = EndPrice
66
+ Price [i ] = Price [i ] + PTCAddition [i ]
40
67
return Price
41
68
42
69
43
- def CalculateTotalRevenue (plantlifetime : int , ConstructionYears : int , CAPEX : float , OPEX : float , AnnualRev , CummRev ):
70
+ def CalculateTotalRevenue (plantlifetime : int , ConstructionYears : int , CAPEX : float , OPEX : float , AnnualRev ):
44
71
"""
45
72
CalculateRevenue calculates the revenue stream for the project. It is used to calculate the revenue
46
73
stream for the project.
@@ -52,10 +79,8 @@ def CalculateTotalRevenue(plantlifetime: int, ConstructionYears: int, CAPEX: flo
52
79
:type CAPEX: float
53
80
:param OPEX: The total annual operating cost of the project in MUSD
54
81
:type OPEX: float
55
- :param Energy: The energy production array for the project in kWh
56
- :type Energy: list
57
- :param Price: The price model array for the project in $/kWh
58
- :type Price: list
82
+ :param AnnualRev: The annual revenue array for the project in MUSD
83
+ :type AnnualRev: list
59
84
:return: CashFlow: The annual cash flow for the project in MUSD and CummCashFlow: The cumulative cash flow for the
60
85
project in MUSD
61
86
:rtype: list
@@ -307,6 +332,7 @@ def CalculateLCOELCOHLCOC(self, model: Model) -> tuple:
307
332
NPVfc = np .sum ((1 + self .inflrateconstruction .value ) * self .CCap .value * self .PTR .value * inflationvector * discountvector )
308
333
NPVit = np .sum (self .CTR .value / (1 - self .CTR .value ) * ((1 + self .inflrateconstruction .value ) * self .CCap .value * CRF - self .CCap .value / model .surfaceplant .plant_lifetime .value ) * discountvector )
309
334
NPVitc = (1 + self .inflrateconstruction .value ) * self .CCap .value * self .RITC .value / (1 - self .CTR .value )
335
+
310
336
if model .surfaceplant .enduse_option .value == EndUseOptions .ELECTRICITY :
311
337
NPVoandm = np .sum (self .Coam .value * inflationvector * discountvector )
312
338
NPVgrt = self .GTR .value / (1 - self .GTR .value ) * (NPVcap + NPVoandm + NPVfc + NPVit - NPVitc )
@@ -1223,6 +1249,57 @@ def __init__(self, model: Model):
1223
1249
ErrMessage = "assume calculation for CHP Electrical Plant Cost Allocation Ratio (cost electrical plant/total CAPEX)" ,
1224
1250
ToolTipText = "CHP Electrical Plant Cost Allocation Ratio (cost electrical plant/total CAPEX)"
1225
1251
)
1252
+ self .PTCElec = self .ParameterDict [self .PTCElec .Name ] = floatParameter (
1253
+ "Production Tax Credit Electricity" ,
1254
+ DefaultValue = 0.04 ,
1255
+ Min = 0.0 ,
1256
+ Max = 10.0 ,
1257
+ UnitType = Units .ENERGYCOST ,
1258
+ PreferredUnits = EnergyCostUnit .DOLLARSPERKWH ,
1259
+ CurrentUnits = EnergyCostUnit .DOLLARSPERKWH ,
1260
+ ErrMessage = "assume default for Production Tax Credit Electricity ($0.04/kWh)" ,
1261
+ ToolTipText = "Production tax credit for electricity in $/kWh"
1262
+ )
1263
+ self .PTCHeat = self .ParameterDict [self .PTCHeat .Name ] = floatParameter (
1264
+ "Production Tax Credit Heat" ,
1265
+ DefaultValue = 0.0 ,
1266
+ Min = 0.0 ,
1267
+ Max = 100.0 ,
1268
+ UnitType = Units .ENERGYCOST ,
1269
+ PreferredUnits = EnergyCostUnit .DOLLARSPERMMBTU ,
1270
+ CurrentUnits = EnergyCostUnit .DOLLARSPERMMBTU ,
1271
+ ErrMessage = "assume default for Production Tax Credit Heat ($0.0/MMBTU)" ,
1272
+ ToolTipText = "Production tax credit for heat in $/MMBTU"
1273
+ )
1274
+ self .PTCCooling = self .ParameterDict [self .PTCCooling .Name ] = floatParameter (
1275
+ "Production Tax Credit Cooling" ,
1276
+ DefaultValue = 0.0 ,
1277
+ Min = 0.0 ,
1278
+ Max = 100.0 ,
1279
+ UnitType = Units .ENERGYCOST ,
1280
+ PreferredUnits = EnergyCostUnit .DOLLARSPERMMBTU ,
1281
+ CurrentUnits = EnergyCostUnit .DOLLARSPERMMBTU ,
1282
+ ErrMessage = "assume default for Production Tax Credit Cooling ($0.0/MMBTU)" ,
1283
+ ToolTipText = "Production tax credit for cooling in $/MMBTU"
1284
+ )
1285
+ self .PTCDuration = self .ParameterDict [self .PTCDuration .Name ] = intParameter (
1286
+ "Production Tax Credit Duration" ,
1287
+ DefaultValue = 10 ,
1288
+ AllowableRange = list (range (0 , 100 , 1 )),
1289
+ UnitType = Units .TIME ,
1290
+ PreferredUnits = TimeUnit .YEAR ,
1291
+ CurrentUnits = TimeUnit .YEAR ,
1292
+ ErrMessage = "assume default for Production Tax Credit Duration (10 years)" ,
1293
+ ToolTipText = "Production tax credit for duration in years"
1294
+ )
1295
+ self .PTCInflationAdjusted = self .ParameterDict [self .PTCInflationAdjusted .Name ] = boolParameter (
1296
+ "Production Tax Credit Inflation Adjusted" ,
1297
+ DefaultValue = False ,
1298
+ UnitType = Units .NONE ,
1299
+ Required = False ,
1300
+ ErrMessage = "assume default for Production Tax Credit Inflation Adjusted (False)" ,
1301
+ ToolTipText = "Production tax credit inflation adjusted"
1302
+ )
1226
1303
1227
1304
# local variable initialization
1228
1305
self .CAPEX_cost_electricity_plant = 0.0
@@ -1512,6 +1589,12 @@ def __init__(self, model: Model):
1512
1589
PreferredUnits = TimeUnit .YEAR ,
1513
1590
CurrentUnits = TimeUnit .YEAR
1514
1591
)
1592
+ self .RITCValue = self .OutputParameterDict [self .RITCValue .Name ] = OutputParameter (
1593
+ Name = "Investment Tax Credit Value" ,
1594
+ UnitType = Units .CURRENCY ,
1595
+ PreferredUnits = CurrencyUnit .MDOLLARS ,
1596
+ CurrentUnits = CurrencyUnit .MDOLLARS
1597
+ )
1515
1598
1516
1599
model .logger .info (f'Complete { __class__ !s} : { sys ._getframe ().f_code .co_name } ' )
1517
1600
@@ -2301,6 +2384,11 @@ def Calculate(self, model: Model) -> None:
2301
2384
else :
2302
2385
self .CCap .value = self .totalcapcost .value
2303
2386
2387
+ # update the capitol costs, assuming the entire ITC is used to reduce the capitol costs
2388
+ if self .RITC .Provided :
2389
+ self .RITCValue .value = self .RITC .value * self .CCap .value
2390
+ self .CCap .value = self .CCap .value - self .RITCValue .value
2391
+
2304
2392
# Add in the FlatLicenseEtc, OtherIncentives, & TotalGrant
2305
2393
self .CCap .value = self .CCap .value + self .FlatLicenseEtc .value - self .OtherIncentives .value - self .TotalGrant .value
2306
2394
@@ -2410,19 +2498,41 @@ def Calculate(self, model: Model) -> None:
2410
2498
model .reserv .depth .value = model .reserv .depth .value / 1000.0
2411
2499
model .reserv .depth .CurrentUnits = LengthUnit .KILOMETERS
2412
2500
2501
+ # build the PTC price models
2502
+ self .PTCElecPrice = [0.0 ] * model .surfaceplant .plant_lifetime .value
2503
+ self .PTCHeatPrice = [0.0 ] * model .surfaceplant .plant_lifetime .value
2504
+ self .PTCCoolingPrice = [0.0 ] * model .surfaceplant .plant_lifetime .value
2505
+ self .PTCCarbonPrice = [0.0 ] * model .surfaceplant .plant_lifetime .value
2506
+ if self .PTCElec .Provided :
2507
+ self .PTCElecPrice = BuildPTCModel (model .surfaceplant .plant_lifetime .value ,
2508
+ self .PTCDuration .value , self .PTCElec .value , self .PTCInflationAdjusted .value ,
2509
+ self .RINFL .value )
2510
+ if self .PTCHeat .Provided :
2511
+ self .PTCHeatPrice = BuildPTCModel (model .surfaceplant .plant_lifetime .value ,
2512
+ self .PTCDuration .value , self .PTCHeat .value , self .PTCInflationAdjusted .value ,
2513
+ self .RINFL .value )
2514
+ if self .PTCCooling .Provided :
2515
+ self .PTCCoolingPrice = BuildPTCModel (model .surfaceplant .plant_lifetime .value ,
2516
+ self .PTCDuration .value ,self .PTCCooling .value , self .PTCInflationAdjusted .value ,
2517
+ self .RINFL .value )
2518
+
2413
2519
# build the price models
2414
- self .ElecPrice .value = BuildPricingModel (model .surfaceplant .plant_lifetime .value , 0 ,
2520
+ self .ElecPrice .value = BuildPricingModel (model .surfaceplant .plant_lifetime .value ,
2415
2521
self .ElecStartPrice .value , self .ElecEndPrice .value ,
2416
- self .ElecEscalationStart .value , self .ElecEscalationRate .value )
2417
- self .HeatPrice .value = BuildPricingModel (model .surfaceplant .plant_lifetime .value , 0 ,
2522
+ self .ElecEscalationStart .value , self .ElecEscalationRate .value ,
2523
+ self .PTCElecPrice )
2524
+ self .HeatPrice .value = BuildPricingModel (model .surfaceplant .plant_lifetime .value ,
2418
2525
self .HeatStartPrice .value , self .HeatEndPrice .value ,
2419
- self .HeatEscalationStart .value , self .HeatEscalationRate .value )
2420
- self .CoolingPrice .value = BuildPricingModel (model .surfaceplant .plant_lifetime .value , 0 ,
2526
+ self .HeatEscalationStart .value , self .HeatEscalationRate .value ,
2527
+ self .PTCHeatPrice )
2528
+ self .CoolingPrice .value = BuildPricingModel (model .surfaceplant .plant_lifetime .value ,
2421
2529
self .CoolingStartPrice .value , self .CoolingEndPrice .value ,
2422
- self .CoolingEscalationStart .value , self .CoolingEscalationRate .value )
2423
- self .CarbonPrice .value = BuildPricingModel (model .surfaceplant .plant_lifetime .value , self .CarbonEscalationStart .value ,
2530
+ self .CoolingEscalationStart .value , self .CoolingEscalationRate .value ,
2531
+ self .PTCCoolingPrice )
2532
+ self .CarbonPrice .value = BuildPricingModel (model .surfaceplant .plant_lifetime .value ,
2424
2533
self .CarbonStartPrice .value , self .CarbonEndPrice .value ,
2425
- self .CarbonEscalationStart .value , self .CarbonEscalationRate .value )
2534
+ self .CarbonEscalationStart .value , self .CarbonEscalationRate .value ,
2535
+ self .PTCCarbonPrice )
2426
2536
2427
2537
# do the additional economic calculations first, if needed, so the summaries below work.
2428
2538
if self .DoAddOnCalculations .value :
@@ -2492,6 +2602,13 @@ def Calculate(self, model: Model) -> None:
2492
2602
self .TotalRevenue .value [i ] = self .TotalRevenue .value [i ] + self .CarbonRevenue .value [i ]
2493
2603
#self.TotalCummRevenue.value[i] = self.TotalCummRevenue.value[i] + self.CarbonCummCashFlow.value[i]
2494
2604
2605
+ # for the sake of display, insert zeros at the beginning of the pricing arrays
2606
+ for i in range (0 , model .surfaceplant .construction_years .value , 1 ):
2607
+ self .ElecPrice .value .insert (0 , 0.0 )
2608
+ self .HeatPrice .value .insert (0 , 0.0 )
2609
+ self .CoolingPrice .value .insert (0 , 0.0 )
2610
+ self .CarbonPrice .value .insert (0 , 0.0 )
2611
+
2495
2612
# Insert the cost of construction into the front of the array that will be used to calculate NPV
2496
2613
# the convention is that the upfront CAPEX is negative
2497
2614
# This is the same for all projects
0 commit comments