Skip to content
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

Output file default behavior #35

Merged
merged 12 commits into from
Oct 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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.5.7
current_version = 3.6.0
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.5.7
version: 3.6.0
version_manager: "bump2version"
website: "https://github.com/NREL"
year_from: "2023"
Expand Down
9 changes: 8 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ tests/__pycache__
# requirements defined in setup.py
requirements.txt

# Temp files
# Temp/output files
.*.sw[po]
*~
*.bak
Expand All @@ -15,6 +15,13 @@ requirements.txt
*.json
all_messages_conf.log
HDR.out
src/geophires_x/*.png
src/geophires_x/*.html
*HEATING_COOLING*.png
*CASHFLOW_PROFILE*.png
Geothermal_district_heating_system_with_peaking_boilers.png
*.html
!docs/*.html

# C extensions
*.so
Expand Down
16 changes: 16 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,22 @@ Changelog
GEOPHIRES-X (2023-2024)
------------------------

3.6
^^^

`release <https://github.com/NREL/GEOPHIRES-X/releases/tag/v3.6.0>`__ | `diff <https://github.com/NREL/GEOPHIRES-X/compare/v3.5.0...v3.6.0>`__

Changes default output file path to the original working directory instead of the the GEOPHIRES module source directory (usually ``geophires-x`` or ``src/geophires_x``, depending on package installation type).
This affects:

1. Users who call GEOPHIRES as a script from a working directory outside of the module source directory and pass no output file argument or a non-absolute output file argument e.g. ``python ./geophires-x/GEOPHIRESv3.py my-input.txt``. In prior versions, the output file would have been generated at ``./geophires_x/HDR.out``; in v.3.6 it is generated at ``./HDR.out`` instead. (Users who call GEOPHIRES as a module – ``python -m geophires_x my-input.txt`` – will see no change since the module has always output relative to the working directory.)

2. Inputs with ``HTML Output File`` and/or ``Improved Text Output File`` parameters specified as non-absolute paths. The associated output files will now be generated relative to the working directory instead of the GEOPHIRES module source directory.


Affected users who do not want the new behavior can specify absolute output paths instead of relative ones e.g. ``python ./geophires-x/GEOPHIRESv3.py my-input.txt /home/user/my-geophires-project/geophires-x/HDR.out``
(Most users are expected to be unaffected.)

3.5
^^^

Expand Down
4 changes: 2 additions & 2 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,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.5.7.svg
.. |commits-since| image:: https://img.shields.io/github/commits-since/softwareengineerprogrammer/GEOPHIRES-X/v3.6.0.svg
:alt: Commits since latest release
:target: https://github.com/softwareengineerprogrammer/GEOPHIRES-X/compare/v3.5.7...main
:target: https://github.com/softwareengineerprogrammer/GEOPHIRES-X/compare/v3.6.0...main

.. |docs| image:: https://readthedocs.org/projects/GEOPHIRES-X/badge/?style=flat
:target: https://nrel.github.io/GEOPHIRES-X
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 = '2024'
author = 'NREL'
copyright = f'{year}, {author}'
version = release = '3.5.7'
version = release = '3.6.0'

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.5.7',
version='3.6.0',
license='MIT',
description='GEOPHIRES is a free and open-source geothermal techno-economic simulator.',
long_description='{}\n{}'.format(
Expand Down
8 changes: 5 additions & 3 deletions src/geophires_x/GEOPHIRESv3.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ def main(enable_geophires_logging_config=True):
logging will be configured in the Model class.
:return: None
"""
original_cwd:Path = Path.cwd().absolute()

# set the starting directory to be the directory that this file is in
os.chdir(os.path.dirname(os.path.abspath(__file__)))

Expand All @@ -33,7 +35,7 @@ def main(enable_geophires_logging_config=True):
model = Model.Model(enable_geophires_logging_config=enable_geophires_logging_config)

# read the parameters that apply to the model
model.read_parameters()
model.read_parameters(default_output_path=original_cwd)

# Calculate the entire model
model.Calculate()
Expand Down Expand Up @@ -61,7 +63,7 @@ def main(enable_geophires_logging_config=True):
supress_warnings=True)
json_merged = {**json_merged, **json.loads(json_sdacgt)}

json_outputfile = 'HDR.json'
json_outputfile = Path(original_cwd, 'HDR.json')
if len(sys.argv) > 2:
output_arg = str(sys.argv[2])
output_arg_path = Path(output_arg)
Expand All @@ -71,7 +73,7 @@ def main(enable_geophires_logging_config=True):

# if the user has asked for it, copy the output file to the screen
if model.outputs.printoutput:
outputfile = 'HDR.out'
outputfile = Path(original_cwd, 'HDR.out')
if len(sys.argv) > 2:
outputfile = sys.argv[2]

Expand Down
10 changes: 6 additions & 4 deletions src/geophires_x/Model.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import sys
from email.policy import default
from pathlib import Path
import logging
import time
Expand Down Expand Up @@ -196,9 +197,10 @@ def __init__(self, enable_geophires_logging_config=True, input_file=None):
def __str__(self):
return "Model"

def read_parameters(self) -> None:
def read_parameters(self, default_output_path: Path = None) -> None:
"""
The read_parameters function reads the parameters from the input file and stores them in a dictionary.
:param default_output_path: Relative path for non-absolute output path parameters
:return: None
"""
self.logger.info(f'Init {__class__}: {__name__}')
Expand All @@ -209,17 +211,17 @@ def read_parameters(self) -> None:
self.wellbores.read_parameters(self)
self.surfaceplant.read_parameters(self)
self.economics.read_parameters(self)
self.outputs.read_parameters(self)
self.outputs.read_parameters(self, default_output_path=default_output_path)

# having read in the parameters, we now need to set up the objects that are specific to the user's choices
# if we find out we have an add-ons, read the parameters
if self.economics.DoAddOnCalculations.value:
self.addeconomics.read_parameters(self)
self.addoutputs.read_parameters(self)
self.addoutputs.read_parameters(self, default_output_path=default_output_path)
# if we find out we have an S-DAC-GT calculation, read for the parameters
if self.economics.DoSDACGTCalculations.value:
self.sdacgteconomics.read_parameters(self)
self.sdacgtoutputs.read_parameters(self)
self.sdacgtoutputs.read_parameters(self, default_output_path=default_output_path)

# Once we are done reading and processing parameters,
# we reset the objects to more specific objects based on user choices
Expand Down
30 changes: 23 additions & 7 deletions src/geophires_x/Outputs.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
from geophires_x.OptionList import EndUseOptions, EconomicModel, ReservoirModel, FractureShape, ReservoirVolume, \
PlantType
from geophires_x.GeoPHIRESUtils import UpgradeSymbologyOfUnits, render_default, InsertImagesIntoHTML
from geophires_x.Parameter import Parameter

NL = '\n'
validFilenameChars = "-_.() %s%s" % (string.ascii_letters, string.digits)
Expand Down Expand Up @@ -635,28 +636,34 @@ class Outputs:
"""
This class handles all the outputs for the GEOPHIRESv3 model.
"""

def __init__(self, model:Model, output_file:str ='HDR.out'):
model.logger.info(f'Init {__class__!s}: {__name__}')
self.ParameterDict = {}
self.OutputParameterDict = {}
self.filepath_parameter_names = []

def filepath_parameter(p: Parameter) -> Parameter:
self.filepath_parameter_names.append(p.Name)
return p

self.text_output_file = self.ParameterDict[self.text_output_file.Name] = strParameter(
self.text_output_file = self.ParameterDict[self.text_output_file.Name] = filepath_parameter(strParameter(
'Improved Text Output File',
DefaultValue='GEOPHIRES_Text.html',
Required=False,
Provided=False,
ErrMessage='assume no improved text output',
ToolTipText='Provide a improved text output name if you want to have improved text output (no output if not provided)',
)
))

self.html_output_file = self.ParameterDict[self.html_output_file.Name] = strParameter(
self.html_output_file = self.ParameterDict[self.html_output_file.Name] = filepath_parameter(strParameter(
'HTML Output File',
DefaultValue='GEOPHIRES.html',
Required=False,
Provided=False,
ErrMessage='assume no HTML output',
ToolTipText='Provide a HTML output name if you want to have HTML output (no output if not provided)',
)
))

self.printoutput = self.ParameterDict[self.printoutput.Name] = boolParameter(
'Print Output to Console',
Expand All @@ -677,7 +684,7 @@ def __init__(self, model:Model, output_file:str ='HDR.out'):
def __str__(self):
return 'Outputs'

def read_parameters(self, model:Model) -> None:
def read_parameters(self, model: Model, default_output_path: Path = None) -> None:
"""
The read_parameters function reads in the parameters from a dictionary and stores them in the parameters.
It also handles special cases that need to be handled after a value has been read in and checked.
Expand All @@ -692,6 +699,8 @@ def read_parameters(self, model:Model) -> None:
to call this method from you class, which can effectively modify all these superclass parameters in your class.
:param model: The container class of the application, giving access to everything else, including the logger
:type model: :class:`~geophires_x.Model.Model`
:param default_output_path: Relative path for non-absolute output path parameters
:type default_output_path: pathlib.Path
:return: None
"""
model.logger.info(f'Init {__class__!s}: {__name__}')
Expand All @@ -702,6 +711,15 @@ def read_parameters(self, model:Model) -> None:
key = ParameterToModify.Name.strip()
if key in model.InputParameters:
ParameterReadIn = model.InputParameters[key]

if key in self.filepath_parameter_names:
if not Path(ParameterReadIn.sValue).is_absolute() and default_output_path is not None:
original_val = ParameterReadIn.sValue
ParameterReadIn.sValue = str(
default_output_path.joinpath(Path(ParameterReadIn.sValue)).absolute())
model.logger.info(f'Adjusted {key} path to {ParameterReadIn.sValue} because original value '
f'({original_val}) was not an absolute path.')

# Before we change the parameter, let's assume that the unit preferences will match
# - if they don't, the later code will fix this.
ParameterToModify.CurrentUnits = ParameterToModify.PreferredUnits
Expand All @@ -724,8 +742,6 @@ def read_parameters(self, model:Model) -> None:
if key.startswith('Units:'):
self.ParameterDict[key.replace('Units:', '')] = LookupUnits(model.InputParameters[key].sValue)[0]

# handle special cases

model.logger.info(f'Complete {__class__!s}: {__name__}')

def PrintOutputs(self, model: Model):
Expand Down
75 changes: 8 additions & 67 deletions src/geophires_x/SUTRAOutputs.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@
import geophires_x
import numpy as np
import geophires_x.Model as Model
from .Parameter import LookupUnits
from geophires_x.Outputs import Outputs
from .OptionList import EconomicModel

NL="\n"

class SUTRAOutputs:
"""TODO should inherit from Outputs"""

class SUTRAOutputs(Outputs):

def __init__(self, model:Model, output_file:str ='HDR.out'):
"""
Expand All @@ -35,45 +35,8 @@ def __init__(self, model:Model, output_file:str ='HDR.out'):
model.logger.info(f'Complete {str(__class__)}: {sys._getframe().f_code.co_name}')

def __str__(self):
return 'Outputs'
return 'SUTRAOutputs'

def read_parameters(self, model:Model) -> None:
"""
The read_parameters function reads in the parameters from a dictionary and stores them in the parameters.
It also handles special cases that need to be handled after a value has been read in and checked.
If you choose to subclass this master class, you can also choose to override this method (or not), and if you do
Deals with all the parameter values that the user has provided. They should really only provide values that
they want to change from the default values, but they can provide a value that is already set because it is a
default value set in __init__. It will ignore those.
This also deals with all the special cases that need to be taken care of after a value has been read in
and checked.
If you choose to subclass this master class, you can also choose to override this method (or not),
and if you do, do it before or after you call you own version of this method. If you do, you can also choose
to call this method from you class, which can effectively modify all these superclass parameters in your class.
:param model: The container class of the application, giving access to everything else, including the logger
:type model: :class:`~geophires_x.Model.Model`
:return: None
"""
model.logger.info(f'Init {str(__class__)}: {sys._getframe().f_code.co_name}')

if len(model.InputParameters) > 0:
# if the user wants it, we need to know if the user wants to copy the contents of the
# output file to the screen - this serves as the screen report
if "Print Output to Console" in model.InputParameters:
ParameterReadIn = model.InputParameters["Print Output to Console"]
if ParameterReadIn.sValue == "0":
self.printoutput = False

# loop through all the parameters that the user wishes to set, looking for parameters that contain the
# prefix "Units:" - that means we want to set a special case for converting this
# output parameter to new units
for key in model.InputParameters.keys():
if key.startswith("Units:"):
self.ParameterDict[key.replace("Units:", "")] = LookupUnits(model.InputParameters[key].sValue)[0]

# handle special cases

model.logger.info(f'Complete {str(__class__)}: {sys._getframe().f_code.co_name}')

def PrintOutputs(self, model: Model):
"""
Expand All @@ -84,30 +47,8 @@ def PrintOutputs(self, model: Model):
"""
model.logger.info(f'Init {str(__class__)}: {sys._getframe().f_code.co_name}')

# Deal with converting Units back to PreferredUnits, if required.
# before we write the outputs, we go thru all the parameters for all of the objects and set the values back
# to the units that the user entered the data in
# We do this because the value may be displayed in the output, and we want the user to recognize their value,
# not some converted value
# for obj in [model.reserv, model.wellbores, model.surfaceplant, model.economics]:
# for key in obj.ParameterDict:
# param = obj.ParameterDict[key]
# if not param.UnitsMatch: ConvertUnitsBack(param, model)

# now we need to loop through all thw output parameters to update their units to
# whatever units the user has specified.
# i.e., they may have specified that all LENGTH results must be in feet, so we need to convert those
# from whatever LENGTH unit they are to feet.
# same for all the other classes of units (TEMPERATURE, DENSITY, etc).

#for obj in [model.reserv, model.wellbores, model.surfaceplant, model.economics]:
# for key in obj.OutputParameterDict:
# if key in self.ParameterDict:
# if self.ParameterDict[key] != obj.OutputParameterDict[key].CurrentUnits:
# ConvertOutputUnits(obj.OutputParameterDict[key], self.ParameterDict[key], model)

# write results to output file and screen

try:
with open(self.output_file,'w', encoding='UTF-8') as f:
f.write(' *****************\n')
Expand Down Expand Up @@ -218,10 +159,10 @@ def PrintOutputs(self, model: Model):
except BaseException as ex:
tb = sys.exc_info()[2]
print(str(ex))
print("Error: GEOPHIRES Failed to write the output file. Exiting....Line %i" % tb.tb_lineno)
msg = "Error: GEOPHIRES Failed to write the output file. Exiting....Line %i" % tb.tb_lineno
print(msg)
model.logger.critical(str(ex))
model.logger.critical("Error: GEOPHIRES Failed to write the output file. Exiting....Line %i" % tb.tb_lineno)
# FIXME raise exception instead of sys.exit()
sys.exit()
model.logger.critical(msg)
raise RuntimeError(msg)

model.logger.info(f'Complete {str(__class__)}: {sys._getframe().f_code.co_name}')
2 changes: 1 addition & 1 deletion src/geophires_x/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = '3.5.7'
__version__ = '3.6.0'
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ Exploration Capital Cost, 0
Production Flow Rate per Well, 20, ---- kg/s for water / 40 kg/s for sCO2
Cylindrical Reservoir Input Depth, 3.0, -----kilometers
Gradient 1, 60.0, ----deg.c/km
Total Nonvertical Length, 9000, ----- m
Nonvertical Length per Multilateral Section, 9000, ----- m
Production Well Diameter,8.5, --- [inch]
Reservoir Depth, 3.0, -----kilometers
Injection Temperature, 60.0, -----deg.C
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ All-in Nonvertical Drilling Costs, 1000.0
Production Flow Rate per Well, 40, ---- kg/s for water / 40 kg/s for sCO2
Cylindrical Reservoir Input Depth, 3000.0 meter,
Gradient 1, 60.0, ----deg.c/km
Total Nonvertical Length, 9000
Nonvertical Length per Multilateral Section, 9000
Production Well Diameter,8.5, --- [inch]
Injection Temperature, 60.0, -----deg.C
Plant Lifetime, 40, --- years
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ Exploration Capital Cost, 0
Production Flow Rate per Well, 40, ---- kg/s for water / 40 kg/s for sCO2
Cylindrical Reservoir Input Depth, 3.0, -----kilometers
Gradient 1, 60.0, ----deg.c/km
Total Nonvertical Length, 9000, ----- m
Nonvertical Length per Multilateral Section, 9000, ----- m
Production Well Diameter,8.5, --- [inch]
Injection Temperature, 60.0, -----deg.C
Plant Lifetime, 40, --- years
Expand Down
Loading