Skip to content

SAM-EM Add-Ons support [v3.9.47] #91

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 36 commits into from
Jul 29, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
eb9899b
WIP - adjust econ capex/opex with add ons for SAM-EM
softwareengineerprogrammer Jul 28, 2025
490b9d8
WIP stash - adjust econ capex/opex with add-ons
softwareengineerprogrammer Jul 28, 2025
d3012f8
Don't override 'Do AddOn Calculations' automatically if it was explic…
softwareengineerprogrammer Jul 28, 2025
01a9515
model add on profit as fixed capacity payment (WIP - TODO to unit test)
softwareengineerprogrammer Jul 28, 2025
8dfa6c8
Remove erroneously-added Model import from 4361af074f275aa952f6800daf…
softwareengineerprogrammer Jul 28, 2025
11b49ff
RuntimeError instead of sys.exit in OutputsAddOns.py
softwareengineerprogrammer Jul 28, 2025
130e4b4
update inaccurate OutputsAddOns return type doc
softwareengineerprogrammer Jul 28, 2025
f112096
Move OutputTableItem to separate OutputsUtils.py in preparation for r…
softwareengineerprogrammer Jul 28, 2025
f79a2da
Extract calls to AddOns & S-DAC PrintOutputs from OutputsRich to Outputs
softwareengineerprogrammer Jul 28, 2025
539b474
Consolidate Outputs SAM-EM checks into is_sam_econ_model
softwareengineerprogrammer Jul 28, 2025
77cd6ae
Don't print extended economics for SAM-EM Add-ons. Add example_SAM-si…
softwareengineerprogrammer Jul 28, 2025
faeae03
raise NotImplementedError for AddOn Electricity/Heat for SAM-EM
softwareengineerprogrammer Jul 28, 2025
61455f3
Print Total Add-On CAPEX in Capital Costs for SAM-EM
softwareengineerprogrammer Jul 28, 2025
19ec4bb
Print Add-on OPEX in opex category for SAM-EM
softwareengineerprogrammer Jul 28, 2025
e6224d9
result fields & unit test/code cleanup
softwareengineerprogrammer Jul 28, 2025
989efeb
Assert AddOn Electricity/Heat are not supported (for now)
softwareengineerprogrammer Jul 28, 2025
1a3d124
Document SAM-EM Add-ons support
softwareengineerprogrammer Jul 28, 2025
216673f
OutputsAddOns py38 compatibility
softwareengineerprogrammer Jul 28, 2025
86b6821
Bump version: 3.9.43 → 3.9.44
softwareengineerprogrammer Jul 28, 2025
92b18cc
Add example_SAM-single-owner-PPA-3 to README examples list
softwareengineerprogrammer Jul 28, 2025
6e56734
Schema & CSV test update
softwareengineerprogrammer Jul 28, 2025
77dc22c
Ensure fields that may occur in multiple categories are only parsed i…
softwareengineerprogrammer Jul 29, 2025
762564b
test that Average Net Electricity Production/Generation appear in exp…
softwareengineerprogrammer Jul 29, 2025
da41556
Merge branch 'client-result-multicategory-fields-fix' into sam-em-add…
softwareengineerprogrammer Jul 29, 2025
1e6f1b4
AddOn CAPEX/OPEX total OutputParameters
softwareengineerprogrammer Jul 29, 2025
e9a277c
Clarify SAM-EM Add-ons limitations. Deeplink to add-ons section of do…
softwareengineerprogrammer Jul 29, 2025
78b0726
Bump version: 3.9.44 → 3.9.45
softwareengineerprogrammer Jul 29, 2025
555ebb8
CHANGELOG entry
softwareengineerprogrammer Jul 29, 2025
e5e9bb2
Search all lines for AGS/CLGS-style output, since these are currently…
softwareengineerprogrammer Jul 29, 2025
665aa1c
Bump version: 3.9.45 → 3.9.46
softwareengineerprogrammer Jul 29, 2025
0e38d57
sync changelog entry to v3.9.46
softwareengineerprogrammer Jul 29, 2025
f03097e
Parse SUTRA Reservoir Model in SUMMARY OF RESULTS (was previously bei…
softwareengineerprogrammer Jul 29, 2025
3e18f62
regenerate schema to sync with previous commit
softwareengineerprogrammer Jul 29, 2025
392898c
Bump version: 3.9.46 → 3.9.47
softwareengineerprogrammer Jul 29, 2025
a04c772
sync v3.9.47 CHANGELOG entry
softwareengineerprogrammer Jul 29, 2025
ff7ca58
CHANGELOG entry in reverse chronological order (for consistency)
softwareengineerprogrammer Jul 29, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .bumpversion.cfg
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[bumpversion]
current_version = 3.9.43
current_version = 3.9.47
commit = True
tag = True

Expand Down
2 changes: 1 addition & 1 deletion .cookiecutterrc
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ default_context:
sphinx_doctest: "no"
sphinx_theme: "sphinx-py3doc-enhanced-theme"
test_matrix_separate_coverage: "no"
version: 3.9.43
version: 3.9.47
version_manager: "bump2version"
website: "https://github.com/NREL"
year_from: "2023"
Expand Down
3 changes: 2 additions & 1 deletion CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ GEOPHIRES v3 (2023-2025)

`release <https://github.com/NREL/GEOPHIRES-X/releases/tag/v3.9.7>`__

v3.9 adds the `SAM Single Owner PPA Economic Model <https://nrel.github.io/GEOPHIRES-X/SAM-Economic-Models.html>`__
v3.9.47 adds `Add-Ons support for SAM Economic Models <https://nrel.github.io/GEOPHIRES-X/SAM-Economic-Models.html#add-ons>`__

v3.9 adds the `SAM Single Owner PPA Economic Model <https://nrel.github.io/GEOPHIRES-X/SAM-Economic-Models.html>`__

3.8
^^^
Expand Down
9 changes: 6 additions & 3 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,9 @@ Free software: `MIT license <LICENSE>`__
:alt: Supported implementations
:target: https://pypi.org/project/geophires-x

.. |commits-since| image:: https://img.shields.io/github/commits-since/softwareengineerprogrammer/GEOPHIRES-X/v3.9.43.svg
.. |commits-since| image:: https://img.shields.io/github/commits-since/softwareengineerprogrammer/GEOPHIRES-X/v3.9.47.svg
:alt: Commits since latest release
:target: https://github.com/softwareengineerprogrammer/GEOPHIRES-X/compare/v3.9.43...main
:target: https://github.com/softwareengineerprogrammer/GEOPHIRES-X/compare/v3.9.47...main

.. |docs| image:: https://readthedocs.org/projects/GEOPHIRES-X/badge/?style=flat
:target: https://nrel.github.io/GEOPHIRES-X
Expand Down Expand Up @@ -308,7 +308,10 @@ Example-specific web interface deeplinks are listed in the Link column.
- `example_SAM-single-owner-PPA-2.txt <tests/examples/example_SAM-single-owner-PPA-2.txt>`__
- `.out <tests/examples/example_SAM-single-owner-PPA-2.out>`__
- `link <https://gtp.scientificwebservices.com/geophires?geophires-example-id=example_SAM-single-owner-PPA-2>`__

* - SAM Single Owner PPA: 50 MWe with Add-on
- `example_SAM-single-owner-PPA-3.txt <tests/examples/example_SAM-single-owner-PPA-3.txt>`__
- `.out <tests/examples/example_SAM-single-owner-PPA-3.out>`__
- `link <https://gtp.scientificwebservices.com/geophires?geophires-example-id=example_SAM-single-owner-PPA-3>`__
.. raw:: html

<embed>
Expand Down
16 changes: 15 additions & 1 deletion docs/SAM-Economic-Models.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ The following table describes how GEOPHIRES parameters are transformed into SAM
| `Fraction of Investment in Bonds` | Financial Parameters → Project Term Debt | `Debt percent` | `Singleowner` | `debt_percent` | .. N/A |
| `Inflated Bond Interest Rate` | Financial Parameters → Project Term Debt | `Annual interest rate` | `Singleowner` | `term_int_rate` | .. N/A |
| `Starting Electricity Sale Price`, `Ending Electricity Sale Price`, `Electricity Escalation Rate Per Year`, `Electricity Escalation Start Year` | Revenue | `PPA price` | `Singleowner` | `ppa_price_input` | GEOPHIRES's pricing model is used to create a PPA price schedule that is passed to SAM. |
| `Total AddOn Profit Gained` | Revenue → Capacity Payments | `Fixed amount`, `Capacity payment amount` | `Singleowner` | `cp_capacity_payment_type = 1`, `cp_capacity_payment_amount` | |
| `Investment Tax Credit Rate` | Incentives → Investment Tax Credit (ITC) | `Federal` → `Percentage (%)` | `Singleowner` | `itc_fed_percent` | Note that unlike the BICYCLE Economic Model's `Total capital costs`, SAM Economic Model's `Total CAPEX` is the total installed cost and does not subtract ITC value (if present). |
| `Production Tax Credit Electricity` | Incentives → Production Tax Credit (PTC) | `Federal` → `Amount ($/kWh)` | `Singleowner` | `ptc_fed_amount` | .. N/A |
| `Production Tax Credit Duration` | Incentives → Production Tax Credit (PTC) | `Federal` → `Term (years)` | `Singleowner` | `ptc_fed_term` | .. N/A |
Expand All @@ -48,7 +49,9 @@ The following table describes how GEOPHIRES parameters are transformed into SAM
### Limitations

1. Only Electricity end-use is supported
2. Only 1 construction year is supported. Note that the `Inflation Rate During Construction` parameter can be used to partially account for longer construction periods.
2. Only 1 construction year is supported. Note that the `Inflation Rate During Construction` parameter can be used to
partially account for longer construction periods.
3. Add-ons with electricity and heat are not currently supported. (Add-ons CAPEX, OPEX, and profit are supported.)

## Using SAM Economic Models with Existing GEOPHIRES Inputs

Expand Down Expand Up @@ -135,6 +138,17 @@ You can then manually enter the parameters from the logged mapping into the SAM

![](sam-desktop-app-manually-enter-system-capacity-from-geophires-log.png)

## Add-Ons

SAM Economic Models incorporate add-ons directly, unlike other GEOPHIRES economic models, which calculate separate
extended economics.
Total Add-on CAPEX is added to Total CAPEX.
Total Add-on OPEX is added to Total operating and maintenance costs.
Total AddOn Profit Gained per year is treated as fixed amount Capacity payment revenue.

Add-ons CAPEX, OPEX, and profit are supported.
Add-ons with electricity and heat are not currently supported, but may be supported in the future.

## Examples

### SAM Single Owner PPA: 50 MWe
Expand Down
2 changes: 1 addition & 1 deletion docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
year = '2025'
author = 'NREL'
copyright = f'{year}, {author}'
version = release = '3.9.43'
version = release = '3.9.47'

pygments_style = 'trac'
templates_path = ['./templates']
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ def read(*names, **kwargs):

setup(
name='geophires-x',
version='3.9.43',
version='3.9.47',
license='MIT',
description='GEOPHIRES is a free and open-source geothermal techno-economic simulator.',
long_description='{}\n{}'.format(
Expand Down
2 changes: 1 addition & 1 deletion src/geophires_x/Economics.py
Original file line number Diff line number Diff line change
Expand Up @@ -2334,7 +2334,7 @@ def _warn(_msg: str) -> None:

# we can determine on-the-fly if Addons, CCUS, or S-DAC-GT are being used in the user input file
for key in model.InputParameters.keys():
if key.startswith("AddOn"):
if key.startswith("AddOn") and not self.DoAddOnCalculations.Provided:
self.DoAddOnCalculations.value = True
break

Expand Down
30 changes: 26 additions & 4 deletions src/geophires_x/EconomicsAddOns.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import numpy_financial as npf
import geophires_x.Economics as Economics
import geophires_x.Model as Model
from geophires_x.OptionList import EndUseOptions
from geophires_x.OptionList import EndUseOptions, EconomicModel
from geophires_x.Parameter import listParameter, OutputParameter
from geophires_x.Units import *

Expand Down Expand Up @@ -108,12 +108,14 @@ def multi_addon_tooltip_text(param_name: str) -> str:
# results
self.AddOnCAPEXTotal = self.OutputParameterDict[self.AddOnCAPEXTotal.Name] = OutputParameter(
"AddOn CAPEX Total",
display_name='Total Add-on CAPEX',
UnitType=Units.CURRENCY,
PreferredUnits=CurrencyUnit.MDOLLARS,
CurrentUnits=CurrencyUnit.MDOLLARS,
)
self.AddOnOPEXTotalPerYear = self.OutputParameterDict[self.AddOnOPEXTotalPerYear.Name] = OutputParameter(
"AddOn OPEX Total Per Year",
display_name='Total Add-on OPEX',
UnitType=Units.CURRENCYFREQUENCY,
PreferredUnits=CurrencyFrequencyUnit.MDOLLARSPERYEAR,
CurrentUnits=CurrencyFrequencyUnit.MDOLLARSPERYEAR
Expand Down Expand Up @@ -222,6 +224,8 @@ def read_parameters(self, model: Model) -> None:
model.logger.info(f'Init {str(__class__)}: {sys._getframe().f_code.co_name}')
super().read_parameters(model) # read the parameters for the parent.

is_sam_econ_model = model.economics.econmodel.value == EconomicModel.SAM_SINGLE_OWNER_PPA

# Deal with all the parameter values that the user has provided that relate to this extension.
# super.read_parameter will have already dealt with all the regular values, but anything unusual
# may not be dealt with, so check.
Expand All @@ -244,12 +248,21 @@ def read_parameters(self, model: Model) -> None:
if key.startswith("AddOn OPEX"):
val = float(model.InputParameters[key].sValue)
self.AddOnOPEXPerYear.value.append(val) # this assumes they put the values in the file in consecutive fashion

if key.startswith("AddOn Electricity Gained"):
if is_sam_econ_model:
raise NotImplementedError('AddOn Electricity is not supported for SAM Economic Models')

val = float(model.InputParameters[key].sValue)
self.AddOnElecGainedPerYear.value.append(val) # this assumes they put the values in the file in consecutive fashion

if key.startswith("AddOn Heat Gained"):
if is_sam_econ_model:
raise NotImplementedError('AddOn Heat is not supported for SAM Economic Models')

val = float(model.InputParameters[key].sValue)
self.AddOnHeatGainedPerYear.value.append(val) # this assumes they put the values in the file in consecutive fashion

if key.startswith("AddOn Profit Gained"):
val = float(model.InputParameters[key].sValue)
self.AddOnProfitGainedPerYear.value.append(val) # this assumes they put the values in the file in consecutive fashion
Expand All @@ -271,7 +284,9 @@ def Calculate(self, model: Model) -> None:
:type model: :class:`~geophires_x.Model.Model`
:return: Nothing, but it does make calculations and set values in the model
"""
model.logger.info("Init " + str(__class__) + ": " + sys._getframe().f_code.co_name)
model.logger.info(f"Init {str(__class__)}: {sys._getframe().f_code.co_name}")

is_sam_em = model.economics.econmodel.value == EconomicModel.SAM_SINGLE_OWNER_PPA

# sum all the AddOn values together, so we can treat all AddOns together. If an AddOn slot is not used,
# it has zeros for the values, so this won't create problems
Expand Down Expand Up @@ -302,6 +317,12 @@ def Calculate(self, model: Model) -> None:
# Calculate the adjusted OPEX and CAPEX
self.AdjustedProjectCAPEX.value = model.economics.CCap.value + self.AddOnCAPEXTotal.value
self.AdjustedProjectOPEX.value = model.economics.Coam.value + self.AddOnOPEXTotalPerYear.value

if is_sam_em:
# SAM econ models incorporate add-ons into main economics, not as separate extended economics
model.economics.CCap.value = self.AdjustedProjectCAPEX.value
model.economics.Coam.value = self.AdjustedProjectOPEX.value

AddOnCapCostPerYear = self.AddOnCAPEXTotal.value / model.surfaceplant.construction_years.value
ProjectCapCostPerYear = self.AdjustedProjectCAPEX.value / model.surfaceplant.construction_years.value

Expand Down Expand Up @@ -386,8 +407,9 @@ def Calculate(self, model: Model) -> None:
self.AdjustedProjectCAPEX.value + (
self.AdjustedProjectOPEX.value * model.surfaceplant.plant_lifetime.value))

# recalculate LCOE/LCOH
self.LCOE.value, self.LCOH.value, LCOC = Economics.CalculateLCOELCOHLCOC(self, model)
if not is_sam_em:
# recalculate LCOE/LCOH
self.LCOE.value, self.LCOH.value, LCOC = Economics.CalculateLCOELCOHLCOC(self, model)

self._calculate_derived_outputs(model)
model.logger.info(f'complete {str(__class__)}: {sys._getframe().f_code.co_name}')
Expand Down
6 changes: 6 additions & 0 deletions src/geophires_x/EconomicsSam.py
Original file line number Diff line number Diff line change
Expand Up @@ -415,6 +415,12 @@ def _get_single_owner_parameters(model: Model) -> dict[str, Any]:

ret['ibi_oth_amount'] = (econ.OtherIncentives.quantity() + econ.TotalGrant.quantity()).to('USD').magnitude

if model.economics.DoAddOnCalculations.value:
add_on_profit_per_year = np.sum(model.addeconomics.AddOnProfitGainedPerYear.quantity().to('USD/yr').magnitude)
add_on_profit_series = [add_on_profit_per_year]
ret['cp_capacity_payment_amount'] = add_on_profit_series
ret['cp_capacity_payment_type'] = 1

return ret


Expand Down
1 change: 0 additions & 1 deletion src/geophires_x/Model.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import sys
from email.policy import default
from pathlib import Path
import logging
import time
Expand Down
Loading
Loading