diff --git a/.bumpversion.cfg b/.bumpversion.cfg index d6c7fdf11..601a0e96e 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 3.13.12 +current_version = 3.13.13 commit = True tag = True diff --git a/.cookiecutterrc b/.cookiecutterrc index 78bb6fa68..24c6f8146 100644 --- a/.cookiecutterrc +++ b/.cookiecutterrc @@ -54,7 +54,7 @@ default_context: sphinx_doctest: "no" sphinx_theme: "sphinx-py3doc-enhanced-theme" test_matrix_separate_coverage: "no" - version: 3.13.12 + version: 3.13.13 version_manager: "bump2version" website: "https://github.com/NREL" year_from: "2023" diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 6dbf7dd7b..438a1a9a3 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,8 @@ GEOPHIRES v3 (2023-2026) 3.13 ^^^^ +3.13.13: `Input params CSV unit and comment parsing `__ | `release `__ + 3.13.12: `Drawdown Parameter Schedule `__ | `release `__ 3.13.10: `Well integrity parameterization to trigger redrilling `__ | `release `__ diff --git a/README.rst b/README.rst index 6955b0c4f..2f0ff1e3c 100644 --- a/README.rst +++ b/README.rst @@ -58,9 +58,9 @@ Free software: `MIT 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.13.12.svg +.. |commits-since| image:: https://img.shields.io/github/commits-since/softwareengineerprogrammer/GEOPHIRES-X/v3.13.13.svg :alt: Commits since latest release - :target: https://github.com/softwareengineerprogrammer/GEOPHIRES-X/compare/v3.13.12...main + :target: https://github.com/softwareengineerprogrammer/GEOPHIRES-X/compare/v3.13.13...main .. |docs| image:: https://readthedocs.org/projects/GEOPHIRES-X/badge/?style=flat :target: https://softwareengineerprogrammer.github.io/GEOPHIRES diff --git a/docs/conf.py b/docs/conf.py index 74001604b..a1332b862 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -18,7 +18,7 @@ year = '2025' author = 'NREL' copyright = f'{year}, {author}' -version = release = '3.13.12' +version = release = '3.13.13' pygments_style = 'trac' templates_path = ['./templates'] diff --git a/setup.py b/setup.py index 72bae6292..2cb7f970f 100755 --- a/setup.py +++ b/setup.py @@ -13,7 +13,7 @@ def read(*names, **kwargs): setup( name='geophires-x', - version='3.13.12', + version='3.13.13', license='MIT', description='GEOPHIRES is a free and open-source geothermal techno-economic simulator.', long_description='{}\n{}'.format( diff --git a/src/geophires_docs/__init__.py b/src/geophires_docs/__init__.py index 11764c27d..5c02a1600 100644 --- a/src/geophires_docs/__init__.py +++ b/src/geophires_docs/__init__.py @@ -8,6 +8,7 @@ from pint.facets.plain import PlainQuantity +from geophires_x.ParameterUtils import COMMENT_PARAMETER_NAME_PREFIX from geophires_x_client import GeophiresInputParameters from geophires_x_client import GeophiresXClient from geophires_x_client import GeophiresXResult @@ -77,7 +78,7 @@ def _get_input_parameters_dict( # TODO consolidate with FervoProjectCape5TestCa ret[field] = fieldValue.strip() if include_line_comments and field.startswith('#'): - ret[f'_COMMENT-{comment_idx}'] = line.strip() + ret[f'{COMMENT_PARAMETER_NAME_PREFIX}{comment_idx}'] = line.strip() comment_idx += 1 # TODO preserve newlines diff --git a/src/geophires_docs/generate_fervo_project_cape_5_md.py b/src/geophires_docs/generate_fervo_project_cape_5_md.py index e7c9743cd..ba0a2d3d1 100755 --- a/src/geophires_docs/generate_fervo_project_cape_5_md.py +++ b/src/geophires_docs/generate_fervo_project_cape_5_md.py @@ -24,6 +24,7 @@ from geophires_docs import _get_project_root from geophires_x.GeoPHIRESUtils import is_int from geophires_x.GeoPHIRESUtils import sig_figs +from geophires_x.ParameterUtils import COMMENT_PARAMETER_NAME_PREFIX from geophires_x_client import GeophiresInputParameters from geophires_x_client import GeophiresXResult from geophires_x_client import ImmutableGeophiresInputParameters @@ -219,7 +220,7 @@ def get_fpc_category_parameters_table_md( table_entries = [] for param_name, param_val_comment in input_params_dict.items(): - if param_name.startswith(('#', '_COMMENT-')): + if param_name.startswith(('#', COMMENT_PARAMETER_NAME_PREFIX)): continue if param_name in parameters_to_exclude: diff --git a/src/geophires_x/ParameterUtils.py b/src/geophires_x/ParameterUtils.py index 6f70caa5b..c211ad31c 100644 --- a/src/geophires_x/ParameterUtils.py +++ b/src/geophires_x/ParameterUtils.py @@ -9,6 +9,8 @@ _log = logging.getLogger(__name__) +COMMENT_PARAMETER_NAME_PREFIX: str = '_COMMENT-' + def expand_schedule_dsl( schedule_strings: list[str | float], total_years: int, allow_schedule_length_to_exceed_total_years: bool = False diff --git a/src/geophires_x/__init__.py b/src/geophires_x/__init__.py index 4ecf7b4ea..acdb4c237 100644 --- a/src/geophires_x/__init__.py +++ b/src/geophires_x/__init__.py @@ -1 +1 @@ -__version__ = '3.13.12' +__version__ = '3.13.13' diff --git a/src/geophires_x_client/geophires_input_parameters.py b/src/geophires_x_client/geophires_input_parameters.py index 7389e7fdc..23ac1ef41 100644 --- a/src/geophires_x_client/geophires_input_parameters.py +++ b/src/geophires_x_client/geophires_input_parameters.py @@ -1,4 +1,6 @@ import csv +import json +import os import tempfile import uuid from dataclasses import dataclass @@ -8,12 +10,16 @@ from pathlib import Path from types import MappingProxyType from typing import Any +from typing import Dict +from typing import List from typing import Mapping from typing import Optional from typing import Union from typing_extensions import override +from geophires_x.ParameterUtils import COMMENT_PARAMETER_NAME_PREFIX + class EndUseOption(Enum): """ @@ -220,16 +226,76 @@ def get_output_file_path(self) -> Path: """Returns a unique path for the GEOPHIRES output file.""" return Path(tempfile.gettempdir(), f'geophires-result_{self._instance_id!s}.out') - def as_csv(self) -> str: + def as_csv(self, parse_units_and_comments: bool = False) -> str: """ This method returns the input parameters in CSV (Comma-Separated Values) format, using its internal dictionary as the definitive source. It does not include a header, but uses the same columns as GeophiresXResult.as_csv, and is meant to be used in conjunction with the same. """ + if self.from_file_path: raise NotImplementedError('CSV from file path is not implemented.') + if parse_units_and_comments: + + def _get_file_path(file_name: Union[str, Path]) -> str: + return os.path.join(os.path.abspath(os.path.dirname(__file__)), str(file_name)) + + with open(_get_file_path('../geophires_x_schema_generator/geophires-request.json'), encoding='utf-8') as f: + request_schema: Dict[str, Any] = json.loads(f.read()) + f = StringIO() w = csv.writer(f) - w.writerows([['INPUT PARAMETERS', key, '', value, ''] for key, value in self.params.items()]) + + def _row_entries(param_name: str, param_value_raw: str) -> List[str]: + value_entry = (str(param_value_raw) if param_value_raw is not None else '').strip() + units_entry = '' + comment_entry = '' + + if param_name.startswith(COMMENT_PARAMETER_NAME_PREFIX): + comment_entry = param_value_raw + value_entry = '' + elif parse_units_and_comments: + # TODO consolidate with other codebase parameter parsing logic... + + param_schema = request_schema['properties'].get(param_name, {param_name: {}}) + is_array_type = param_schema.get('type') == 'array' + + value_non_value_split = ( + value_entry.split(' ', maxsplit=1) if not is_array_type else value_entry.split(', --', maxsplit=1) + ) + value_entry = value_non_value_split[0].rstrip(',').rstrip() + remainder = value_non_value_split[1] if len(value_non_value_split) > 1 else '' + + default_units_for_param = param_schema.get('units', '') + + if is_array_type: + # For array-type params, the ', --' split already consumed the comment delimiter; + # whatever remains is the comment (no units possible from the value string). + comment_entry = remainder.lstrip() if remainder else '' + units_entry = default_units_for_param + else: + # For scalar params, remainder is either "" or "-- " + # (optionally " -- "). + unit_and_comment_split = remainder.split(' --', maxsplit=1) if remainder else [''] + units_part = unit_and_comment_split[0] + if units_part.lstrip().startswith('--'): + # No units, just an inline comment introduced by '-- '. + comment_entry = units_part.lstrip()[2:].lstrip() + units_entry = default_units_for_param + else: + units_entry = units_part if units_part != '' else default_units_for_param + if len(unit_and_comment_split) > 1: + comment_entry = unit_and_comment_split[1].lstrip() + + return [ + 'INPUT PARAMETERS', + param_name, + '', # Year column, N/A for input parameters + value_entry, + units_entry, + comment_entry, + ] + + w.writerows([_row_entries(key, value) for key, value in self.params.items()]) return f.getvalue() diff --git a/tests/fpc5-input-params-dict.json b/tests/fpc5-input-params-dict.json new file mode 100644 index 000000000..e11762879 --- /dev/null +++ b/tests/fpc5-input-params-dict.json @@ -0,0 +1,90 @@ +{ + "_COMMENT-0": "# Case Study: 500 MWe EGS Project Modeled on Fervo Cape Station Phase II", + "_COMMENT-1": "# See documentation: https://softwareengineerprogrammer.github.io/GEOPHIRES/Fervo_Project_Cape-5.html", + "_COMMENT-2": "# *** ECONOMIC/FINANCIAL PARAMETERS ***", + "_COMMENT-3": "# *************************************", + "Economic Model": "5, -- The SAM Single Owner PPA economic model is used to calculate financial results including LCOE, NPV, IRR, and pro-forma cash flow analysis. See [GEOPHIRES documentation of SAM Economic Models](https://softwareengineerprogrammer.github.io/GEOPHIRES/SAM-Economic-Models.html) for details on how System Advisor Model financial models are integrated into GEOPHIRES.", + "Inflation Rate": ".027, -- US inflation as of December 2025. Note: [2024b ATB models lower inflation](https://atb.nrel.gov/electricity/2024b/definitions#inflation).", + "Starting Electricity Sale Price": "0.095, -- Aligns with Geysers - Sacramento pricing in [2024b ATB](https://atb.nrel.gov/electricity/2024/geothermal) (NREL, 2025). See Sensitivity Analysis for effect of different prices on results.", + "Electricity Escalation Rate Per Year": "0.00057, -- Calibrated to reach $100/MWh at project year 11", + "Ending Electricity Sale Price": "1, -- Note that this value does not directly determine price at the end of the project life, but rather as a cap as the maximum price to which the starting price can escalate.", + "Electricity Escalation Start Year": "1", + "Fraction of Investment in Bonds": ".7, -- Approximate debt required to cover CAPEX after $1 billion sponsor equity per [Matson, 2024](https://www.linkedin.com/pulse/fervo-energy-technology-day-2024-entering-geothermal-decade-matson-n4stc/). Note that this source says that Fervo ultimately wants to target \u201c15% sponsor equity, 15% bridge loan, and 70% construction to term loans\u201d, but this case study does not attempt to model that capital structure precisely.", + "Discount Rate": "0.12, -- Typical discount rates for higher-risk projects may be 12\u201315%.", + "Inflated Bond Interest Rate": ".07, -- 2024b ATB (NREL, 2025)", + "Inflated Bond Interest Rate During Construction": "0.105, -- Higher than interest rate during normal operation to account for increased risk of default prior to COD. Value aligns with ATB discount rate (NREL, 2025).", + "Bond Financing Start Year": "-2, -- Equity-only for first 2 construction years (ATB)", + "Construction Years": "5, -- Ground broken in 2023 (Fervo Energy, 2023). Expected to reach full scale production in 2028 (Fervo Energy, 2025). See [GEOPHIRES documentation](SAM-EM_Multiple-Construction-Years.html) for details on how construction years affect CAPEX, IRR, and other calculations.", + "_COMMENT-4": "# ATB advanced scenario", + "_COMMENT-5": "# Construction CAPEX Schedule, 0.09,0.28,0.1,0.34,0.28", + "_COMMENT-6": "# DOE scenario (alternative)", + "_COMMENT-7": "# Construction CAPEX Schedule, 0.014,0.027,0.137,0.274,0.548", + "_COMMENT-8": "# DOE-ATB hybrid scenario", + "Construction CAPEX Schedule": "0.014,0.027,0.139,0.431,0.389", + "Investment Tax Credit Rate": "0.3, -- Geothermal Drilling and Completions Apprenticeship Program ensures compliance with ITC labor requirements (Southern Utah University, 2024).", + "Combined Income Tax Rate": ".2555, -- Federal Corporate Income Tax Rate of 21% plus Utah Corporate Franchise and Income Tax Rate of 4.55%. (Note: This input uses a simple summation of statutory rates; the effective combined rate calculated in the model may differ due to standard federal-state tax interactions.)", + "Property Tax Rate": "0.0022, -- Utah Inland Port Authority (UIPA) tax differential incentive", + "Capital Cost for Power Plant for Electricity Generation": "1900, -- [US DOE, 2021](https://betterbuildingssolutioncenter.energy.gov/sites/default/files/attachments/Waste_Heat_to_Power_Fact_Sheet.pdf). Pricing information not publicly available for Turboden or Baker Hughes Gen 2 ORC units (Turboden, 2025; Jacobs, 2025).", + "Exploration Capital Cost": "30, -- Equivalent to 2024b ATB NF-EGS conservative scenario exploration assumption of 5 full-size wells (NREL, 2025), plus $1M for geophysical and field work, plus 15% contingency, plus 12% indirect costs.", + "Well Drilling Cost Correlation": "3, -- 2025 NREL Geothermal Drilling Cost Curve Update (Akindipe and Witter, 2025).", + "Well Drilling and Completion Capital Cost Adjustment Factor": "0.9, -- 2024b Geothermal ATB ([NREL, 2025](https://atb.nrel.gov/electricity/2024b/geothermal)). Note: Fervo has claimed lower drilling costs equivalent to an adjustment factor of 0.8 (Latimer, 2025); the case study conservatively uses the higher ATB-aligned value. See [Sensitivity Analysis](#sensitivity-analysis-section) for effect of different drilling costs on results.", + "Reservoir Stimulation Capital Cost per Injection Well": "4, -- The baseline stimulation cost is calibrated from costs of high-intensity U.S. shale wells (Baytex Energy, 2024; Quantum Proppant Technologies, 2020), which are the closest technological analogue for multi-stage EGS (Gradl, 2018). Costs are also driven by the requirement for high-strength ceramic proppant rather than standard sand, which would crush or chemically degrade (diagenesis) over a 30-year lifecycle at 200\u2103 (Ko et al., 2023; Shiozawa and McClure, 2014) and the premium for ultra-high-temperature (HT) downhole tools. Note that all-in costs per well are higher than the baseline cost because they include additional indirect costs and contingency. See [Sensitivity Analysis](#sensitivity-analysis-section) for effect of different stimulation costs on results.", + "Reservoir Stimulation Capital Cost per Production Well": "4, -- See Reservoir Stimulation Capital Cost per Injection Well", + "Field Gathering System Capital Cost Adjustment Factor": "0.54, -- Gathering costs represent 2% of facilities CAPEX per [Matson, 2024](https://www.linkedin.com/pulse/fervo-energy-technology-day-2024-entering-geothermal-decade-matson-n4stc/).", + "Royalty Rate": "0.0175, -- The BLM royalty structure is 1.75% of gross proceeds from electricity sales for the first 10 years of production (Code of Federal Regulations, 2024).", + "Royalty Rate Escalation Start Year": "11, -- After the first 10 years of production, the royalty rate escalates to 3.5%.", + "Royalty Rate Escalation": "0.0175, -- Escalation at Year 11 from 1.75% to 3.5%.", + "Royalty Rate Maximum": "0.035, -- No further escalation beyond 3.5%.", + "_COMMENT-9": "# *** SURFACE & SUBSURFACE TECHNICAL PARAMETERS ***", + "_COMMENT-10": "# *************************************************", + "End-Use Option": "1, -- Electricity", + "Power Plant Type": "2, -- Gen 2 ORC units (Turboden, 2025).", + "Plant Lifetime": "30, -- Sets the project economic horizon, aligned with Fervo's anticipated 30-year well life (Fervo Energy, 2025). Modeling Distinction: While Fervo projects physical wellbore integrity for 30 years, GEOPHIRES simulates \"redrilling events\" to model thermal management of the reservoir volume. This treats the 30-year lifespan as an aggregate of shorter-lived thermal cycles delineated by discrete redrilling events occurring at intervals dictated by the Maximum Drawdown parameter. The modeled cost of each redrilling event is equivalent to the drilling and stimulation cost of the entire wellfield, serving as a conservative cost proxy for the major interventions (e.g., sidetracking and stimulating laterals into fresh rock, or drilling new wells if necessary) required to sustain the PPA target against thermal depletion.", + "Surface Temperature": "13, -- Surface temperature near Milford, UT (38.4987670, -112.9163432) ([Project InnerSpace, 2025](https://geomap.projectinnerspace.org/test/)).", + "Number of Segments": "3", + "Gradient 1": "74, -- Sedimentary overburden. 200\u2103 at 8500 ft depth (Fercho et al. 2024); 228.89\u2103 at 9824 ft (Norbeck et al. 2024).", + "Thickness 1": "2.5", + "Gradient 2": "41, -- Crystalline reservoir", + "Thickness 2": "0.5", + "Gradient 3": "39.1, -- Sugarloaf appraisal", + "Reservoir Depth": "2.68, -- Extrapolated from surface temperature, gradient, and average production temperature of shallower and deeper producers in Singh et al., 2025.", + "Reservoir Density": "2800, -- phyllite + quartzite + diorite + granodiorite ([Norbeck et al., 2023](https://doi.org/10.31223/X52X0B))", + "Reservoir Heat Capacity": "790", + "Reservoir Thermal Conductivity": "3.05", + "Reservoir Porosity": "0.0118", + "Reservoir Model": "1, -- See the [reservoir engineering calibration section](#res-eng-params-calibration-section) for additional details.", + "Reservoir Volume Option": "1, -- FRAC_NUM_SEP: Reservoir volume calculated with fracture separation and number of fractures as input", + "Number of Fractures per Stimulated Well": "150, -- The model assumes an Extreme Limited Entry stimulation design (Fervo Energy, 2023) utilizing 12 stages with 15 clusters per stage (derived from Singh et al., 2025) and 81\u201385% stimulation success rate per 2024b ATB Moderate Scenario (NREL, 2025).", + "Fracture Separation": "9.8255, -- Based on 30 foot cluster spacing (Singh et al., 2025) marginally uprated to align with long-term thermal decline behavior trend towards wider fracture spacing (Fercho et al., 2025).", + "Fracture Shape": "4, -- Bench design and fracture geometry in Singh et al., 2025 are given in rectangular dimensions.", + "Fracture Width": "305, -- Matches intra-bench well spacing of 500 ft (corresponding to fracture length of 1000 ft) (Singh. et al., 2025)", + "Fracture Height": "100, -- Actual fracture geometry is irregular and heterogeneous; this height complies with the minimum height required by the implemented bench design (200 ft; 60.96 meters) and yields an effective fracture surface area consistent with simulation results in Singh. et al., 2025.", + "Water Loss Fraction": "0.01, -- \"Long-term modeling, calibrated to early field data, predicts circulation recapture rates exceeding 99%\" ([Geothermal Mythbusting: Water Use and Impacts](https://fervoenergy.com/geothermal-mythbusting-water-use-and-impacts/); Fervo Energy, 2025). Modeling in Singh et al., 2025 predicts fluid loss of 0.36% to 0.49%.", + "Water Cost Adjustment Factor": "2, -- Local scarcity may increase procurement costs. Development near/on land with active/shut-in oil and gas wells could potentially utilize waste water to recover losses and offset costs.", + "Ambient Temperature": "11.17, -- Average annual temperature of Milford, Utah ([NCEI](https://www.ncei.noaa.gov/access/us-climate-normals/#dataset=normals-annualseasonal&timeframe=30&station=USC00425654)). Note that this value affects heat to power conversion efficiency. The effects of hourly and seasonal ambient temperature fluctuations on efficiency and power generation are not modeled in this version of the case study.", + "Utilization Factor": ".9", + "Plant Outlet Pressure": "2000 psi, -- McClure, 2024; Singh et al., 2025.", + "Circulation Pump Efficiency": "0.80", + "_COMMENT-11": "# *** Well Bores Parameters ***", + "Number of Production Wells": "56, -- Number of production wells required to produce net generation greater than the PPA minimum and total generation less than nameplate capacity (Gen 2 ORCs gross capacity).", + "Number of Injection Wells per Production Well": "0.666, -- Modeled on the reference case 5-well bench pattern (3 producers : 2 injectors) described in Singh et al., 2025.", + "Nonvertical Length per Multilateral Section": "5000 feet, -- Target lateral length given in environmental assessment (BLM, 2024). Note that lateral length is assumed to be an upper bound constraining the number of fractures per well for a given cluster spacing.", + "Number of Multilateral Sections": "0, -- This parameter is set to 0 because, for this case study, the cost of horizontal drilling is included within the 'vertical drilling cost.' This approach allows us to more directly convey the overall well drilling and completion cost.", + "Production Flow Rate per Well": "107, -- Cape Station pilot testing reported a sustained flow rate of 95\u2013100 kg/s and maximum flow rate of 107 kg/s (Fervo Energy, 2024). Modeling by Singh et al. suggests initial flow rates of 120\u2013130 kg/sec that gradually decrease over time (Singh et al., 2025). The case study flow rate is chosen both as a conservative target for long-term sustainability and to achieve a more economically favorable drawdown and redrilling schedule.", + "_COMMENT-12": "# The ATB Advanced Scenario models sustained flow rates of 110 kg/s (NREL, 2024).", + "Production Well Diameter": "8.535, -- Inner diameter of 9\u215d inch casing size, the next standard casing size up from 7 inches, implied by announcement of \u201cincreasing casing diameter\u201d (Fervo Energy, 2025).", + "Injection Well Diameter": "8.535, -- See Production Well Diameter", + "Production Wellhead Pressure": "303 psi, -- Modeled at a constant 300 psi in Singh et al., 2025. We use a marginally uprated value to conform to GEOPHIRES's calculated minimum wellhead pressure and nominally align with the gradual increase in WHP for constant flow rates modeled by Singh et al.", + "Injectivity Index": "1.38, -- Based on ATB Conservative Scenario (NREL, 2025) derated to align with expected parasitic loads of 15\u201320% and per analyses that suggest lower productivity/injectivitity (Xing et al., 2025; Yearsley and Kombrink, 2024).", + "Productivity Index": "1.13, -- See Injectivity Index", + "Ramey Production Wellbore Model": "True, -- Ramey's model estimates the geofluid temperature drop in production wells", + "Injection Temperature": "53.6, -- Calibrated with GEOPHIRES model-calculated reinjection temperature (Beckers and McCabe, 2019). Close to upper bound of Project Red injection temperatures (75\u2013125\u2109; 23.89\u201351.67\u2103) (Norbeck and Latimer, 2023). Note: GEOPHIRES enforces a thermodynamic optimum that overrides higher values, such as Fervo's considered operational target of 80\u00b0C (intended for silica scaling mitigation), resulting in a \"maximum theoretical power\" scenario. Support for higher reinjection temperatures may be added in future GEOPHIRES versions.", + "Injection Wellbore Temperature Gain": "3, -- Empirical estimate for high-flow rate wells where rapid fluid velocity minimizes heat uptake during descent (Ramey, 1962).", + "Maximum Drawdown": "0.0025, -- This value represents the fractional drop in production temperature compared to the initial temperature that is allowed before the wellfield is redrilled. It is calibrated to maintain the PPA minimum net electricity generation requirement. It is a very small percentage because it is relative to the initial production temperature; the temperature quickly rises higher due to thermal conditioning and plateaus until breakthrough, so any drawdown relative to the initial value signals that the temperature has already declined from its stabilized peak.", + "_COMMENT-13": "# *** SIMULATION PARAMETERS ***", + "_COMMENT-14": "# *****************************", + "Maximum Temperature": "500", + "Time steps per year": "12", + "Project Latitude": "38.506196", + "Project Longitude": "-112.918155" +} diff --git a/tests/geophires_x_tests/test_fervo_project_cape_5.py b/tests/geophires_x_tests/test_fervo_project_cape_5.py index f9da52a29..99b8c64b6 100644 --- a/tests/geophires_x_tests/test_fervo_project_cape_5.py +++ b/tests/geophires_x_tests/test_fervo_project_cape_5.py @@ -12,6 +12,7 @@ from geophires_x.GeoPHIRESUtils import quantity from geophires_x.GeoPHIRESUtils import sig_figs from geophires_x.Parameter import HasQuantity +from geophires_x.ParameterUtils import COMMENT_PARAMETER_NAME_PREFIX from geophires_x_client import GeophiresInputParameters from geophires_x_client import GeophiresXClient from geophires_x_client import GeophiresXResult @@ -85,7 +86,7 @@ def _get_input_parameters( ret[field] = fieldValue.strip() if include_line_comments and field.startswith('#'): - ret[f'_COMMENT-{comment_idx}'] = line.strip() + ret[f'{COMMENT_PARAMETER_NAME_PREFIX}{comment_idx}'] = line.strip() comment_idx += 1 # TODO preserve newlines diff --git a/tests/input-parameters-with-parsed-units-and-comments.csv b/tests/input-parameters-with-parsed-units-and-comments.csv new file mode 100644 index 000000000..4c1dece30 --- /dev/null +++ b/tests/input-parameters-with-parsed-units-and-comments.csv @@ -0,0 +1,6 @@ +INPUT PARAMETERS,_COMMENT-0,,,,# My parameters +INPUT PARAMETERS,Reservoir Depth,,3000,m, +INPUT PARAMETERS,Gradient 1,,50,degC/km, +INPUT PARAMETERS,End-Use Option,,1,,Direct-Use Heat +INPUT PARAMETERS,Construction CAPEX Schedule,,"0.014,0.027,0.139,0.431,0.389",, +INPUT PARAMETERS,Drawdown Parameter Schedule,,"0.003,0.001,0.0 * 10,0.001 * 3,0.002 * 3,0.003 * 3,0.004 * 3,0.005 * 4,0.006",1/year,"No drawdown for first 10 years, then 0.005/year" diff --git a/tests/test_geophires_x_client.py b/tests/test_geophires_x_client.py index f3f8fa873..53b8ceab7 100644 --- a/tests/test_geophires_x_client.py +++ b/tests/test_geophires_x_client.py @@ -1,4 +1,7 @@ +from __future__ import annotations + import csv +import json import tempfile import uuid from pathlib import Path @@ -641,6 +644,15 @@ def test_stash_cwd(self): self.assertEqual(start_cwd, Path.cwd()) + def _assert_fpc5_input_dict_csv(self, parse_units_and_comments: bool): + with open(self._get_test_file_path('fpc5-input-params-dict.json'), encoding='utf-8') as f: + fpc5_input_dict = json.loads(f.read()) + + fpc_input_dict_as_csv = ImmutableGeophiresInputParameters(params=fpc5_input_dict).as_csv( + parse_units_and_comments=parse_units_and_comments + ) + self.assertEqual(len(fpc5_input_dict.keys()), len(fpc_input_dict_as_csv.splitlines())) + def test_csv_with_input_parameters(self): with self.assertRaises(NotImplementedError): # This should fail because CSV from file path is not implemented. @@ -654,16 +666,42 @@ def test_csv_with_input_parameters(self): # Ensure the returned CSV are as expected. csv_lines = csv_result.splitlines() - self.assertEqual(csv_lines[0], 'Category,Field,Year,Value,Units') - self.assertEqual(csv_lines[1], 'INPUT PARAMETERS,Reservoir Depth,,3,') - self.assertEqual(csv_lines[2], 'INPUT PARAMETERS,Gradient 1,,50,') - self.assertEqual(csv_lines[3], 'SUMMARY OF RESULTS,End-Use Option,,Direct-Use Heat,') + self.assertEqual('Category,Field,Year,Value,Units', csv_lines[0]) + self.assertEqual('INPUT PARAMETERS,Reservoir Depth,,3,,', csv_lines[1]) + self.assertEqual('INPUT PARAMETERS,Gradient 1,,50,,', csv_lines[2]) + self.assertEqual('SUMMARY OF RESULTS,End-Use Option,,Direct-Use Heat,', csv_lines[3]) self.assertEqual( - csv_lines[len(csv_lines) - 1], 'HEAT AND/OR ELECTRICITY EXTRACTION AND GENERATION PROFILE,PERCENTAGE OF TOTAL HEAT MINED,25,42.7,%', + csv_lines[len(csv_lines) - 1], ) # Export the CSV for testing in Excel (or other spreadsheet software). result_file = Path(tempfile.gettempdir(), f'geophires-result_{uuid.uuid1()!s}.csv') with open(result_file, 'w', newline='', encoding='utf-8') as rf: rf.write(csv_result) + + self._assert_fpc5_input_dict_csv(parse_units_and_comments=False) + + def test_csv_with_input_parameters_parse_units_and_comments(self): + csv_input_with_units_and_comments = ImmutableGeophiresInputParameters( + params={ + '_COMMENT-0': '# My parameters', + 'Reservoir Depth': '3000 m', + 'Gradient 1': 50, + 'End-Use Option': '1, -- Direct-Use Heat', + 'Construction CAPEX Schedule': '0.014,0.027,0.139,0.431,0.389', + 'Drawdown Parameter Schedule': '0.003,0.001,0.0 * 10,0.001 * 3,0.002 * 3,0.003 * 3,0.004 * 3,0.005 * 4,0.006, -- No drawdown for first 10 years, then 0.005/year', + } + ).as_csv(parse_units_and_comments=True) + + # Export the CSV for testing in Excel (or other spreadsheet software). + result_file = Path(tempfile.gettempdir(), f'geophires-result_{uuid.uuid1()!s}.csv') + with open(result_file, 'w', newline='', encoding='utf-8') as rf: + rf.write(csv_input_with_units_and_comments) + + self.assertIsNotNone(csv_input_with_units_and_comments) + self.assertCsvFileContentsEqual( + self._get_test_file_path('input-parameters-with-parsed-units-and-comments.csv'), result_file + ) + + self._assert_fpc5_input_dict_csv(parse_units_and_comments=True)