Skip to content

Commit 467ef07

Browse files
Switch from integer 'Cashflow Series Start Year' to boolean 'Discount Initial Year Cashflow' to match Excel's (simpler) convention, which does not treat cash outlays spread over multiple initial years differently.
1 parent 7213230 commit 467ef07

File tree

4 files changed

+29
-28
lines changed

4 files changed

+29
-28
lines changed

src/geophires_x/Economics.py

Lines changed: 20 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -310,7 +310,7 @@ def CalculateFinancialPerformance(plantlifetime: int,
310310
TotalCummRevenue: list,
311311
CAPEX: float,
312312
OPEX: float,
313-
cashflow_series_start_year: float = 0.):
313+
discount_initial_year_cashflow: bool = False):
314314
"""
315315
CalculateFinancialPerformance calculates the financial performance of the project. It is used to calculate the
316316
financial performance of the project. It is used to calculate the revenue stream for the project.
@@ -326,8 +326,8 @@ def CalculateFinancialPerformance(plantlifetime: int,
326326
:type CAPEX: float
327327
:param OPEX: The total annual operating cost of the project in MUSD
328328
:type OPEX: float
329-
:param cashflow_series_start_year: The starting year of the cashflow series used to calculate NPV
330-
:type cashflow_series_start_year: float
329+
:param discount_initial_year_cashflow: Whether to discount the initial year of cashflow used to calculate NPV
330+
:type discount_initial_year_cashflow: bool
331331
332332
:return: NPV: The net present value of the project in MUSD
333333
:rtype: float
@@ -343,12 +343,7 @@ def CalculateFinancialPerformance(plantlifetime: int,
343343

344344
cashflow_series = TotalRevenue.copy()
345345

346-
if cashflow_series_start_year not in [0., 1.]:
347-
param_name = 'Cashflow Series Start Year' # TODO reference name defined in parameter dict
348-
raise NotImplementedError(f'Unsupported value for {param_name}: {cashflow_series_start_year}')
349-
350-
351-
if cashflow_series_start_year == 1.:
346+
if discount_initial_year_cashflow:
352347
cashflow_series = [0, *cashflow_series]
353348

354349
NPV = npf.npv(FixedInternalRate / 100, cashflow_series)
@@ -875,16 +870,22 @@ def __init__(self, model: Model):
875870
"will be automatically set to the same value."
876871
)
877872

878-
# TODO add support for float values
879-
self.cashflow_series_start_year = self.ParameterDict[self.cashflow_series_start_year.Name] = intParameter(
880-
"Cashflow Series Start Year",
881-
DefaultValue=0,
882-
AllowableRange=[0, 1],
873+
874+
self.discount_initial_year_cashflow = self.ParameterDict[self.discount_initial_year_cashflow.Name] = boolParameter(
875+
'Discount Initial Year Cashflow',
876+
DefaultValue=False,
883877
UnitType=Units.NONE,
884-
ErrMessage=f'assume default Cashflow Series Start Year ({0})',
885-
ToolTipText="Cashflow Series Start Year used to calculate NPV"
886-
# "in the Standard Levelized Cost Model." # (?)
887-
# TODO documentation re: values/conventions
878+
ToolTipText='Whether to discount cashflow in the initial project year when calculating NPV. '
879+
'The default value of False conforms to NREL\'s standard convention for NPV calculation '
880+
'(Short W et al, 1995. https://www.nrel.gov/docs/legosti/old/5173.pdf). '
881+
'A value of True will, by contrast, cause NPV calculation to follow the convention used by '
882+
'Excel, Google Sheets, and other common spreadsheet software. '
883+
'Although NREL\'s NPV convention may typically be considered more technically correct '
884+
'from a financial mathematics perspective, '
885+
'Excel-style NPV calculation may be preferred by some for familiarity, '
886+
'compliance with de-facto organizational standards, or comparison of '
887+
'GEOPHIRES with independent financial calculations. '
888+
'See https://github.com/NREL/GEOPHIRES-X/discussions/344 for discussion and further details.'
888889
)
889890

890891
self.FIB = self.ParameterDict[self.FIB.Name] = floatParameter(
@@ -2916,7 +2917,7 @@ def Calculate(self, model: Model) -> None:
29162917
self.TotalCummRevenue.value,
29172918
self.CCap.value,
29182919
self.Coam.value,
2919-
self.cashflow_series_start_year.value
2920+
self.discount_initial_year_cashflow.value
29202921
)
29212922

29222923
# Calculate the project payback period

src/geophires_x/Parameter.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ class boolParameter(Parameter):
144144

145145
def __post_init__(self):
146146
if self.value is None:
147-
self.value:bool = self.DefaultValue
147+
self.value: bool = self.DefaultValue
148148

149149
value: bool = None
150150
DefaultValue: bool = value

tests/geophires_x_tests/test_economics.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,15 +45,15 @@ def test_npv(self):
4545

4646
rate = 0.12
4747

48-
def calc_npv(total_revenue, cashflow_series_start_year=0):
48+
def calc_npv(total_revenue, discount_initial_year_cashflow=False):
4949
NPV, IRR, VIR, MOIC = CalculateFinancialPerformance(
5050
len(total_revenue) + 1,
5151
rate * 100,
5252
total_revenue,
5353
EconomicsTestCase.cumm_revenue(total_revenue),
5454
total_revenue[0],
5555
10,
56-
cashflow_series_start_year=cashflow_series_start_year,
56+
discount_initial_year_cashflow=discount_initial_year_cashflow,
5757
)
5858

5959
return NPV
@@ -73,7 +73,7 @@ def calc_npv(total_revenue, cashflow_series_start_year=0):
7373
excel_npv = npf.npv(rate, [0, *cashflow_series])
7474
self.assertEqual(1188.44, round(excel_npv, 2))
7575

76-
geophires_npv = calc_npv(cashflow_series, cashflow_series_start_year=1)
76+
geophires_npv = calc_npv(cashflow_series, discount_initial_year_cashflow=True)
7777
self.assertEqual(1188.44, round(geophires_npv, 2))
7878

7979
def test_well_drilling_cost_correlation_tooltiptext(self):

tests/test_geophires_x.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -590,24 +590,24 @@ def assertHasLogRecordWithMessage(logs_, message):
590590
logs2, 'Set Discount Rate to 0.042 because Fixed Internal Rate was provided (4.2 percent)'
591591
)
592592

593-
def test_cashflow_series_start_year(self):
594-
def _get_result(series_start_year: int) -> GeophiresXResult:
593+
def test_discount_initial_year_cashflow(self):
594+
def _get_result(do_discount: bool) -> GeophiresXResult:
595595
return GeophiresXClient().get_geophires_result(
596596
GeophiresInputParameters(
597597
# TODO switch over to generic EGS case to avoid thrash from example updates
598598
# from_file_path=self._get_test_file_path('geophires_x_tests/generic-egs-case.txt'),
599599
from_file_path=self._get_test_file_path('examples/Fervo_Project_Cape-3.txt'),
600600
params={
601-
'Cashflow Series Start Year': series_start_year,
601+
'Discount Initial Year Cashflow': do_discount,
602602
},
603603
)
604604
)
605605

606606
def _npv(r: GeophiresXResult) -> dict:
607607
return r.result['ECONOMIC PARAMETERS']['Project NPV']['value']
608608

609-
self.assertEqual(4580.36, _npv(_get_result(0)))
610-
self.assertEqual(4280.71, _npv(_get_result(1)))
609+
self.assertEqual(4580.36, _npv(_get_result(False)))
610+
self.assertEqual(4280.71, _npv(_get_result(True)))
611611

612612
def test_transmission_pipeline_cost(self):
613613
result = GeophiresXClient().get_geophires_result(

0 commit comments

Comments
 (0)