1313from geophires_x .EconomicsUtils import BuildPricingModel , wacc_output_parameter , nominal_discount_rate_parameter , \
1414 real_discount_rate_parameter , after_tax_irr_parameter , moic_parameter , project_vir_parameter , \
1515 project_payback_period_parameter , inflation_cost_during_construction_output_parameter , \
16- total_capex_parameter_output_parameter
16+ interest_during_construction_output_parameter , total_capex_parameter_output_parameter , \
17+ overnight_capital_cost_output_parameter , CONSTRUCTION_CAPEX_SCHEDULE_PARAMETER_NAME , \
18+ _YEAR_INDEX_VALUE_EXPLANATION_SNIPPET
1719from geophires_x .GeoPHIRESUtils import quantity
1820from geophires_x .OptionList import Configuration , WellDrillingCostCorrelation , EconomicModel , EndUseOptions , PlantType , \
1921 _WellDrillingCostCorrelationCitation
2022from geophires_x .Parameter import intParameter , floatParameter , OutputParameter , ReadParameter , boolParameter , \
21- coerce_int_params_to_enum_values
23+ coerce_int_params_to_enum_values , listParameter , Parameter
24+ from geophires_x .SurfacePlantUtils import MAX_CONSTRUCTION_YEARS
2225from geophires_x .Units import *
2326from geophires_x .WellBores import calculate_total_drilling_lengths_m
2427
@@ -1004,6 +1007,17 @@ def __init__(self, model: Model):
10041007 "increases a 4% rate (0.04) to 4.1% (0.041) in the next year."
10051008 )
10061009
1010+ self .royalty_escalation_rate_start_year = self .ParameterDict [self .royalty_escalation_rate_start_year .Name ] = intParameter (
1011+ 'Royalty Rate Escalation Start Year' ,
1012+ DefaultValue = 1 ,
1013+ AllowableRange = list (range (1 , model .surfaceplant .plant_lifetime .AllowableRange [- 1 ], 1 )),
1014+ UnitType = Units .PERCENT ,
1015+ PreferredUnits = PercentUnit .TENTH ,
1016+ CurrentUnits = PercentUnit .TENTH ,
1017+ ToolTipText = f'The first year that the { self .royalty_escalation_rate .Name } is applied. '
1018+ f'{ _YEAR_INDEX_VALUE_EXPLANATION_SNIPPET } .'
1019+ )
1020+
10071021 maximum_royalty_rate_default_val = 1.0
10081022 self .maximum_royalty_rate = self .ParameterDict [self .maximum_royalty_rate .Name ] = floatParameter (
10091023 'Royalty Rate Maximum' ,
@@ -1050,28 +1064,43 @@ def __init__(self, model: Model):
10501064 'See https://github.com/NREL/GEOPHIRES-X/discussions/344 for further details.'
10511065 )
10521066
1067+ default_fraction_in_bonds = 0.5
10531068 self .FIB = self .ParameterDict [self .FIB .Name ] = floatParameter (
10541069 "Fraction of Investment in Bonds" ,
1055- DefaultValue = 0.5 ,
1070+ DefaultValue = default_fraction_in_bonds ,
10561071 Min = 0.0 ,
10571072 Max = 1.0 ,
10581073 UnitType = Units .PERCENT ,
10591074 PreferredUnits = PercentUnit .TENTH ,
10601075 CurrentUnits = PercentUnit .TENTH ,
1061- ErrMessage = "assume default fraction of investment in bonds (0.5 )" ,
1062- ToolTipText = "Fraction of geothermal project financing through bonds (debt)."
1076+ ErrMessage = f "assume default fraction of investment in bonds ({ default_fraction_in_bonds } )" ,
1077+ ToolTipText = "Fraction of geothermal project financing through bonds (debt/loans )."
10631078 )
1079+
1080+ default_bond_interest_rate = 0.05
10641081 self .BIR = self .ParameterDict [self .BIR .Name ] = floatParameter (
10651082 "Inflated Bond Interest Rate" ,
1066- DefaultValue = 0.05 ,
1083+ DefaultValue = default_bond_interest_rate ,
1084+ Min = 0.0 ,
1085+ Max = 1.0 ,
1086+ UnitType = Units .PERCENT ,
1087+ PreferredUnits = PercentUnit .TENTH ,
1088+ CurrentUnits = PercentUnit .TENTH ,
1089+ ErrMessage = f"assume default inflated bond interest rate ({ default_bond_interest_rate } )" ,
1090+ ToolTipText = "Inflated bond interest rate (for debt/loans)"
1091+ )
1092+
1093+ self .bond_interest_rate_during_construction = self .ParameterDict [self .bond_interest_rate_during_construction .Name ] = floatParameter (
1094+ 'Inflated Bond Interest Rate During Construction' ,
1095+ DefaultValue = self .BIR .DefaultValue ,
10671096 Min = 0.0 ,
10681097 Max = 1.0 ,
10691098 UnitType = Units .PERCENT ,
10701099 PreferredUnits = PercentUnit .TENTH ,
10711100 CurrentUnits = PercentUnit .TENTH ,
1072- ErrMessage = "assume default inflated bond interest rate (0.05)" ,
1073- ToolTipText = "Inflated bond interest rate (see docs)"
1101+ ToolTipText = 'Inflated bond interest rate during construction (for debt/loans)'
10741102 )
1103+
10751104 self .EIR = self .ParameterDict [self .EIR .Name ] = floatParameter (
10761105 "Inflated Equity Interest Rate" ,
10771106 DefaultValue = 0.1 ,
@@ -1155,6 +1184,50 @@ def __init__(self, model: Model):
11551184 'calculated automatically by compounding Inflation Rate over Construction Years.'
11561185 )
11571186
1187+ self .construction_capex_schedule = self .ParameterDict [self .construction_capex_schedule .Name ] = listParameter (
1188+ CONSTRUCTION_CAPEX_SCHEDULE_PARAMETER_NAME ,
1189+ DefaultValue = [1. ],
1190+ Min = 0.0 ,
1191+ Max = 1.0 ,
1192+ ToolTipText = f'A list of fractions of the total overnight CAPEX spent in each construction year. '
1193+ f'For example, for 3 construction years with 10% in the first year, 40% in the second, '
1194+ f'and 50% in the third, provide { CONSTRUCTION_CAPEX_SCHEDULE_PARAMETER_NAME } = 0.1,0.4,0.5. '
1195+ f'The schedule will be automatically interpolated to match the number of construction years '
1196+ f'and normalized to sum to 1.0.'
1197+ )
1198+
1199+ bond_financing_start_year_name = 'Bond Financing Start Year'
1200+ min_bond_financing_start_year = - 1 * (MAX_CONSTRUCTION_YEARS - 1 )
1201+ default_bond_financing_start_year = min_bond_financing_start_year
1202+ latest_allowed_bond_financing_start_year_index = 0
1203+ self .bond_financing_start_year = self .ParameterDict [self .bond_financing_start_year .Name ] = intParameter (
1204+ bond_financing_start_year_name ,
1205+ DefaultValue = default_bond_financing_start_year ,
1206+ AllowableRange = list (range (
1207+ min_bond_financing_start_year ,
1208+ latest_allowed_bond_financing_start_year_index + 1 ,
1209+ 1 )),
1210+ UnitType = Units .TIME ,
1211+ PreferredUnits = TimeUnit .YEAR ,
1212+ CurrentUnits = TimeUnit .YEAR ,
1213+ ToolTipText = f'By default, bond financing (debt/loans) starts during the first construction year '
1214+ f'(if { self .FIB .Name } is >0). '
1215+ f'Provide { bond_financing_start_year_name } to delay the '
1216+ f'start of bond financing during construction; years prior to { bond_financing_start_year_name } '
1217+ f'will be financed with equity only. '
1218+ f'{ _YEAR_INDEX_VALUE_EXPLANATION_SNIPPET } ; the first construction year has the year index '
1219+ f'{{({ model .surfaceplant .construction_years .Name } - 1) * -1}})'
1220+ f' and the final construction year index is 0. '
1221+ f'For example, a project with 4 construction years '
1222+ f'where bond financing starts on the third '
1223+ f'{ model .surfaceplant .construction_years .Name [:- 1 ].lower ()} '
1224+ f'would have a { bond_financing_start_year_name } value of -1; construction starts in Year -3, '
1225+ f'the second year is Year -2, and the final 2 bond-financed construction years are Year -1 '
1226+ f'and Year 0. '
1227+ f'Bond financing will start on the first construction year if the specified year index is '
1228+ f'prior to the first construction year.'
1229+ )
1230+
11581231 self .contingency_percentage = self .ParameterDict [self .contingency_percentage .Name ] = floatParameter (
11591232 'Contingency Percentage' ,
11601233 DefaultValue = 15. ,
@@ -2160,22 +2233,26 @@ def __init__(self, model: Model):
21602233 PreferredUnits = PercentUnit .PERCENT ,
21612234 CurrentUnits = PercentUnit .PERCENT
21622235 )
2236+
2237+ self .overnight_capital_cost = self .OutputParameterDict [
2238+ self .overnight_capital_cost .Name ] = overnight_capital_cost_output_parameter ()
2239+
21632240 self .accrued_financing_during_construction_percentage = self .OutputParameterDict [
21642241 self .accrued_financing_during_construction_percentage .Name ] = OutputParameter (
21652242 Name = 'Accrued financing during construction' ,
21662243 UnitType = Units .PERCENT ,
21672244 PreferredUnits = PercentUnit .PERCENT ,
21682245 CurrentUnits = PercentUnit .PERCENT ,
21692246 ToolTipText = 'The accrued inflation on total capital costs over the construction period, '
2170- f'as defined by { self .inflrateconstruction .Name } . '
2171- 'For SAM Economic Models, this is calculated automatically by compounding '
2172- f'{ self .RINFL .Name } over Construction Years '
2173- f'if { self .inflrateconstruction .Name } is not provided.'
2247+ f'as defined by { self .inflrateconstruction .Name } .'
21742248 )
21752249
21762250 self .inflation_cost_during_construction = self .OutputParameterDict [
21772251 self .inflation_cost_during_construction .Name ] = inflation_cost_during_construction_output_parameter ()
21782252
2253+ self .interest_during_construction = self .OutputParameterDict [
2254+ self .interest_during_construction .Name ] = interest_during_construction_output_parameter ()
2255+
21792256 self .after_tax_irr = self .OutputParameterDict [self .after_tax_irr .Name ] = (
21802257 after_tax_irr_parameter ())
21812258 self .real_discount_rate = self .OutputParameterDict [self .real_discount_rate .Name ] = (
@@ -2551,10 +2628,16 @@ def _warn(_msg: str) -> None:
25512628 if self .econmodel .value == EconomicModel .SAM_SINGLE_OWNER_PPA :
25522629 EconomicsSam .validate_read_parameters (model )
25532630 else :
2554- if self .royalty_rate .Provided :
2555- raise NotImplementedError ('Royalties are only supported for SAM Economic Models' )
2631+ sam_em_only_params : list [Parameter ] = [
2632+ self .royalty_rate ,
2633+ # TODO other royalty params
2634+ self .construction_capex_schedule ,
2635+ self .bond_financing_start_year
2636+ ]
2637+ for sam_em_only_param in sam_em_only_params :
2638+ if sam_em_only_param .Provided :
2639+ raise NotImplementedError (f'{ sam_em_only_param .Name } is only supported for SAM Economic Models' )
25562640
2557- # TODO validate that other SAM-EM-only parameters have not been provided
25582641 else :
25592642 model .logger .info ("No parameters read because no content provided" )
25602643
@@ -3330,10 +3413,11 @@ def r(x: float) -> float:
33303413
33313414 schedule = []
33323415 current_rate = r (self .royalty_rate .value )
3333- for _ in range (plant_lifetime ):
3416+ for year_index in range (plant_lifetime ):
33343417 current_rate = r (current_rate )
33353418 schedule .append (min (current_rate , max_rate ))
3336- current_rate += escalation_rate
3419+ if year_index >= (model .economics .royalty_escalation_rate_start_year .value - 2 ):
3420+ current_rate += escalation_rate
33373421
33383422 return schedule
33393423
@@ -3436,7 +3520,7 @@ def calculate_cashflow(self, model: Model) -> None:
34363520
34373521 def _calculate_sam_economics (self , model : Model ) -> None :
34383522 non_calculated_output_placeholder_val = - 1
3439- self .sam_economics_calculations = calculate_sam_economics (model )
3523+ self .sam_economics_calculations : SamEconomicsCalculations = calculate_sam_economics (model )
34403524
34413525 # Setting capex_total distinguishes capex from CCap's display name of 'Total capital costs',
34423526 # since SAM Economic Model doesn't subtract ITC from this value.
@@ -3445,6 +3529,14 @@ def _calculate_sam_economics(self, model: Model) -> None:
34453529 self .CCap .value = (self .sam_economics_calculations .capex .quantity ()
34463530 .to (self .CCap .CurrentUnits .value ).magnitude )
34473531
3532+ self .overnight_capital_cost .value = (self .sam_economics_calculations .overnight_capital_cost .quantity ()
3533+ .to (self .overnight_capital_cost .CurrentUnits .value ).magnitude )
3534+
3535+ self .interest_during_construction .value = quantity (
3536+ self .sam_economics_calculations .pre_revenue_costs_and_cash_flow .interest_during_construction_usd ,
3537+ 'USD'
3538+ ).to (self .interest_during_construction .CurrentUnits .value ).magnitude
3539+
34483540
34493541 if self .royalty_rate .Provided :
34503542 # ignore pre-revenue year(s) (e.g. Year 0)
0 commit comments