Skip to content

Commit e3b92c9

Browse files
Initial implementation of royalty rate. WIP - TODO to implement royalty collector revenue/NPV/etc., update documentation, add more unit tests
1 parent 875f598 commit e3b92c9

File tree

4 files changed

+48
-1
lines changed

4 files changed

+48
-1
lines changed

src/geophires_x/Economics.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -967,6 +967,16 @@ def __init__(self, model: Model):
967967
"will be automatically set to the same value."
968968
)
969969

970+
self.royalty_rate = self.ParameterDict[self.royalty_rate.Name] = floatParameter(
971+
"Royalty Rate",
972+
DefaultValue=0.,
973+
Min=0.0,
974+
Max=1.0,
975+
UnitType=Units.PERCENT,
976+
PreferredUnits=PercentUnit.TENTH,
977+
CurrentUnits=PercentUnit.TENTH,
978+
ToolTipText="Royalty rate used in SAM Economic Models." # FIXME WIP TODO documentation
979+
)
970980

971981
self.discount_initial_year_cashflow = self.ParameterDict[self.discount_initial_year_cashflow.Name] = boolParameter(
972982
'Discount Initial Year Cashflow',

src/geophires_x/EconomicsSam.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -395,13 +395,29 @@ def _get_single_owner_parameters(model: Model) -> dict[str, Any]:
395395
geophires_ptr_tenths = Decimal(econ.PTR.value)
396396
ret['property_tax_rate'] = float(geophires_ptr_tenths * Decimal(100))
397397

398-
ret['ppa_price_input'] = _ppa_pricing_model(
398+
ppa_price_schedule_per_kWh = _ppa_pricing_model(
399399
model.surfaceplant.plant_lifetime.value,
400400
econ.ElecStartPrice.value,
401401
econ.ElecEndPrice.value,
402402
econ.ElecEscalationStart.value,
403403
econ.ElecEscalationRate.value,
404404
)
405+
ret['ppa_price_input'] = ppa_price_schedule_per_kWh
406+
407+
if hasattr(econ, 'royalty_rate') and econ.royalty_rate.value > 0.0:
408+
royalty_rate_fraction = econ.royalty_rate.quantity().to(convertible_unit('dimensionless')).magnitude
409+
410+
# For each year, calculate the royalty as a $/MWh variable cost.
411+
# The royalty is a percentage of revenue (MWh * $/MWh). By setting the
412+
# variable O&M rate to (PPA Price * Royalty Rate), SAM's calculation
413+
# (Rate * MWh) will correctly yield the total royalty payment.
414+
variable_om_schedule_per_MWh = [
415+
(price_per_kWh * 1000) * royalty_rate_fraction # TODO pint unit conversion (kWh -> MWh)
416+
for price_per_kWh in ppa_price_schedule_per_kWh
417+
]
418+
419+
# The PySAM parameter for variable operating cost in $/MWh is 'om_production'.
420+
ret['om_production'] = variable_om_schedule_per_MWh
405421

406422
# Debt/equity ratio ('Fraction of Investment in Bonds' parameter)
407423
ret['debt_percent'] = _pct(econ.FIB)

src/geophires_x/Units.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ def convertible_unit(unit: Any) -> Any:
2626
if unit == Units.PERCENT or unit == PercentUnit.PERCENT or unit == Units.PERCENT.value:
2727
return 'percent'
2828

29+
if unit == PercentUnit.TENTH or unit == PercentUnit.TENTH.value:
30+
return 'dimensionless'
31+
2932
return unit
3033

3134

tests/geophires_x_tests/test_economics_sam.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -638,6 +638,24 @@ def _assert_capex_line_items_sum_to_total(self, r: GeophiresXResult):
638638

639639
self.assertEqual(total_capex, capex_line_item_sum)
640640

641+
def test_royalty_rate(self):
642+
royalty_rate = 0.1
643+
m: Model = EconomicsSamTestCase._new_model(
644+
self._egs_test_file_path(), additional_params={'Royalty Rate': royalty_rate}
645+
)
646+
647+
sam_econ = calculate_sam_economics(m)
648+
cash_flow = sam_econ.sam_cash_flow_profile
649+
650+
def get_row(name: str):
651+
return EconomicsSamTestCase._get_cash_flow_row(cash_flow, name)
652+
653+
ppa_revenue_row = get_row('PPA revenue ($)')
654+
expected_royalties = [x * royalty_rate for x in ppa_revenue_row]
655+
656+
om_prod_based_expense_row = get_row('O&M production-based expense ($)')
657+
self.assertListAlmostEqual(expected_royalties, om_prod_based_expense_row, places=0)
658+
641659
@staticmethod
642660
def _new_model(input_file: Path, additional_params: dict[str, Any] | None = None, read_and_calculate=True) -> Model:
643661
if additional_params is not None:

0 commit comments

Comments
 (0)