From 4e0da1facfaaea8d3ad0addb044eee633b25af56 Mon Sep 17 00:00:00 2001 From: softwareengineerprogrammer <4056124+softwareengineerprogrammer@users.noreply.github.com> Date: Thu, 3 Oct 2024 10:24:13 -0700 Subject: [PATCH 01/12] Switch instances of 'Total Nonvertical Length' to 'Nonvertical Length per Multilateral Section' per https://github.com/NREL/GEOPHIRES-X/pull/297, https://github.com/NREL/GEOPHIRES-X/issues/278 --- ...Beckers_et_al_2023_Tabulated_Database_Coaxial_water_heat.txt | 2 +- .../Beckers_et_al_2023_Tabulated_Database_Uloop_sCO2_elec.txt | 2 +- .../Beckers_et_al_2023_Tabulated_Database_Uloop_sCO2_heat.txt | 2 +- .../Beckers_et_al_2023_Tabulated_Database_Uloop_water_elec.txt | 2 +- .../Beckers_et_al_2023_Tabulated_Database_Uloop_water_heat.txt | 2 +- tests/examples/Fervo_Norbeck_Latimer_2023.txt | 2 +- tests/examples/Fervo_Project_Cape.txt | 2 +- .../Wanju_Yuan_Closed-Loop_Geothermal_Energy_Recovery.txt | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/examples/Beckers_et_al_2023_Tabulated_Database_Coaxial_water_heat.txt b/tests/examples/Beckers_et_al_2023_Tabulated_Database_Coaxial_water_heat.txt index 24962050..2c5caec2 100644 --- a/tests/examples/Beckers_et_al_2023_Tabulated_Database_Coaxial_water_heat.txt +++ b/tests/examples/Beckers_et_al_2023_Tabulated_Database_Coaxial_water_heat.txt @@ -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 diff --git a/tests/examples/Beckers_et_al_2023_Tabulated_Database_Uloop_sCO2_elec.txt b/tests/examples/Beckers_et_al_2023_Tabulated_Database_Uloop_sCO2_elec.txt index ee3bc36a..d4e54693 100644 --- a/tests/examples/Beckers_et_al_2023_Tabulated_Database_Uloop_sCO2_elec.txt +++ b/tests/examples/Beckers_et_al_2023_Tabulated_Database_Uloop_sCO2_elec.txt @@ -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 diff --git a/tests/examples/Beckers_et_al_2023_Tabulated_Database_Uloop_sCO2_heat.txt b/tests/examples/Beckers_et_al_2023_Tabulated_Database_Uloop_sCO2_heat.txt index 3ef8ddda..83c05451 100644 --- a/tests/examples/Beckers_et_al_2023_Tabulated_Database_Uloop_sCO2_heat.txt +++ b/tests/examples/Beckers_et_al_2023_Tabulated_Database_Uloop_sCO2_heat.txt @@ -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 diff --git a/tests/examples/Beckers_et_al_2023_Tabulated_Database_Uloop_water_elec.txt b/tests/examples/Beckers_et_al_2023_Tabulated_Database_Uloop_water_elec.txt index 4b709ee3..80eb0d2e 100644 --- a/tests/examples/Beckers_et_al_2023_Tabulated_Database_Uloop_water_elec.txt +++ b/tests/examples/Beckers_et_al_2023_Tabulated_Database_Uloop_water_elec.txt @@ -15,7 +15,7 @@ Production Flow Rate per Well, 20, ---- kg/s for water / 40 kg/s for sCO2 Cylindrical Reservoir Input Depth, 3000.0 meter, Reservoir 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 diff --git a/tests/examples/Beckers_et_al_2023_Tabulated_Database_Uloop_water_heat.txt b/tests/examples/Beckers_et_al_2023_Tabulated_Database_Uloop_water_heat.txt index 2bdd5f9d..310adcc6 100644 --- a/tests/examples/Beckers_et_al_2023_Tabulated_Database_Uloop_water_heat.txt +++ b/tests/examples/Beckers_et_al_2023_Tabulated_Database_Uloop_water_heat.txt @@ -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 diff --git a/tests/examples/Fervo_Norbeck_Latimer_2023.txt b/tests/examples/Fervo_Norbeck_Latimer_2023.txt index 2b0309f0..c22658f3 100644 --- a/tests/examples/Fervo_Norbeck_Latimer_2023.txt +++ b/tests/examples/Fervo_Norbeck_Latimer_2023.txt @@ -31,7 +31,7 @@ Well Geometry Configuration, 4 Has Nonvertical Section, True Multilaterals Cased, True Number of Multilateral Sections, 2, Two parallel horizontal sections -Total Nonvertical Length, 3250 feet, per the paper +Nonvertical Length per Multilateral Section, 3250 feet, per the paper Well Drilling Cost Correlation, 10, per the drill cost paper - works out to $400/ft Horizontal Well Drilling Cost Correlation, 10, per the drill cost paper - works out to $400/ft Production Flow Rate per Well, 41.02, =650 gpm per the paper - per the paper the maximum flow rate was 63 L/s but the range was 550-750 gpm diff --git a/tests/examples/Fervo_Project_Cape.txt b/tests/examples/Fervo_Project_Cape.txt index 5f8e45a4..f241f781 100644 --- a/tests/examples/Fervo_Project_Cape.txt +++ b/tests/examples/Fervo_Project_Cape.txt @@ -21,7 +21,7 @@ Well Geometry Configuration, 4 Has Nonvertical Section, True Multilaterals Cased, True Number of Multilateral Sections, 2 -Total Nonvertical Length, 5000 feet +Nonvertical Length per Multilateral Section, 5000 feet Production Flow Rate per Well, 98 Production Well Diameter, 8 Injection Well Diameter, 8 diff --git a/tests/examples/Wanju_Yuan_Closed-Loop_Geothermal_Energy_Recovery.txt b/tests/examples/Wanju_Yuan_Closed-Loop_Geothermal_Energy_Recovery.txt index 8c890d9c..fcc9fb69 100644 --- a/tests/examples/Wanju_Yuan_Closed-Loop_Geothermal_Energy_Recovery.txt +++ b/tests/examples/Wanju_Yuan_Closed-Loop_Geothermal_Energy_Recovery.txt @@ -12,7 +12,7 @@ Multilaterals Cased, True Well Geometry Configuration, 1, ----U-loop (could be Eavor style) Plant Lifetime, 40, ---Years Water Thermal Conductivity, 0.65 -Total Nonvertical Length, 5001.0 +Nonvertical Length per Multilateral Section, 5001.0 Nonvertical Wellbore Diameter, 0.23495, -----m Cylindrical Reservoir Radius of Effect Factor, 5.0 Closed Loop Calculation Start Year, 0.1 From d65d9f3df8a2365ba3790bf16b38435f26de4383 Mon Sep 17 00:00:00 2001 From: softwareengineerprogrammer <4056124+softwareengineerprogrammer@users.noreply.github.com> Date: Thu, 3 Oct 2024 10:56:07 -0700 Subject: [PATCH 02/12] Ignore output files (will be generated in src/geophires_x if filename without relative path is provided) --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 6e6d6d1c..872ceaad 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,8 @@ requirements.txt *.json all_messages_conf.log HDR.out +src/geophires_x/*.png +src/geophires_x/*.html # C extensions *.so From cafd4544743de5ced31f692cd2dd030138f15353 Mon Sep 17 00:00:00 2001 From: softwareengineerprogrammer <4056124+softwareengineerprogrammer@users.noreply.github.com> Date: Thu, 3 Oct 2024 11:40:59 -0700 Subject: [PATCH 03/12] Ignore output files in root --- .gitignore | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 872ceaad..657ac0d3 100644 --- a/.gitignore +++ b/.gitignore @@ -6,7 +6,7 @@ tests/__pycache__ # requirements defined in setup.py requirements.txt -# Temp files +# Temp/output files .*.sw[po] *~ *.bak @@ -17,6 +17,11 @@ 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 From 4361af074f275aa952f6800daf4329ce8f7fb129 Mon Sep 17 00:00:00 2001 From: softwareengineerprogrammer <4056124+softwareengineerprogrammer@users.noreply.github.com> Date: Thu, 3 Oct 2024 11:59:31 -0700 Subject: [PATCH 04/12] Output files to original working directory if absolute path not specified (otherwise defaults to src/geophires_x which is undesirable) --- src/geophires_x/GEOPHIRESv3.py | 4 ++- src/geophires_x/Model.py | 10 +++++--- src/geophires_x/Outputs.py | 26 +++++++++++++++---- src/geophires_x/SUTRAOutputs.py | 45 +++------------------------------ 4 files changed, 34 insertions(+), 51 deletions(-) diff --git a/src/geophires_x/GEOPHIRESv3.py b/src/geophires_x/GEOPHIRESv3.py index 2692f8de..a7c31ebd 100644 --- a/src/geophires_x/GEOPHIRESv3.py +++ b/src/geophires_x/GEOPHIRESv3.py @@ -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__))) @@ -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() diff --git a/src/geophires_x/Model.py b/src/geophires_x/Model.py index 96c463e3..63a48dd9 100644 --- a/src/geophires_x/Model.py +++ b/src/geophires_x/Model.py @@ -1,4 +1,5 @@ import sys +from email.policy import default from pathlib import Path import logging import time @@ -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__}') @@ -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 diff --git a/src/geophires_x/Outputs.py b/src/geophires_x/Outputs.py index 006e0125..3af53a95 100644 --- a/src/geophires_x/Outputs.py +++ b/src/geophires_x/Outputs.py @@ -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) @@ -639,24 +640,29 @@ 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 = [] - self.text_output_file = self.ParameterDict[self.text_output_file.Name] = strParameter( + def file_path_parameter(p: Parameter) -> Parameter: + self.filepath_parameter_names.append(p.Name) + return p + + self.text_output_file = self.ParameterDict[self.text_output_file.Name] = file_path_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] = file_path_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', @@ -677,7 +683,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. @@ -692,6 +698,7 @@ 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 :return: None """ model.logger.info(f'Init {__class__!s}: {__name__}') @@ -702,6 +709,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(): + original_val = ParameterReadIn.sValue + ParameterReadIn.sValue = str( + default_output_path.joinpath(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 diff --git a/src/geophires_x/SUTRAOutputs.py b/src/geophires_x/SUTRAOutputs.py index 023adea3..e2ac45cf 100644 --- a/src/geophires_x/SUTRAOutputs.py +++ b/src/geophires_x/SUTRAOutputs.py @@ -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'): """ @@ -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' - - 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 + return 'SUTRAOutputs' - # 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): """ From 1227b933ea181e3ec3f785598b4359412d58cd9f Mon Sep 17 00:00:00 2001 From: softwareengineerprogrammer <4056124+softwareengineerprogrammer@users.noreply.github.com> Date: Thu, 3 Oct 2024 12:06:01 -0700 Subject: [PATCH 05/12] Minor code cleanup --- src/geophires_x/Outputs.py | 8 +++++--- src/geophires_x/SUTRAOutputs.py | 30 ++++-------------------------- 2 files changed, 9 insertions(+), 29 deletions(-) diff --git a/src/geophires_x/Outputs.py b/src/geophires_x/Outputs.py index 3af53a95..9b774071 100644 --- a/src/geophires_x/Outputs.py +++ b/src/geophires_x/Outputs.py @@ -636,17 +636,18 @@ 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 file_path_parameter(p: Parameter) -> Parameter: + 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] = file_path_parameter(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, @@ -655,7 +656,7 @@ def file_path_parameter(p: Parameter) -> Parameter: 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] = file_path_parameter(strParameter( + self.html_output_file = self.ParameterDict[self.html_output_file.Name] = filepath_parameter(strParameter( 'HTML Output File', DefaultValue='GEOPHIRES.html', Required=False, @@ -699,6 +700,7 @@ def read_parameters(self, model: Model, default_output_path: Path = None) -> Non :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__}') diff --git a/src/geophires_x/SUTRAOutputs.py b/src/geophires_x/SUTRAOutputs.py index e2ac45cf..b6c2a15c 100644 --- a/src/geophires_x/SUTRAOutputs.py +++ b/src/geophires_x/SUTRAOutputs.py @@ -47,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') @@ -181,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}') From 78ddb02afb4e46ae136d90e949c688e938174b2a Mon Sep 17 00:00:00 2001 From: softwareengineerprogrammer <4056124+softwareengineerprogrammer@users.noreply.github.com> Date: Thu, 3 Oct 2024 12:29:21 -0700 Subject: [PATCH 06/12] Specify HTML Output File in example12_DH.txt; don't use default output path if None --- src/geophires_x/Outputs.py | 4 +--- tests/examples/example12_DH.txt | 1 + 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/geophires_x/Outputs.py b/src/geophires_x/Outputs.py index 9b774071..b262ce1a 100644 --- a/src/geophires_x/Outputs.py +++ b/src/geophires_x/Outputs.py @@ -713,7 +713,7 @@ def read_parameters(self, model: Model, default_output_path: Path = None) -> Non ParameterReadIn = model.InputParameters[key] if key in self.filepath_parameter_names: - if not Path(ParameterReadIn.sValue).is_absolute(): + 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(ParameterReadIn.sValue).absolute()) @@ -742,8 +742,6 @@ def read_parameters(self, model: Model, default_output_path: Path = None) -> Non 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): diff --git a/tests/examples/example12_DH.txt b/tests/examples/example12_DH.txt index 48955c28..d121470f 100644 --- a/tests/examples/example12_DH.txt +++ b/tests/examples/example12_DH.txt @@ -60,3 +60,4 @@ Fracture Height, 300 Fracture Width, 200 Fracture Shape, 4 Well Drilling and Completion Capital Cost, 12 +HTML Output File, example12_DH.html From 2d0531e5f77770c49b539f9d9f4f8a7b328f07f0 Mon Sep 17 00:00:00 2001 From: softwareengineerprogrammer <4056124+softwareengineerprogrammer@users.noreply.github.com> Date: Thu, 3 Oct 2024 12:31:44 -0700 Subject: [PATCH 07/12] Output HDR.out/HDR.json relative to original working directory. Note that this behavior was already present when running from CLI so the new behavior is only expected to occur and have an effect when running GEOPHIRESv3.py directly from a directory other than src/geophires_x (which is probably rare and should also generally be avoided by users as best practice anyway) --- src/geophires_x/GEOPHIRESv3.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/geophires_x/GEOPHIRESv3.py b/src/geophires_x/GEOPHIRESv3.py index a7c31ebd..3f22f8e5 100644 --- a/src/geophires_x/GEOPHIRESv3.py +++ b/src/geophires_x/GEOPHIRESv3.py @@ -63,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) @@ -73,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] From 40173dcbc75b5bb14d901a3d928c96c7b23d9dc4 Mon Sep 17 00:00:00 2001 From: softwareengineerprogrammer <4056124+softwareengineerprogrammer@users.noreply.github.com> Date: Thu, 3 Oct 2024 12:36:11 -0700 Subject: [PATCH 08/12] =?UTF-8?q?Bump=20version:=203.5.7=20=E2=86=92=203.6?= =?UTF-8?q?.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .bumpversion.cfg | 2 +- .cookiecutterrc | 2 +- README.rst | 4 ++-- docs/conf.py | 2 +- setup.py | 2 +- src/geophires_x/__init__.py | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.bumpversion.cfg b/.bumpversion.cfg index bf393e5b..19d844f3 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 3.5.7 +current_version = 3.6.0 commit = True tag = True diff --git a/.cookiecutterrc b/.cookiecutterrc index 0433782c..c10fa5b6 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.5.7 + version: 3.6.0 version_manager: "bump2version" website: "https://github.com/NREL" year_from: "2023" diff --git a/README.rst b/README.rst index ff091e15..dbb86640 100644 --- a/README.rst +++ b/README.rst @@ -51,9 +51,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.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 diff --git a/docs/conf.py b/docs/conf.py index b5eda9bf..0e706ab2 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -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'] diff --git a/setup.py b/setup.py index 34b6d93f..fd546b08 100755 --- a/setup.py +++ b/setup.py @@ -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( diff --git a/src/geophires_x/__init__.py b/src/geophires_x/__init__.py index aa89269e..826cf62c 100644 --- a/src/geophires_x/__init__.py +++ b/src/geophires_x/__init__.py @@ -1 +1 @@ -__version__ = '3.5.7' +__version__ = '3.6.0' From 22b3d3a27f775358cbb44e5e4685b9c20779fd52 Mon Sep 17 00:00:00 2001 From: softwareengineerprogrammer <4056124+softwareengineerprogrammer@users.noreply.github.com> Date: Thu, 3 Oct 2024 13:08:32 -0700 Subject: [PATCH 09/12] Document technically-backwards-incompatible output file path change in CHANGELOG --- CHANGELOG.rst | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 21e743fb..e7b057e9 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -5,6 +5,22 @@ Changelog GEOPHIRES-X (2023-2024) ------------------------ +3.6 +^^^ + +`release `__ | `diff `__ + +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 ^^^ From dab297d819054e157392682ff2afb6d83f7734e7 Mon Sep 17 00:00:00 2001 From: softwareengineerprogrammer <4056124+softwareengineerprogrammer@users.noreply.github.com> Date: Thu, 3 Oct 2024 13:31:14 -0700 Subject: [PATCH 10/12] Fix Path constructor, unit test default output file paths --- src/geophires_x/Outputs.py | 2 +- tests/geophires_x_tests/test_outputs.py | 45 +++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 tests/geophires_x_tests/test_outputs.py diff --git a/src/geophires_x/Outputs.py b/src/geophires_x/Outputs.py index b262ce1a..271c7bac 100644 --- a/src/geophires_x/Outputs.py +++ b/src/geophires_x/Outputs.py @@ -716,7 +716,7 @@ def read_parameters(self, model: Model, default_output_path: Path = None) -> Non 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(ParameterReadIn.sValue).absolute()) + 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.') diff --git a/tests/geophires_x_tests/test_outputs.py b/tests/geophires_x_tests/test_outputs.py new file mode 100644 index 00000000..8d06e4ae --- /dev/null +++ b/tests/geophires_x_tests/test_outputs.py @@ -0,0 +1,45 @@ +import os +import sys +from pathlib import Path + +from geophires_x.Model import Model +from geophires_x_client import GeophiresInputParameters +from tests.base_test_case import BaseTestCase + + +class OutputsTestCase(BaseTestCase): + + def test_relative_output_file_path(self): + input_file = GeophiresInputParameters({'HTML Output File': 'foo.html'}).as_file_path() + m = self._new_model(input_file=input_file, original_cwd=Path('/tmp/')) # noqa: S108 + html_filepath = Path(m.outputs.html_output_file.value) + self.assertTrue(html_filepath.is_absolute()) + self.assertEqual(str(html_filepath), '/tmp/foo.html') # noqa: S108 + + def test_absolute_output_file_path(self): + input_file = GeophiresInputParameters( + {'HTML Output File': '/home/user/my-geophires-project/foo.html'} + ).as_file_path() + m = self._new_model(input_file=input_file, original_cwd=Path('/tmp/')) # noqa: S108 + html_filepath = Path(m.outputs.html_output_file.value) + self.assertTrue(html_filepath.is_absolute()) + self.assertEqual(str(html_filepath), '/home/user/my-geophires-project/foo.html') + + def _new_model(self, input_file=None, original_cwd=None) -> Model: + stash_cwd = Path.cwd() + stash_sys_argv = sys.argv + + sys.argv = [''] + + if input_file is not None: + sys.argv.append(input_file) + + m = Model(enable_geophires_logging_config=False) + + if input_file is not None: + m.read_parameters(default_output_path=original_cwd) + + sys.argv = stash_sys_argv + os.chdir(stash_cwd) + + return m From e49454e99666ba6ce62897e7a11ffe03f2068af5 Mon Sep 17 00:00:00 2001 From: softwareengineerprogrammer <4056124+softwareengineerprogrammer@users.noreply.github.com> Date: Thu, 3 Oct 2024 13:50:13 -0700 Subject: [PATCH 11/12] Make outputs test path comparison OS-agnostic --- tests/geophires_x_tests/test_outputs.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/geophires_x_tests/test_outputs.py b/tests/geophires_x_tests/test_outputs.py index 8d06e4ae..8bca685e 100644 --- a/tests/geophires_x_tests/test_outputs.py +++ b/tests/geophires_x_tests/test_outputs.py @@ -14,7 +14,7 @@ def test_relative_output_file_path(self): m = self._new_model(input_file=input_file, original_cwd=Path('/tmp/')) # noqa: S108 html_filepath = Path(m.outputs.html_output_file.value) self.assertTrue(html_filepath.is_absolute()) - self.assertEqual(str(html_filepath), '/tmp/foo.html') # noqa: S108 + self.assertEqual(str(html_filepath), str(Path('/tmp/foo.html'))) # noqa: S108 def test_absolute_output_file_path(self): input_file = GeophiresInputParameters( @@ -23,7 +23,7 @@ def test_absolute_output_file_path(self): m = self._new_model(input_file=input_file, original_cwd=Path('/tmp/')) # noqa: S108 html_filepath = Path(m.outputs.html_output_file.value) self.assertTrue(html_filepath.is_absolute()) - self.assertEqual(str(html_filepath), '/home/user/my-geophires-project/foo.html') + self.assertEqual(str(html_filepath), str(Path('/home/user/my-geophires-project/foo.html'))) def _new_model(self, input_file=None, original_cwd=None) -> Model: stash_cwd = Path.cwd() From 104fe35a0de8185862d5a603456fe0d17e12428d Mon Sep 17 00:00:00 2001 From: softwareengineerprogrammer <4056124+softwareengineerprogrammer@users.noreply.github.com> Date: Thu, 3 Oct 2024 13:58:18 -0700 Subject: [PATCH 12/12] More unit test OS-agnosticism --- tests/geophires_x_tests/test_outputs.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/geophires_x_tests/test_outputs.py b/tests/geophires_x_tests/test_outputs.py index 8bca685e..5cd6c9a2 100644 --- a/tests/geophires_x_tests/test_outputs.py +++ b/tests/geophires_x_tests/test_outputs.py @@ -14,7 +14,7 @@ def test_relative_output_file_path(self): m = self._new_model(input_file=input_file, original_cwd=Path('/tmp/')) # noqa: S108 html_filepath = Path(m.outputs.html_output_file.value) self.assertTrue(html_filepath.is_absolute()) - self.assertEqual(str(html_filepath), str(Path('/tmp/foo.html'))) # noqa: S108 + self.assertEqual(str(html_filepath).replace('D:', ''), str(Path('/tmp/foo.html'))) # noqa: S108 def test_absolute_output_file_path(self): input_file = GeophiresInputParameters( @@ -23,7 +23,7 @@ def test_absolute_output_file_path(self): m = self._new_model(input_file=input_file, original_cwd=Path('/tmp/')) # noqa: S108 html_filepath = Path(m.outputs.html_output_file.value) self.assertTrue(html_filepath.is_absolute()) - self.assertEqual(str(html_filepath), str(Path('/home/user/my-geophires-project/foo.html'))) + self.assertEqual(str(html_filepath).replace('D:', ''), str(Path('/home/user/my-geophires-project/foo.html'))) def _new_model(self, input_file=None, original_cwd=None) -> Model: stash_cwd = Path.cwd()