Skip to content

Commit e8efadd

Browse files
Implemement SAM economic model MOIC NREL#390
1 parent 791f56d commit e8efadd

File tree

8 files changed

+62
-35
lines changed

8 files changed

+62
-35
lines changed

src/geophires_x/Economics.py

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from geophires_x import EconomicsSam
77
from geophires_x.EconomicsSam import calculate_sam_economics, SamEconomicsCalculations
88
from geophires_x.EconomicsUtils import BuildPricingModel, wacc_output_parameter, nominal_discount_rate_parameter, \
9-
real_discount_rate_parameter, after_tax_irr_parameter
9+
real_discount_rate_parameter, after_tax_irr_parameter, moic_parameter
1010
from geophires_x.OptionList import Configuration, WellDrillingCostCorrelation, EconomicModel, EndUseOptions, PlantType, \
1111
_WellDrillingCostCorrelationCitation
1212
from geophires_x.Parameter import intParameter, floatParameter, OutputParameter, ReadParameter, boolParameter, \
@@ -1844,13 +1844,7 @@ def __init__(self, model: Model):
18441844
PreferredUnits=PercentUnit.TENTH,
18451845
CurrentUnits=PercentUnit.TENTH
18461846
)
1847-
self.ProjectMOIC = self.OutputParameterDict[self.ProjectMOIC.Name] = OutputParameter(
1848-
"Project MOIC",
1849-
ToolTipText="Project Multiple of Invested Capital",
1850-
UnitType=Units.PERCENT,
1851-
PreferredUnits=PercentUnit.TENTH,
1852-
CurrentUnits=PercentUnit.TENTH
1853-
)
1847+
self.ProjectMOIC = self.OutputParameterDict[self.ProjectMOIC.Name] = moic_parameter()
18541848
self.ProjectPaybackPeriod = self.OutputParameterDict[self.ProjectPaybackPeriod.Name] = OutputParameter(
18551849
"Project Payback Period",
18561850
UnitType=Units.TIME,
@@ -2802,13 +2796,14 @@ def Calculate(self, model: Model) -> None:
28022796
self.after_tax_irr.value = self.sam_economics_calculations.after_tax_irr.quantity().to(
28032797
convertible_unit(self.ProjectIRR.CurrentUnits)).magnitude
28042798

2805-
self.ProjectVIR.value = non_calculated_output_placeholder_val # TODO SAM VIR
2806-
self.ProjectMOIC.value = non_calculated_output_placeholder_val # TODO SAM MOIC
2799+
self.ProjectMOIC.value = self.sam_economics_calculations.moic.value
28072800

2808-
# Calculate the project payback period
2801+
# TODO SAM economic models VIR https://github.com/NREL/GEOPHIRES-X/issues/390
2802+
# self.ProjectVIR.value = non_calculated_output_placeholder_val
28092803

2804+
# Calculate the project payback period
28102805
if self.econmodel.value == EconomicModel.SAM_SINGLE_OWNER_PPA:
2811-
# TODO SAM project payback period
2806+
# TODO TODO SAM economic models Payback period https://github.com/NREL/GEOPHIRES-X/issues/390
28122807
self.ProjectPaybackPeriod.value = non_calculated_output_placeholder_val
28132808
else:
28142809
self.ProjectPaybackPeriod.value = 0.0 # start by assuming the project never pays back

src/geophires_x/EconomicsSam.py

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
wacc_output_parameter,
3535
nominal_discount_rate_parameter,
3636
after_tax_irr_parameter,
37+
moic_parameter,
3738
)
3839
from geophires_x.GeoPHIRESUtils import is_float, is_int
3940
from geophires_x.OptionList import EconomicModel, EndUseOptions
@@ -72,6 +73,8 @@ class SamEconomicsCalculations:
7273

7374
wacc: OutputParameter = field(default_factory=wacc_output_parameter)
7475

76+
moic: OutputParameter = field(default_factory=moic_parameter)
77+
7578

7679
def validate_read_parameters(model: Model):
7780
def _inv_msg(param_name: str, invalid_value: Any, supported_description: str) -> str:
@@ -171,20 +174,16 @@ def sf(_v: float, num_sig_figs: int = 5) -> float:
171174
model, single_owner
172175
)
173176

177+
sam_economics.moic.value = _calculate_moic(cash_flow, model)
178+
174179
return sam_economics
175180

176181

177182
def _get_after_tax_irr_pct(single_owner: Singleowner, cash_flow: list[list[Any]], model: Model) -> float:
178183
after_tax_irr_pct = single_owner.Outputs.project_return_aftertax_irr
179184
if math.isnan(after_tax_irr_pct):
180185
try:
181-
182-
def cash_flow_profile_row(row_name: str) -> list[Any]:
183-
return next( # type: ignore[no-any-return]
184-
row for row in cash_flow if len(row) > 0 and row[0] == row_name
185-
)[1:]
186-
187-
after_tax_returns_cash_flow = cash_flow_profile_row('Total after-tax returns ($)')
186+
after_tax_returns_cash_flow = _cash_flow_profile_row(cash_flow, 'Total after-tax returns ($)')
188187
after_tax_irr_pct = npf.irr(after_tax_returns_cash_flow) * 100.0
189188
model.logger.info(f'After-tax IRR was NaN, calculated with numpy-financial: {after_tax_irr_pct}%')
190189
except Exception as e:
@@ -193,6 +192,10 @@ def cash_flow_profile_row(row_name: str) -> list[Any]:
193192
return after_tax_irr_pct
194193

195194

195+
def _cash_flow_profile_row(cash_flow: list[list[Any]], row_name: str) -> list[Any]:
196+
return next(row for row in cash_flow if len(row) > 0 and row[0] == row_name)[1:] # type: ignore[no-any-return]
197+
198+
196199
def _calculate_nominal_discount_rate_and_wacc(model: Model, single_owner: Singleowner) -> tuple[float]:
197200
"""
198201
Calculation per SAM Help -> Financial Parameters -> Commercial -> Commercial Loan Parameters -> WACC
@@ -214,6 +217,18 @@ def _calculate_nominal_discount_rate_and_wacc(model: Model, single_owner: Single
214217
return nominal_discount_rate_pct, wacc_pct
215218

216219

220+
def _calculate_moic(cash_flow: list[list[Any]], model) -> float | None:
221+
try:
222+
total_capital_invested_USD = _cash_flow_profile_row(cash_flow, 'Issuance of equity ($)')[0]
223+
total_value_received_from_investment_USD = np.sum(
224+
_cash_flow_profile_row(cash_flow, 'Total pre-tax returns ($)')
225+
)
226+
return total_value_received_from_investment_USD / total_capital_invested_USD
227+
except Exception as e:
228+
model.logger.error(f'Encountered exception calculating MOIC: {e}')
229+
return None
230+
231+
217232
def get_sam_cash_flow_profile_tabulated_output(model: Model, **tabulate_kw_args) -> str:
218233
"""
219234
Note model must have already calculated economics for this to work (used in Outputs)

src/geophires_x/EconomicsUtils.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,18 @@ def BuildPricingModel(plantlifetime: int, StartPrice: float, EndPrice: float,
3737
return Price
3838

3939

40+
def moic_parameter() -> OutputParameter:
41+
return OutputParameter(
42+
"Project MOIC",
43+
ToolTipText='Project Multiple of Invested Capital. For SAM Economic Models, this is calculated as the '
44+
'cash flow profile Issuance of equity (total capital invested) divided by the sum of '
45+
'Total pre-tax returns (total value received).',
46+
UnitType=Units.PERCENT,
47+
PreferredUnits=PercentUnit.TENTH,
48+
CurrentUnits=PercentUnit.TENTH
49+
)
50+
51+
4052
def after_tax_irr_parameter() -> OutputParameter:
4153
return OutputParameter(
4254
Name='After-tax IRR',

src/geophires_x/Outputs.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -286,11 +286,13 @@ def PrintOutputs(self, model: Model):
286286
f.write(f' {irr_field_label}{irr_display_value} {irr_output_param.CurrentUnits.value}\n')
287287

288288
if econ.econmodel.value != EconomicModel.SAM_SINGLE_OWNER_PPA:
289-
# VIR, MOIC, and Payback period not currently supported by SAM economic model(s)
290-
289+
# TODO SAM economic models VIR https://github.com/NREL/GEOPHIRES-X/issues/390
291290
f.write(f' {econ.ProjectVIR.display_name}: {econ.ProjectVIR.value:10.2f}\n')
292-
f.write(f' {econ.ProjectMOIC.display_name}: {econ.ProjectMOIC.value:10.2f}\n')
293291

292+
f.write(f' {econ.ProjectMOIC.display_name}: {econ.ProjectMOIC.value:10.2f}\n')
293+
294+
if econ.econmodel.value != EconomicModel.SAM_SINGLE_OWNER_PPA:
295+
# TODO TODO SAM economic models Payback period https://github.com/NREL/GEOPHIRES-X/issues/390
294296
payback_period_val = model.economics.ProjectPaybackPeriod.value
295297
project_payback_period_display = f'{payback_period_val:10.2f} {econ.ProjectPaybackPeriod.PreferredUnits.value}' \
296298
if payback_period_val > 0.0 else 'N/A'

src/geophires_x_schema_generator/geophires-result.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@
115115
},
116116
"Project MOIC": {
117117
"type": "number",
118-
"description": "Project Multiple of Invested Capital",
118+
"description": "Project Multiple of Invested Capital. For SAM Economic Models, this is calculated as the cash flow profile Issuance of equity (total capital invested) divided by the sum of Total pre-tax returns (total value received).",
119119
"units": ""
120120
},
121121
"Fixed Charge Rate (FCR)": {},

tests/examples/Fervo_Project_Cape-4.out

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@
44

55
Simulation Metadata
66
----------------------
7-
GEOPHIRES Version: 3.9.13
8-
Simulation Date: 2025-05-30
9-
Simulation Time: 09:38
10-
Calculation Time: 1.052 sec
7+
GEOPHIRES Version: 3.9.14
8+
Simulation Date: 2025-06-03
9+
Simulation Time: 08:48
10+
Calculation Time: 1.054 sec
1111

1212
***SUMMARY OF RESULTS***
1313

@@ -33,6 +33,7 @@ Simulation Metadata
3333
Capacity factor: 90.0 %
3434
Project NPV: 869.26 MUSD
3535
After-tax IRR: 18.80 %
36+
Project MOIC: 2.83
3637
Estimated Jobs Created: 1196
3738

3839
***ENGINEERING PARAMETERS***

tests/examples/example_SAM-single-owner-PPA-2.out

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@
44

55
Simulation Metadata
66
----------------------
7-
GEOPHIRES Version: 3.9.12
8-
Simulation Date: 2025-05-29
9-
Simulation Time: 08:52
10-
Calculation Time: 0.878 sec
7+
GEOPHIRES Version: 3.9.14
8+
Simulation Date: 2025-06-03
9+
Simulation Time: 08:48
10+
Calculation Time: 0.875 sec
1111

1212
***SUMMARY OF RESULTS***
1313

@@ -33,6 +33,7 @@ Simulation Metadata
3333
Capacity factor: 90.0 %
3434
Project NPV: 2877.00 MUSD
3535
After-tax IRR: 59.73 %
36+
Project MOIC: 12.36
3637
Estimated Jobs Created: 976
3738

3839
***ENGINEERING PARAMETERS***

tests/examples/example_SAM-single-owner-PPA.out

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@
44

55
Simulation Metadata
66
----------------------
7-
GEOPHIRES Version: 3.9.12
8-
Simulation Date: 2025-05-29
9-
Simulation Time: 08:55
10-
Calculation Time: 1.052 sec
7+
GEOPHIRES Version: 3.9.14
8+
Simulation Date: 2025-06-03
9+
Simulation Time: 08:48
10+
Calculation Time: 1.039 sec
1111

1212
***SUMMARY OF RESULTS***
1313

@@ -33,6 +33,7 @@ Simulation Metadata
3333
Capacity factor: 90.0 %
3434
Project NPV: 124.91 MUSD
3535
After-tax IRR: 22.33 %
36+
Project MOIC: 4.15
3637
Estimated Jobs Created: 125
3738

3839
***ENGINEERING PARAMETERS***

0 commit comments

Comments
 (0)