Skip to content

Commit 21b75ef

Browse files
Merge pull request NREL#339 from softwareengineerprogrammer/main
S-DAC result fields & profile fixes [v3.7.16]
2 parents 4eb9734 + 7d86cd7 commit 21b75ef

File tree

12 files changed

+144
-56
lines changed

12 files changed

+144
-56
lines changed

.bumpversion.cfg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[bumpversion]
2-
current_version = 3.7.13
2+
current_version = 3.7.16
33
commit = True
44
tag = True
55

.cookiecutterrc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ default_context:
5454
sphinx_doctest: "no"
5555
sphinx_theme: "sphinx-py3doc-enhanced-theme"
5656
test_matrix_separate_coverage: "no"
57-
version: 3.7.13
57+
version: 3.7.16
5858
version_manager: "bump2version"
5959
website: "https://github.com/NREL"
6060
year_from: "2023"

README.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,9 @@ Free software: `MIT license <LICENSE>`__
5656
:alt: Supported implementations
5757
:target: https://pypi.org/project/geophires-x
5858

59-
.. |commits-since| image:: https://img.shields.io/github/commits-since/softwareengineerprogrammer/GEOPHIRES-X/v3.7.13.svg
59+
.. |commits-since| image:: https://img.shields.io/github/commits-since/softwareengineerprogrammer/GEOPHIRES-X/v3.7.16.svg
6060
:alt: Commits since latest release
61-
:target: https://github.com/softwareengineerprogrammer/GEOPHIRES-X/compare/v3.7.13...main
61+
:target: https://github.com/softwareengineerprogrammer/GEOPHIRES-X/compare/v3.7.16...main
6262

6363
.. |docs| image:: https://readthedocs.org/projects/GEOPHIRES-X/badge/?style=flat
6464
:target: https://nrel.github.io/GEOPHIRES-X

docs/conf.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
year = '2025'
1919
author = 'NREL'
2020
copyright = f'{year}, {author}'
21-
version = release = '3.7.13'
21+
version = release = '3.7.16'
2222

2323
pygments_style = 'trac'
2424
templates_path = ['./templates']

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ def read(*names, **kwargs):
1313

1414
setup(
1515
name='geophires-x',
16-
version='3.7.13',
16+
version='3.7.16',
1717
license='MIT',
1818
description='GEOPHIRES is a free and open-source geothermal techno-economic simulator.',
1919
long_description='{}\n{}'.format(

src/geophires_x/Economics.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1221,7 +1221,13 @@ def __init__(self, model: Model):
12211221
Max=100,
12221222
UnitType=Units.ENERGYCOST,
12231223
PreferredUnits=EnergyCostUnit.DOLLARSPERKWH,
1224-
CurrentUnits=EnergyCostUnit.DOLLARSPERKWH
1224+
CurrentUnits=EnergyCostUnit.DOLLARSPERKWH,
1225+
ToolTipText="The maximum price to which the electricity sale price can escalate. For example, if "
1226+
"Starting Electricity Sale Price = 0.10 USD/kWh and Electricity Escalation Rate = "
1227+
"0.01 USD/kWh/yr: Electricity Price will reach 0.15 USD/kWh after 4 years of escalation. "
1228+
"The price will then remain at 0.15 USD/kWh for the remaining years of the project lifetime. "
1229+
"If the Ending Electricity Sale Price is not reached by escalation during the project "
1230+
"lifetime, then the value will have no effect beyond allowing escalation to occur every year."
12251231
)
12261232
self.ElecEscalationStart = self.ParameterDict[self.ElecEscalationStart.Name] = intParameter(
12271233
"Electricity Escalation Start Year",
@@ -1541,7 +1547,7 @@ def __init__(self, model: Model):
15411547
CurrentUnits=EnergyCostUnit.DOLLARSPERMMBTU
15421548
) # $/MMBTU
15431549
self.Cstim = self.OutputParameterDict[self.Cstim.Name] = OutputParameter(
1544-
Name="O&M Surface Plant costs",
1550+
Name="O&M Surface Plant costs", # FIXME wrong name - should be Stimulation Costs
15451551
UnitType=Units.CURRENCY,
15461552
PreferredUnits=CurrencyUnit.MDOLLARS,
15471553
CurrentUnits=CurrencyUnit.MDOLLARS

src/geophires_x/OutputsS_DAC_GT.py

Lines changed: 38 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -27,35 +27,41 @@ def PrintOutputs(self, model) -> tuple:
2727
sdac_results: list[OutputTableItem] = []
2828
f.write(NL)
2929
f.write(NL)
30-
f.write(" ***S_DAC_GT ECONOMICS***" + NL)
30+
f.write(" ***S-DAC-GT ECONOMICS***\n")
3131
f.write(NL)
3232
f.write(NL)
33-
f.write(f" S-DAC-GT Report: Levelized Cost of Direct Air Capture (LCOD)" + NL)
33+
34+
msdac = model.sdacgteconomics
35+
36+
f.write(f" S-DAC-GT Report: Levelized Cost of Direct Air Capture (LCOD)\n")
3437
sdac_results.append(OutputTableItem('S-DAC-GT Report: Levelized Cost of Direct Air Capture (LCOD)'))
35-
f.write(f" Using grid-based electricity only: {model.sdacgteconomics.LCOD_elec.value:10.2f} " + model.sdacgteconomics.LCOD_elec.PreferredUnits.value + NL)
36-
sdac_results.append(OutputTableItem('Using grid-based electricity only', '{0:10.2f}'.format(model.sdacgteconomics.LCOD_elec.value), model.sdacgteconomics.LCOD_elec.PreferredUnits.value))
37-
f.write(f" Using natural gas only: {model.sdacgteconomics.LCOD_ng.value:10.2f} " + model.sdacgteconomics.LCOD_ng.PreferredUnits.value + NL)
38-
sdac_results.append(OutputTableItem('Using natural gas only', '{0:10.2f}'.format(model.sdacgteconomics.LCOD_ng.value), model.sdacgteconomics.LCOD_ng.PreferredUnits.value))
39-
f.write(f" Using geothermal energy only: {model.sdacgteconomics.LCOD_geo.value:10.2f} " + model.sdacgteconomics.LCOD_geo.PreferredUnits.value + NL + NL)
40-
sdac_results.append(OutputTableItem('Using geothermal energy only', '{0:10.2f}'.format(model.sdacgteconomics.LCOD_geo.value), model.sdacgteconomics.LCOD_geo.PreferredUnits.value))
41-
f.write(f" S-DAC-GT Report: CO2 Intensity of process (percent of CO2 mitigated that is emitted by S-DAC process)" + NL)
38+
lcod_prefix = 'LCOD'
39+
f.write(f" {lcod_prefix} using grid-based electricity only: {model.sdacgteconomics.LCOD_elec.value:10.2f} {msdac.LCOD_elec.PreferredUnits.value}\n")
40+
sdac_results.append(OutputTableItem(f'Using grid-based electricity only', '{0:10.2f}'.format(msdac.LCOD_elec.value), msdac.LCOD_elec.PreferredUnits.value))
41+
f.write(f" {lcod_prefix} using natural gas only: {model.sdacgteconomics.LCOD_ng.value:10.2f} {msdac.LCOD_ng.PreferredUnits.value}\n")
42+
sdac_results.append(OutputTableItem('Using natural gas only', '{0:10.2f}'.format(model.sdacgteconomics.LCOD_ng.value), msdac.LCOD_ng.PreferredUnits.value))
43+
f.write(f" {lcod_prefix} using geothermal energy only: {model.sdacgteconomics.LCOD_geo.value:10.2f} {msdac.LCOD_geo.PreferredUnits.value}\n\n")
44+
sdac_results.append(OutputTableItem(f'Using geothermal energy only', '{0:10.2f}'.format(msdac.LCOD_geo.value), msdac.LCOD_geo.PreferredUnits.value))
45+
46+
f.write(f" S-DAC-GT Report: CO2 Intensity of process (percent of CO2 mitigated that is emitted by S-DAC process)\n")
4247
sdac_results.append(OutputTableItem('S-DAC-GT Report: CO2 Intensity of process (percent of CO2 mitigated that is emitted by S-DAC process)'))
43-
f.write(f" Using grid-based electricity only: {model.sdacgteconomics.CO2total_elec.value*100.0:10.2f}%" + NL)
44-
sdac_results.append(OutputTableItem('Using grid-based electricity only', '{0:10.2f}'.format(model.sdacgteconomics.CO2total_elec.value*100.0), '%'))
45-
f.write(f" Using natural gas only: {model.sdacgteconomics.CO2total_ng.value*100:10.2f}%" + NL)
46-
sdac_results.append(OutputTableItem('Using natural gas only', '{0:10.2f}'.format(model.sdacgteconomics.CO2total_ng.value*100.0), '%'))
47-
f.write(f" Using geothermal energy only: {model.sdacgteconomics.CO2total_geo.value*100:10.2f}%" + NL + NL)
48-
sdac_results.append(OutputTableItem('Using geothermal energy only', '{0:10.2f}'.format(model.sdacgteconomics.CO2total_geo.value*100.0), '%'))
49-
f.write(f" Geothermal LCOH: {model.sdacgteconomics.LCOH.value:10.4f} " + model.sdacgteconomics.LCOH.PreferredUnits.value + NL)
50-
sdac_results.append(OutputTableItem('Geothermal LCOH', '{0:10.4f}'.format(model.sdacgteconomics.LCOH.value), model.sdacgteconomics.LCOH.PreferredUnits.value))
51-
f.write(f" Geothermal Ratio (electricity vs heat):{model.sdacgteconomics.percent_thermal_energy_going_to_heat.value*100:10.4f}%" + NL)
52-
sdac_results.append(OutputTableItem('Geothermal Ratio (electricity vs heat)', '{0:10.4f}'.format(model.sdacgteconomics.percent_thermal_energy_going_to_heat.value*100.0), '%'))
53-
f.write(f" Percent Energy Devoted To Process: {model.sdacgteconomics.EnergySplit.value*100:10.4f}%" + NL + NL)
54-
sdac_results.append(OutputTableItem('Percent Energy Devoted To Process', '{0:10.4f}'.format(model.sdacgteconomics.EnergySplit.value*100.0), '%'))
55-
f.write(f" Total Tonnes of CO2 Captured: {model.sdacgteconomics.CarbonExtractedTotal.value:,.2f} " + model.sdacgteconomics.CarbonExtractedTotal.PreferredUnits.value + NL)
56-
sdac_results.append(OutputTableItem('Total Tonnes of CO2 Captured', '{0:,.2f}'.format(model.sdacgteconomics.CarbonExtractedTotal.value), model.sdacgteconomics.CarbonExtractedTotal.PreferredUnits.value))
57-
f.write(f" Total Cost of Capture: {model.sdacgteconomics.S_DAC_GTCummCashFlow.value[len(model.sdacgteconomics.S_DAC_GTCummCashFlow.value)-1]:,.2f} " + model.sdacgteconomics.S_DAC_GTCummCashFlow.PreferredUnits.value + NL)
58-
sdac_results.append(OutputTableItem('Total Cost of Capture', '{0:,.2f}'.format(model.sdacgteconomics.S_DAC_GTCummCashFlow.value[len(model.sdacgteconomics.S_DAC_GTCummCashFlow.value)-1]), model.sdacgteconomics.S_DAC_GTCummCashFlow.PreferredUnits.value))
48+
co2i_prefix = 'CO2 Intensity'
49+
f.write(f" {co2i_prefix} using grid-based electricity only: {msdac.CO2total_elec.value*100.0:10.2f} %\n") # TODO CurrentUnits
50+
sdac_results.append(OutputTableItem('Using grid-based electricity only', '{0:10.2f}'.format(msdac.CO2total_elec.value*100.0), '%'))
51+
f.write(f" {co2i_prefix} using natural gas only: {msdac.CO2total_ng.value*100:10.2f} %\n") # TODO CurrentUnits
52+
sdac_results.append(OutputTableItem('Using natural gas only', '{0:10.2f}'.format(msdac.CO2total_ng.value*100.0), '%'))
53+
f.write(f" {co2i_prefix} using geothermal energy only: {msdac.CO2total_geo.value*100:10.2f} %\n\n") # TODO CurrentUnits
54+
sdac_results.append(OutputTableItem('Using geothermal energy only', '{0:10.2f}'.format(msdac.CO2total_geo.value*100.0), '%'))
55+
f.write(f" Geothermal LCOH: {msdac.LCOH.value:10.4f} {msdac.LCOH.PreferredUnits.value}\n")
56+
sdac_results.append(OutputTableItem('Geothermal LCOH', '{0:10.4f}'.format(msdac.LCOH.value), msdac.LCOH.PreferredUnits.value))
57+
f.write(f" Geothermal Ratio (electricity vs heat):{msdac.percent_thermal_energy_going_to_heat.value*100:10.4f} %\n") # TODO CurrentUnits
58+
sdac_results.append(OutputTableItem('Geothermal Ratio (electricity vs heat)', '{0:10.4f}'.format(msdac.percent_thermal_energy_going_to_heat.value*100.0), '%'))
59+
f.write(f" Percent Energy Devoted To Process: {msdac.EnergySplit.value*100:10.4f} %\n\n") # TODO CurrentUnits
60+
sdac_results.append(OutputTableItem('Percent Energy Devoted To Process', '{0:10.4f}'.format(msdac.EnergySplit.value*100.0), '%'))
61+
f.write(f" Total Tonnes of CO2 Captured: {msdac.CarbonExtractedTotal.value:,.2f} {msdac.CarbonExtractedTotal.PreferredUnits.value}\n")
62+
sdac_results.append(OutputTableItem('Total Tonnes of CO2 Captured', '{0:,.2f}'.format(msdac.CarbonExtractedTotal.value), msdac.CarbonExtractedTotal.PreferredUnits.value))
63+
f.write(f" Total Cost of Capture: {msdac.S_DAC_GTCummCashFlow.value[len(msdac.S_DAC_GTCummCashFlow.value)-1]:,.2f} {msdac.S_DAC_GTCummCashFlow.PreferredUnits.value}\n")
64+
sdac_results.append(OutputTableItem('Total Cost of Capture', '{0:,.2f}'.format(msdac.S_DAC_GTCummCashFlow.value[len(msdac.S_DAC_GTCummCashFlow.value)-1]), msdac.S_DAC_GTCummCashFlow.PreferredUnits.value))
5965
f.write(NL)
6066

6167
# Build the data frame to hold the SDAC result profile
@@ -77,9 +83,9 @@ def PrintOutputs(self, model) -> tuple:
7783

7884
f.write(NL)
7985
f.write(" **********************" + NL)
80-
f.write(" * S_DAC_GT PROFILE *" + NL)
86+
f.write(" * S-DAC-GT PROFILE *" + NL)
8187
f.write(" **********************" + NL)
82-
f.write("Year Carbon Cumm. Carbon S_DAC_GT S_DAC_GT Cumm. Cumm. Cost" + NL)
88+
f.write("Year Carbon Cumm. Carbon S-DAC-GT S-DAC-GT Cumm. Cumm. Cost" + NL)
8389
f.write("Since Captured Captured Annual Cost Cash Flow Cost Per Tonne" + NL)
8490
f.write("Start ("+model.sdacgteconomics.CarbonExtractedAnnually.PreferredUnits.value +
8591
") ("+model.sdacgteconomics.S_DAC_GTCummCarbonExtracted.PreferredUnits.value +
@@ -93,11 +99,13 @@ def PrintOutputs(self, model) -> tuple:
9399

94100
except BaseException as ex:
95101
tb = sys.exc_info()[2]
102+
msg = "Error: GEOPHIRES failed to Failed to write the output file. Exiting...Line %i" % tb.tb_lineno
103+
96104
print(str(ex))
97-
print("Error: GEOPHIRES failed to Failed to write the output file. Exiting....Line %i" % tb.tb_lineno)
105+
print(msg)
98106
model.logger.critical(str(ex))
99-
model.logger.critical("Error: GEOPHIRES failed to Failed to write the output file. Exiting....Line %i" % tb.tb_lineno)
100-
sys.exit()
107+
model.logger.critical(msg)
108+
raise RuntimeError(msg, e)
101109

102110
model.logger.info(f'Complete {str(__class__)}: {__name__}')
103111

src/geophires_x/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = '3.7.13'
1+
__version__ = '3.7.16'

src/geophires_x_client/geophires_x_result.py

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,19 @@ class GeophiresXResult:
9898
'Project MOIC (including carbon credit)',
9999
'Project Payback Period (including carbon credit)',
100100
],
101+
'S-DAC-GT ECONOMICS': [
102+
# TODO S-DAC-GT Report sub-titles as string value fields
103+
'LCOD using grid-based electricity only',
104+
'LCOD using natural gas only',
105+
'LCOD using geothermal energy only',
106+
'CO2 Intensity using grid-based electricity only',
107+
'CO2 Intensity using natural gas only',
108+
'CO2 Intensity using geothermal energy only',
109+
'Geothermal LCOH',
110+
'Geothermal Ratio (electricity vs heat)',
111+
'Percent Energy Devoted To Process',
112+
'Total Cost of Capture',
113+
],
101114
'ENGINEERING PARAMETERS': [
102115
'Number of Production Wells',
103116
'Number of Injection Wells',
@@ -359,6 +372,10 @@ def __init__(self, output_file_path, logger_name=None):
359372
if ccus_profile is not None:
360373
self.result['CCUS PROFILE'] = ccus_profile
361374

375+
sdacgt_profile = self._get_sdacgt_profile()
376+
if sdacgt_profile is not None:
377+
self.result['S-DAC-GT PROFILE'] = sdacgt_profile
378+
362379
self.result['metadata'] = {'output_file_path': self.output_file_path}
363380
for metadata_field in GeophiresXResult._METADATA_FIELDS:
364381
self.result['metadata'][metadata_field] = self._get_equal_sign_delimited_field(metadata_field)
@@ -583,6 +600,27 @@ def extract_table_header(lines: list) -> list:
583600
self._logger.debug(f'Failed to get extended economic profile: {e}')
584601
return None
585602

603+
def _get_sdacgt_profile(self):
604+
def extract_table_header(lines: list) -> list:
605+
# Tried various regexy approaches to extract this programmatically but landed on hard-coding.
606+
return [
607+
'Year Since Start',
608+
'Carbon Captured (tonne/yr)',
609+
'Cumm. Carbon Captured (tonne)',
610+
'S-DAC-GT Annual Cost (USD/yr)',
611+
'S-DAC-GT Cumm. Cash Flow (USD)',
612+
'Cumm. Cost Per Tonne (USD/tonne)',
613+
]
614+
615+
try:
616+
lines = self._get_profile_lines('S-DAC-GT PROFILE')
617+
profile = [extract_table_header(lines)]
618+
profile.extend(self._extract_addons_style_table_data(lines))
619+
return profile
620+
except BaseException as e:
621+
self._logger.debug(f'Failed to get S-DAC-GT profile: {e}')
622+
return None
623+
586624
def _get_ccus_profile(self):
587625
"""
588626
FIXME TODO - transform from revenue & cashflow if present (CCUS profile replaced by revenue & cashflow
@@ -669,10 +707,11 @@ def _get_data_from_profile_lines(self, profile_lines):
669707
return data
670708

671709
def _parse_number(self, number_str, field='string') -> int | float:
672-
if number_str == 'N/A':
710+
if number_str == 'N/A' or number_str is None:
673711
return None
674712

675713
try:
714+
number_str = number_str.replace(',', '')
676715
if '.' in number_str:
677716
# TODO should probably ideally use decimal.Decimal to preserve precision,
678717
# i.e. 1.00 for USD instead of 1.0

src/geophires_x_schema_generator/geophires-request.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1956,7 +1956,7 @@
19561956
"maximum": 100
19571957
},
19581958
"Ending Electricity Sale Price": {
1959-
"description": "",
1959+
"description": "The maximum price to which the electricity sale price can escalate. For example, if Starting Electricity Sale Price = 0.10 USD/kWh and Electricity Escalation Rate = 0.01 USD/kWh/yr: Electricity Price will reach 0.15 USD/kWh after 4 years of escalation. The price will then remain at 0.15 USD/kWh for the remaining years of the project lifetime. If the Ending Electricity Sale Price is not reached by escalation during the project lifetime, then the value will have no effect beyond allowing escalation to occur every year.",
19601960
"type": "number",
19611961
"units": "USD/kWh",
19621962
"category": "Economics",

0 commit comments

Comments
 (0)