Skip to content

Commit 7d86cd7

Browse files
Merge pull request #47 from softwareengineerprogrammer/sdac-output-fixes
SDAC output fixes
2 parents bb947c6 + 2eed94d commit 7d86cd7

File tree

10 files changed

+135
-53
lines changed

10 files changed

+135
-53
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/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

tests/examples/S-DAC-GT.out

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

55
Simulation Metadata
66
----------------------
7-
GEOPHIRES Version: 3.7.1
8-
Simulation Date: 2025-01-22
9-
Simulation Time: 10:48
10-
Calculation Time: 0.107 sec
7+
GEOPHIRES Version: 3.7.15
8+
Simulation Date: 2025-02-27
9+
Simulation Time: 10:36
10+
Calculation Time: 0.100 sec
1111

1212
***SUMMARY OF RESULTS***
1313

@@ -246,31 +246,31 @@ ________________________________________________________________________________
246246

247247

248248

249-
***S_DAC_GT ECONOMICS***
249+
***S-DAC-GT ECONOMICS***
250250

251251

252252
S-DAC-GT Report: Levelized Cost of Direct Air Capture (LCOD)
253-
Using grid-based electricity only: 387.69 USD/tonne
254-
Using natural gas only: 312.00 USD/tonne
255-
Using geothermal energy only: 288.87 USD/tonne
253+
LCOD using grid-based electricity only: 387.69 USD/tonne
254+
LCOD using natural gas only: 312.00 USD/tonne
255+
LCOD using geothermal energy only: 288.87 USD/tonne
256256

257257
S-DAC-GT Report: CO2 Intensity of process (percent of CO2 mitigated that is emitted by S-DAC process)
258-
Using grid-based electricity only: 94.52%
259-
Using natural gas only: 64.85%
260-
Using geothermal energy only: 36.91%
258+
CO2 Intensity using grid-based electricity only: 94.52 %
259+
CO2 Intensity using natural gas only: 64.85 %
260+
CO2 Intensity using geothermal energy only: 36.91 %
261261

262262
Geothermal LCOH: 0.0017 USD/kWh
263-
Geothermal Ratio (electricity vs heat): 20.7259%
264-
Percent Energy Devoted To Process: 50.0000%
263+
Geothermal Ratio (electricity vs heat): 20.7259 %
264+
Percent Energy Devoted To Process: 50.0000 %
265265

266266
Total Tonnes of CO2 Captured: 2,246,284.10 tonne
267267
Total Cost of Capture: 499,311,405.59 USD
268268

269269

270270
**********************
271-
* S_DAC_GT PROFILE *
271+
* S-DAC-GT PROFILE *
272272
**********************
273-
Year Carbon Cumm. Carbon S_DAC_GT S_DAC_GT Cumm. Cumm. Cost
273+
Year Carbon Cumm. Carbon S-DAC-GT S-DAC-GT Cumm. Cumm. Cost
274274
Since Captured Captured Annual Cost Cash Flow Cost Per Tonne
275275
Start (tonne/yr) (tonne) (USD/yr) (USD) (USD/tonne)
276276
1 78,330.80 78,330.80 17,411,627.98 17,411,627.98 222.28

tests/test_geophires_x_client.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -528,3 +528,38 @@ def test_parse_chp_percent_cost_allocation(self):
528528
def test_parse_annualized_capital_costs(self):
529529
result = GeophiresXResult(self._get_test_file_path('examples/example1_addons.out'))
530530
self.assertIsNotNone(result.result['CAPITAL COSTS (M$)']['Annualized capital costs']['value'])
531+
532+
def test_parse_number_with_commas(self):
533+
result = GeophiresXResult(self._get_test_file_path('examples/S-DAC-GT.out'))
534+
sdac_e = result.result['S-DAC-GT ECONOMICS']
535+
self.assertAlmostEqualWithinPercentage(499_311_405.59, sdac_e['Total Cost of Capture']['value'])
536+
537+
self.assertAlmostEqualWithinPercentage(0.0017, sdac_e['Geothermal LCOH']['value'])
538+
539+
self.assertAlmostEqualWithinPercentage(20.7259, sdac_e['Geothermal Ratio (electricity vs heat)']['value'])
540+
541+
def test_parse_sdacgt_profile(self):
542+
result = GeophiresXResult(self._get_test_file_path('examples/S-DAC-GT.out'))
543+
sdacgt_profile = result.result['S-DAC-GT PROFILE']
544+
self.assertIsNotNone(sdacgt_profile)
545+
self.assertEqual(
546+
sdacgt_profile[0],
547+
[
548+
'Year Since Start',
549+
'Carbon Captured (tonne/yr)',
550+
'Cumm. Carbon Captured (tonne)',
551+
'S-DAC-GT Annual Cost (USD/yr)',
552+
'S-DAC-GT Cumm. Cash Flow (USD)',
553+
'Cumm. Cost Per Tonne (USD/tonne)',
554+
],
555+
)
556+
557+
# Values below need to be synchronized if S-DAC-GT example output values change.
558+
self.assertEqual(sdacgt_profile[1], [1, 78330.8, 78330.8, 17411627.98, 17411627.98, 222.28])
559+
560+
self.assertEqual(
561+
sdacgt_profile[15],
562+
[15, 76263.89, 1167207.48, 16952186.81, 259450710.33, 222.28],
563+
)
564+
565+
self.assertEqual(sdacgt_profile[30], [30, 61974.61, 2246284.1, 13775920.11, 499311405.59, 222.28])

0 commit comments

Comments
 (0)