Skip to content

Commit 784bccc

Browse files
Include royalties in opex output/total opex (WIP)
1 parent b5191f2 commit 784bccc

File tree

7 files changed

+64
-5
lines changed

7 files changed

+64
-5
lines changed

src/geophires_x/Economics.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
from 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+
total_capex_parameter_output_parameter, royalties_opex_parameter_output_parameter
1717
from geophires_x.GeoPHIRESUtils import quantity
1818
from geophires_x.OptionList import Configuration, WellDrillingCostCorrelation, EconomicModel, EndUseOptions, PlantType, \
1919
_WellDrillingCostCorrelationCitation
@@ -1906,6 +1906,7 @@ def __init__(self, model: Model):
19061906
PreferredUnits=CurrencyFrequencyUnit.MDOLLARSPERYEAR,
19071907
CurrentUnits=CurrencyFrequencyUnit.MDOLLARSPERYEAR
19081908
)
1909+
self.royalties_opex = self.OutputParameterDict[self.royalties_opex.Name] = royalties_opex_parameter_output_parameter()
19091910

19101911
# district heating
19111912
self.peakingboilercost = self.OutputParameterDict[self.peakingboilercost.Name] = OutputParameter(
@@ -2502,10 +2503,17 @@ def Calculate(self, model: Model) -> None:
25022503
# Setting capex_total distinguishes capex from CCap's display name of 'Total capital costs',
25032504
# since SAM Economic Model doesn't subtract ITC from this value.
25042505
self.capex_total.value = (self.sam_economics_calculations.capex.quantity()
2505-
.to(self.capex_total.CurrentUnits.value).magnitude)
2506+
.to(self.capex_total.CurrentUnits.value).magnitude)
25062507
self.CCap.value = (self.sam_economics_calculations.capex.quantity()
25072508
.to(self.CCap.CurrentUnits.value).magnitude)
25082509

2510+
# FIXME WIP adjust OPEX for royalties
2511+
# FIXME WIP unit conversion
2512+
average_annual_royalties = np.average(self.sam_economics_calculations.royalties_opex[1:]) # ignore Year 0
2513+
if average_annual_royalties > 0:
2514+
self.royalties_opex.value = average_annual_royalties
2515+
self.Coam.value += self.royalties_opex.quantity().to(self.Coam.CurrentUnits.value).magnitude
2516+
25092517
self.wacc.value = self.sam_economics_calculations.wacc.value
25102518
self.nominal_discount_rate.value = self.sam_economics_calculations.nominal_discount_rate.value
25112519
self.ProjectNPV.value = self.sam_economics_calculations.project_npv.quantity().to(

src/geophires_x/EconomicsSam.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,12 @@
3939
project_payback_period_parameter,
4040
inflation_cost_during_construction_output_parameter,
4141
total_capex_parameter_output_parameter,
42+
royalties_opex_parameter_output_parameter,
4243
)
4344
from geophires_x.GeoPHIRESUtils import is_float, is_int, sig_figs, quantity
4445
from geophires_x.OptionList import EconomicModel, EndUseOptions
4546
from geophires_x.Parameter import Parameter, OutputParameter, floatParameter
46-
from geophires_x.Units import convertible_unit, EnergyCostUnit, CurrencyUnit, Units
47+
from geophires_x.Units import convertible_unit, EnergyCostUnit, CurrencyUnit, Units, CurrencyFrequencyUnit
4748

4849

4950
@dataclass
@@ -59,6 +60,8 @@ class SamEconomicsCalculations:
5960

6061
capex: OutputParameter = field(default_factory=total_capex_parameter_output_parameter)
6162

63+
royalties_opex: OutputParameter = field(default_factory=royalties_opex_parameter_output_parameter)
64+
6265
project_npv: OutputParameter = field(
6366
default_factory=lambda: OutputParameter(
6467
UnitType=Units.CURRENCY,
@@ -170,6 +173,13 @@ def sf(_v: float, num_sig_figs: int = 5) -> float:
170173
sam_economics.project_npv.value = sf(single_owner.Outputs.project_return_aftertax_npv * 1e-6)
171174
sam_economics.capex.value = single_owner.Outputs.adjusted_installed_cost * 1e-6
172175

176+
royalty_rate = model.economics.royalty_rate.quantity().to('dimensionless').magnitude
177+
ppa_revenue_row = _cash_flow_profile_row(cash_flow, 'PPA revenue ($)')
178+
royalties_unit = sam_economics.royalties_opex.CurrentUnits.value.replace('/yr', '')
179+
sam_economics.royalties_opex = [
180+
quantity(x * royalty_rate, 'USD').to(royalties_unit).magnitude for x in ppa_revenue_row
181+
]
182+
173183
sam_economics.nominal_discount_rate.value, sam_economics.wacc.value = _calculate_nominal_discount_rate_and_wacc(
174184
model, single_owner
175185
)

src/geophires_x/EconomicsUtils.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from __future__ import annotations
22

33
from geophires_x.Parameter import OutputParameter
4-
from geophires_x.Units import Units, PercentUnit, TimeUnit, CurrencyUnit
4+
from geophires_x.Units import Units, PercentUnit, TimeUnit, CurrencyUnit, CurrencyFrequencyUnit
55

66

77
def BuildPricingModel(plantlifetime: int, StartPrice: float, EndPrice: float,
@@ -144,3 +144,13 @@ def total_capex_parameter_output_parameter() -> OutputParameter:
144144
'For SAM Economic models, it also includes any cost escalation from inflation during construction. '
145145
'It is used as the total installed cost input for SAM Economic Models.'
146146
)
147+
148+
149+
def royalties_opex_parameter_output_parameter() -> OutputParameter:
150+
return OutputParameter(
151+
Name='Royalties',
152+
UnitType=Units.CURRENCYFREQUENCY,
153+
PreferredUnits=CurrencyFrequencyUnit.MDOLLARSPERYEAR,
154+
CurrentUnits=CurrencyFrequencyUnit.MDOLLARSPERYEAR,
155+
ToolTipText='Average annual royalties' # TODO WIP clarify
156+
)

src/geophires_x/Outputs.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -555,6 +555,10 @@ def PrintOutputs(self, model: Model):
555555
aoc_label = Outputs._field_label(model.addeconomics.AddOnOPEXTotalPerYear.display_name, 47)
556556
f.write(f' {aoc_label}{model.addeconomics.AddOnOPEXTotalPerYear.value:10.2f} {model.addeconomics.AddOnOPEXTotalPerYear.CurrentUnits.value}\n')
557557

558+
if econ.royalty_rate.value > 0.0:
559+
royalties_label = Outputs._field_label(econ.royalties_opex.display_name, 47)
560+
f.write(f' {royalties_label}{econ.royalties_opex.value:10.2f} {econ.royalties_opex.CurrentUnits.value}\n')
561+
558562
f.write(f' {econ.Coam.display_name}: {(econ.Coam.value + econ.averageannualpumpingcosts.value + econ.averageannualheatpumpelectricitycost.value):10.2f} {econ.Coam.CurrentUnits.value}\n')
559563
else:
560564
f.write(f' {econ.Coam.display_name}: {econ.Coam.value:10.2f} {econ.Coam.CurrentUnits.value}\n')

src/geophires_x_client/geophires_x_result.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,7 @@ class GeophiresXResult:
290290
'Annual District Heating O&M Cost',
291291
'Average Annual Peaking Fuel Cost',
292292
'Average annual pumping costs',
293+
'Royalties',
293294
# SUTRA
294295
'Average annual auxiliary fuel cost',
295296
'Average annual pumping cost',

tests/geophires_x_tests/test_economics_sam.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
get_sam_cash_flow_profile_tabulated_output,
2121
_ppa_pricing_model,
2222
_get_fed_and_state_tax_rates,
23+
SamEconomicsCalculations,
2324
)
2425
from geophires_x.GeoPHIRESUtils import sig_figs, quantity
2526

@@ -644,7 +645,7 @@ def test_royalty_rate(self):
644645
self._egs_test_file_path(), additional_params={'Royalty Rate': royalty_rate}
645646
)
646647

647-
sam_econ = calculate_sam_economics(m)
648+
sam_econ: SamEconomicsCalculations = calculate_sam_economics(m)
648649
cash_flow = sam_econ.sam_cash_flow_profile
649650

650651
def get_row(name: str):
@@ -653,8 +654,12 @@ def get_row(name: str):
653654
ppa_revenue_row = get_row('PPA revenue ($)')
654655
expected_royalties = [x * royalty_rate for x in ppa_revenue_row]
655656

657+
self.assertListEqual(expected_royalties, sam_econ.royalties_opex)
658+
656659
om_prod_based_expense_row = get_row('O&M production-based expense ($)')
657660
self.assertListAlmostEqual(expected_royalties, om_prod_based_expense_row, places=0)
661+
# Note the above assertion assumes royalties are the only production-based O&M expenses. If this changes,
662+
# the assertion will need to be updated.
658663

659664
@staticmethod
660665
def _new_model(input_file: Path, additional_params: dict[str, Any] | None = None, read_and_calculate=True) -> Model:

tests/test_geophires_x.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1294,3 +1294,24 @@ def test_redrilling_costs(self):
12941294
) / result.result['ECONOMIC PARAMETERS']['Project lifetime']['value']
12951295

12961296
self.assertAlmostEqual(expected_annual_redrilling_cost, result_opex['Redrilling costs']['value'], places=2)
1297+
1298+
def test_royalty_rate(self):
1299+
royalties_output_name = 'Royalties'
1300+
1301+
for royalty_rate in [0, 0.1]:
1302+
result = GeophiresXClient().get_geophires_result(
1303+
ImmutableGeophiresInputParameters(
1304+
from_file_path=self._get_test_file_path(
1305+
'geophires_x_tests/generic-egs-case-2_sam-single-owner-ppa.txt'
1306+
),
1307+
params={'Royalty Rate': royalty_rate},
1308+
)
1309+
)
1310+
opex_result = result.result['OPERATING AND MAINTENANCE COSTS (M$/yr)']
1311+
if royalty_rate > 0.0:
1312+
self.assertIsNotNone(opex_result[royalties_output_name])
1313+
self.assertEqual(58.88, opex_result[royalties_output_name]['value'])
1314+
self.assertEqual('MUSD/yr', opex_result[royalties_output_name]['unit'])
1315+
# FIXME WIP assert total opex includes royalties
1316+
else:
1317+
self.assertIsNone(opex_result[royalties_output_name])

0 commit comments

Comments
 (0)