Skip to content

Commit 49a3e7d

Browse files
Calculate and display WACC for SAM Economic Model
1 parent b475294 commit 49a3e7d

File tree

9 files changed

+76
-26
lines changed

9 files changed

+76
-26
lines changed

src/geophires_x/Economics.py

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
import numpy_financial as npf
55
import geophires_x.Model as Model
66
from geophires_x import EconomicsSam
7-
from geophires_x.EconomicsSam import calculate_sam_economics, SamEconomics
8-
from geophires_x.EconomicsUtils import BuildPricingModel
7+
from geophires_x.EconomicsSam import calculate_sam_economics, SamEconomicsCalculations
8+
from geophires_x.EconomicsUtils import BuildPricingModel, wacc_output_parameter
99
from geophires_x.OptionList import Configuration, WellDrillingCostCorrelation, EconomicModel, EndUseOptions, PlantType, \
1010
_WellDrillingCostCorrelationCitation
1111
from geophires_x.Parameter import intParameter, floatParameter, OutputParameter, ReadParameter, boolParameter, \
@@ -442,7 +442,7 @@ def CalculateLCOELCOHLCOC(econ, model: Model) -> tuple:
442442
LCOH = LCOH * 2.931 # $/Million Btu
443443
elif econ.econmodel.value == EconomicModel.SAM_SINGLE_OWNER_PPA:
444444
# Designated as nominal (as opposed to real) in parameter tooltip text
445-
LCOE = econ.sam_economics.lcoe_nominal.quantity().to(convertible_unit(econ.LCOE.CurrentUnits.value)).magnitude
445+
LCOE = econ.sam_economics_calculations.lcoe_nominal.quantity().to(convertible_unit(econ.LCOE.CurrentUnits.value)).magnitude
446446
else:
447447
# must be BICYCLE
448448
# average return on investment (tax and inflation adjusted)
@@ -852,7 +852,7 @@ def __init__(self, model: Model):
852852
PreferredUnits=PercentUnit.TENTH,
853853
CurrentUnits=PercentUnit.TENTH,
854854
ErrMessage=f'assume default discount rate ({discount_rate_default_val})',
855-
ToolTipText="Discount rate used in the Standard Levelized Cost Model. "
855+
ToolTipText="Discount rate used in the Standard Levelized Cost Model and SAM Economic Models. "
856856
"Discount Rate is synonymous with Fixed Internal Rate. If one is provided, the other's value "
857857
"will be automatically set to the same value."
858858
)
@@ -1519,7 +1519,7 @@ def __init__(self, model: Model):
15191519
self.InputFile = ""
15201520
self.Cplantcorrelation = 0.0
15211521

1522-
self.sam_economics: SamEconomics = None
1522+
self.sam_economics_calculations: SamEconomicsCalculations = None
15231523

15241524
sclass = str(__class__).replace("<class \'", "")
15251525
self.MyClass = sclass.replace("\'>", "")
@@ -1775,6 +1775,8 @@ def __init__(self, model: Model):
17751775
CurrentUnits=PercentUnit.PERCENT
17761776
)
17771777

1778+
self.wacc = self.OutputParameterDict[self.wacc.Name] = wacc_output_parameter()
1779+
17781780
# TODO this is displayed as "Project Net Revenue" in Revenue & Cashflow Profile which is probably not an
17791781
# accurate synonym for annual revenue
17801782
self.TotalRevenue = self.OutputParameterDict[self.TotalRevenue.Name] = OutputParameter(
@@ -2755,10 +2757,10 @@ def Calculate(self, model: Model) -> None:
27552757

27562758
non_calculated_output_placeholder_val = -1
27572759
if self.econmodel.value == EconomicModel.SAM_SINGLE_OWNER_PPA:
2758-
self.sam_economics = calculate_sam_economics(model)
2759-
self.ProjectNPV.value = self.sam_economics.project_npv.quantity().to(
2760+
self.sam_economics_calculations = calculate_sam_economics(model)
2761+
self.ProjectNPV.value = self.sam_economics_calculations.project_npv.quantity().to(
27602762
convertible_unit(self.ProjectNPV.CurrentUnits)).magnitude
2761-
self.ProjectIRR.value = self.sam_economics.project_irr.quantity().to(
2763+
self.ProjectIRR.value = self.sam_economics_calculations.project_irr.quantity().to(
27622764
convertible_unit(self.ProjectIRR.CurrentUnits)).magnitude
27632765

27642766
self.ProjectVIR.value = non_calculated_output_placeholder_val # TODO SAM VIR

src/geophires_x/EconomicsSam.py

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
import json
44
import os
5-
import pprint
65
from dataclasses import dataclass, field
76
from functools import lru_cache
87
from math import isnan
@@ -28,15 +27,15 @@
2827

2928
from geophires_x import Model as Model
3029
from geophires_x.EconomicsSamCashFlow import _calculate_sam_economics_cash_flow
31-
from geophires_x.EconomicsUtils import BuildPricingModel
30+
from geophires_x.EconomicsUtils import BuildPricingModel, wacc_output_parameter
3231
from geophires_x.GeoPHIRESUtils import is_float, is_int
3332
from geophires_x.OptionList import EconomicModel, EndUseOptions
3433
from geophires_x.Parameter import Parameter, OutputParameter, floatParameter
3534
from geophires_x.Units import convertible_unit, EnergyCostUnit, CurrencyUnit, Units, PercentUnit
3635

3736

3837
@dataclass
39-
class SamEconomics:
38+
class SamEconomicsCalculations:
4039
sam_cash_flow_profile: list[list[Any]]
4140

4241
lcoe_nominal: OutputParameter = field(
@@ -64,6 +63,8 @@ class SamEconomics:
6463
)
6564
)
6665

66+
wacc: OutputParameter = field(default_factory=wacc_output_parameter)
67+
6768

6869
def validate_read_parameters(model: Model):
6970
def _inv_msg(param_name: str, invalid_value: Any, supported_description: str) -> str:
@@ -107,11 +108,11 @@ def _inv_msg(param_name: str, invalid_value: Any, supported_description: str) ->
107108

108109

109110
@lru_cache(maxsize=12)
110-
def calculate_sam_economics(model: Model) -> SamEconomics:
111+
def calculate_sam_economics(model: Model) -> SamEconomicsCalculations:
111112
custom_gen = CustomGeneration.new()
112113
grid = Grid.from_existing(custom_gen)
113114
utility_rate = UtilityRate.from_existing(custom_gen)
114-
single_owner = Singleowner.from_existing(custom_gen)
115+
single_owner: Singleowner = Singleowner.from_existing(custom_gen)
115116

116117
project_name = 'Generic_400_MWe'
117118
project_dir = Path(os.path.dirname(model.economics.MyPath), 'sam_economics', project_name)
@@ -152,15 +153,35 @@ def calculate_sam_economics(model: Model) -> SamEconomics:
152153
def sf(_v: float) -> float:
153154
return _sig_figs(_v, 5)
154155

155-
sam_economics: SamEconomics = SamEconomics(sam_cash_flow_profile=cash_flow)
156+
sam_economics: SamEconomicsCalculations = SamEconomicsCalculations(sam_cash_flow_profile=cash_flow)
156157
sam_economics.lcoe_nominal.value = sf(single_owner.Outputs.lcoe_nom)
157158
sam_economics.project_irr.value = sf(single_owner.Outputs.project_return_aftertax_irr)
158159
sam_economics.project_npv.value = sf(single_owner.Outputs.project_return_aftertax_npv * 1e-6)
159160
sam_economics.capex.value = single_owner.Outputs.adjusted_installed_cost * 1e-6
160161

162+
sam_economics.wacc.value = _calculate_wacc(model, single_owner)
163+
161164
return sam_economics
162165

163166

167+
def _calculate_wacc(model: Model, single_owner: Singleowner) -> float:
168+
"""
169+
Calculation per SAM Help -> Financial Parameters -> Commercial -> Commercial Loan Parameters -> WACC
170+
"""
171+
172+
econ = model.economics
173+
nominal_discount_rate = ((1 + econ.discountrate.value) * (1 + econ.RINFL.value) - 1) * 100
174+
fed_tax_rate = max(single_owner.Outputs.cf_federal_tax_frac)
175+
state_tax_rate = max(single_owner.Outputs.cf_state_tax_frac)
176+
effective_tax_rate = (fed_tax_rate * (1 - state_tax_rate) + state_tax_rate) * 100
177+
debt_fraction = single_owner.Outputs.debt_fraction / 100
178+
wacc = (
179+
nominal_discount_rate / 100 * (1 - debt_fraction)
180+
+ debt_fraction * econ.BIR.value * (1 - effective_tax_rate / 100)
181+
) * 100
182+
return wacc
183+
184+
164185
def get_sam_cash_flow_profile_tabulated_output(model: Model, **tabulate_kw_args) -> str:
165186
"""
166187
Note model must have already calculated economics for this to work (used in Outputs)
@@ -186,7 +207,7 @@ def get_entry_display(entry: Any) -> str:
186207
return entry_display
187208
return entry
188209

189-
profile_display = model.economics.sam_economics.sam_cash_flow_profile.copy()
210+
profile_display = model.economics.sam_economics_calculations.sam_cash_flow_profile.copy()
190211
for i in range(len(profile_display)):
191212
for j in range(len(profile_display[i])):
192213
profile_display[i][j] = get_entry_display(profile_display[i][j])

src/geophires_x/EconomicsUtils.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
from __future__ import annotations
2+
3+
from geophires_x.Parameter import OutputParameter
4+
from geophires_x.Units import Units, PercentUnit
5+
6+
17
def BuildPricingModel(plantlifetime: int, StartPrice: float, EndPrice: float,
28
EscalationStartYear: int, EscalationRate: float, PTCAddition: list) -> list:
39
"""
@@ -29,3 +35,13 @@ def BuildPricingModel(plantlifetime: int, StartPrice: float, EndPrice: float,
2935
Price[i] = EndPrice
3036
Price[i] = Price[i] + PTCAddition[i]
3137
return Price
38+
39+
40+
def wacc_output_parameter() -> OutputParameter:
41+
return OutputParameter(
42+
Name='WACC',
43+
ToolTipText='Weighted Average Cost of Capital',
44+
UnitType=Units.PERCENT,
45+
CurrentUnits=PercentUnit.PERCENT,
46+
PreferredUnits=PercentUnit.PERCENT,
47+
)

src/geophires_x/Outputs.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -254,8 +254,12 @@ def PrintOutputs(self, model: Model):
254254
if model.economics.econmodel.value == EconomicModel.SAM_SINGLE_OWNER_PPA:
255255
# TODO disambiguate interest rate for all economic models - see
256256
# https://github.com/softwareengineerprogrammer/GEOPHIRES/commit/535c02d4adbeeeca553b61e9b996fccf00016529
257-
irfl = Outputs._field_label(econ.interest_rate.Name, 49)
258-
f.write(f' {irfl}{econ.interest_rate.value:10.2f} {econ.interest_rate.CurrentUnits.value}\n')
257+
ir_fl = Outputs._field_label(econ.interest_rate.Name, 49)
258+
f.write(f' {ir_fl}{econ.interest_rate.value:10.2f} {econ.interest_rate.CurrentUnits.value}\n')
259+
260+
wacc = econ.sam_economics_calculations.wacc
261+
wacc_fl = Outputs._field_label(wacc.Name, 49)
262+
f.write(f' {wacc_fl}{wacc.value:10.2f} {wacc.CurrentUnits.value}\n')
259263

260264
# FIXME TODO unit is missing https://github.com/NREL/GEOPHIRES-X/issues/382
261265
f.write(f' Accrued financing during construction: {model.economics.inflrateconstruction.value*100:10.2f} {model.economics.inflrateconstruction.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
@@ -67,6 +67,7 @@ class GeophiresXResult:
6767
'ECONOMIC PARAMETERS': [
6868
_EqualSignDelimitedField('Economic Model'),
6969
'Interest Rate', # %
70+
'WACC',
7071
'Accrued financing during construction',
7172
'Project lifetime',
7273
'Capacity factor',

src/geophires_x_schema_generator/geophires-request.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1154,7 +1154,7 @@
11541154
"maximum": 1.0
11551155
},
11561156
"Utilization Factor": {
1157-
"description": "Ratio of the time the plant is running in normal production in a 1-year time period.",
1157+
"description": "Ratio of the time the plant is running in normal production in a 1-year time period. Synonymous with capacity factor.",
11581158
"type": "number",
11591159
"units": "",
11601160
"category": "Surface Plant",
@@ -1577,7 +1577,7 @@
15771577
"maximum": 1.0
15781578
},
15791579
"Discount Rate": {
1580-
"description": "Discount rate used in the Standard Levelized Cost Model. Discount Rate is synonymous with Fixed Internal Rate. If one is provided, the other's value will be automatically set to the same value.",
1580+
"description": "Discount rate used in the Standard Levelized Cost Model and SAM Economic Models. Discount Rate is synonymous with Fixed Internal Rate. If one is provided, the other's value will be automatically set to the same value.",
15811581
"type": "number",
15821582
"units": "",
15831583
"category": "Economics",

src/geophires_x_schema_generator/geophires-result.json

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
"Average Net Electricity Production": {},
1515
"Electricity breakeven price": {
1616
"type": "number",
17-
"description": "LCOE",
17+
"description": "LCOE. For SAM economic models, this is the nominal LCOE value (as opposed to real).",
1818
"units": "cents/kWh"
1919
},
2020
"Average Direct-Use Heat Production": {},
@@ -49,7 +49,7 @@
4949
"Segment 4 Geothermal gradient": {},
5050
"LCOE": {
5151
"type": "number",
52-
"description": "LCOE",
52+
"description": "LCOE. For SAM economic models, this is the nominal LCOE value (as opposed to real).",
5353
"units": "cents/kWh"
5454
},
5555
"LCOH": {
@@ -74,6 +74,11 @@
7474
"description": "",
7575
"units": "%"
7676
},
77+
"WACC": {
78+
"type": "number",
79+
"description": "Weighted Average Cost of Capital",
80+
"units": "%"
81+
},
7782
"Accrued financing during construction": {},
7883
"Project lifetime": {},
7984
"Capacity factor": {},

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.5
8-
Simulation Date: 2025-04-24
9-
Simulation Time: 09:38
10-
Calculation Time: 0.875 sec
7+
GEOPHIRES Version: 3.9.6
8+
Simulation Date: 2025-05-14
9+
Simulation Time: 10:30
10+
Calculation Time: 0.883 sec
1111

1212
***SUMMARY OF RESULTS***
1313

@@ -25,6 +25,7 @@ Simulation Metadata
2525

2626
Economic Model = SAM Single Owner PPA
2727
Interest Rate: 7.00 %
28+
WACC: 6.41 %
2829
Accrued financing during construction: 5.00
2930
Project lifetime: 20 yr
3031
Capacity factor: 90.0 %

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ Electricity Escalation Start Year, 1
1515

1616
Fraction of Investment in Bonds, .5
1717
Inflated Bond Interest Rate, .05
18-
# Inflated Equity Interest Rate, .08, -- not supported by SAM economic model
18+
Discount Rate, 0.07
1919

2020
Inflation Rate, .02
2121
Inflation Rate During Construction, 0.05

0 commit comments

Comments
 (0)