From af28ed8ffc7e6d6fd19d82a3f9a1d496514ef6b7 Mon Sep 17 00:00:00 2001 From: spaulins-usgs Date: Thu, 13 Aug 2020 12:59:01 -0700 Subject: [PATCH] fix(internal): Flopy fixed to correctly reads in internal keyword when no multiplier supplied. Also, header line added to package files. (#962) * fix(internal): Flopy fixed to correctly reads in internal keyword in the case where there is no multiplier supplied. * feat(header): Flopy feature added that by default adds a header line to each package file indicating that it was generated by flopy. Option called "write_headers" added to simulations that allows users to turn on and off headers. * fix(package/simulation): reformatted with "black --line-length 79" --- autotest/t504_test.py | 3 +- autotest/t505_test.py | 2 +- .../data/mf6/test036_twrihfb/twrihfb2015.rch | 17 ++++- flopy/mf6/data/mfdatastorage.py | 24 +------ flopy/mf6/data/mffileaccess.py | 21 ------- flopy/mf6/mfpackage.py | 35 ++++++++--- flopy/mf6/modflow/mfsimulation.py | 63 +++++++++++++------ 7 files changed, 91 insertions(+), 74 deletions(-) diff --git a/autotest/t504_test.py b/autotest/t504_test.py index a8de8a0563..0d359a5c21 100644 --- a/autotest/t504_test.py +++ b/autotest/t504_test.py @@ -49,7 +49,8 @@ def test001a_tharmonic(): # load simulation sim = MFSimulation.load(model_name, 'mf6', exe_name, pth, - verbosity_level=0, verify_data=True) + verbosity_level=0, verify_data=True, + write_headers=False) sim.simulation_data.mfpath.set_sim_path(run_folder) # write simulation to new location diff --git a/autotest/t505_test.py b/autotest/t505_test.py index 018a1df69f..bb04d0f174 100644 --- a/autotest/t505_test.py +++ b/autotest/t505_test.py @@ -98,7 +98,7 @@ def np001(): # create simulation sim = MFSimulation(sim_name=test_ex_name, version='mf6', exe_name=exe_name, - sim_ws=pth) + sim_ws=pth, write_headers=False) tdis_rc = [(6.0, 2, 1.0), (6.0, 3, 1.0)] tdis_package = ModflowTdis(sim, time_units='DAYS', nper=1, perioddata=[(2.0, 1, 1.0)]) diff --git a/examples/data/mf6/test036_twrihfb/twrihfb2015.rch b/examples/data/mf6/test036_twrihfb/twrihfb2015.rch index 6886f903f6..19df473330 100644 --- a/examples/data/mf6/test036_twrihfb/twrihfb2015.rch +++ b/examples/data/mf6/test036_twrihfb/twrihfb2015.rch @@ -7,5 +7,20 @@ END Options BEGIN PERIOD 1 RECHARGE - CONSTANT 0.30000000E-07 + INTERNAL + 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 + 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 + 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 + 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 + 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 + 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 + 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 + 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 + 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 + 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 + 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 + 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 + 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 + 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 + 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 3.00000000E-08 END PERIOD diff --git a/flopy/mf6/data/mfdatastorage.py b/flopy/mf6/data/mfdatastorage.py index bbc7c0bfb5..702ba50959 100644 --- a/flopy/mf6/data/mfdatastorage.py +++ b/flopy/mf6/data/mfdatastorage.py @@ -1686,28 +1686,6 @@ def process_internal_line(self, arr_line): multiplier = self.get_default_mult() print_format = None if isinstance(arr_line, list): - if len(arr_line) < 2: - message = ( - 'Data array "{}" contains an INTERNAL ' - "that is not followed by a multiplier in line " - '"{}".'.format( - self.data_dimensions.structure.name, " ".join(arr_line) - ) - ) - type_, value_, traceback_ = sys.exc_info() - raise MFDataException( - self.data_dimensions.structure.get_model(), - self.data_dimensions.structure.get_package(), - self.data_dimensions.structure.path, - "processing internal data header", - self.data_dimensions.structure.name, - inspect.stack()[0][3], - type_, - value_, - traceback_, - message, - self._simulation_data.debug, - ) index = 1 while index < len(arr_line): if isinstance(arr_line[index], str): @@ -1851,7 +1829,7 @@ def process_open_close_line(self, arr_line, layer, store=True): except Exception as ex: message = ( "Data array {} contains an OPEN/CLOSE " - "with an invalid multiplier following the " + "with an invalid factor following the " '"factor" keyword.' ".".format(data_dim.structure.name) ) diff --git a/flopy/mf6/data/mffileaccess.py b/flopy/mf6/data/mffileaccess.py index 590192f649..ba382eded7 100644 --- a/flopy/mf6/data/mffileaccess.py +++ b/flopy/mf6/data/mffileaccess.py @@ -787,27 +787,6 @@ def _load_layer( ) # if internal elif arr_line[0].upper() == "INTERNAL": - if len(arr_line) < 2: - message = ( - 'Data array "{}" contains a INTERNAL that is not ' - "followed by a multiplier" - ".".format(self.structure.name) - ) - type_, value_, traceback_ = sys.exc_info() - raise MFDataException( - self.structure.get_model(), - self.structure.get_package(), - self._path, - "loading data layer from file", - self.structure.name, - inspect.stack()[0][3], - type_, - value_, - traceback_, - message, - self._simulation_data.debug, - ) - try: multiplier, print_format = storage.process_internal_line( arr_line diff --git a/flopy/mf6/mfpackage.py b/flopy/mf6/mfpackage.py index e7efa8226e..a4ec749561 100644 --- a/flopy/mf6/mfpackage.py +++ b/flopy/mf6/mfpackage.py @@ -2,6 +2,7 @@ import sys import errno import inspect +import datetime import numpy as np from collections import OrderedDict @@ -22,6 +23,7 @@ from ..pakbase import PackageInterface from .data.mfdatautil import MFComment from ..utils.check import mf6check +from ..version import __version__ class MFBlockHeader(object): @@ -249,7 +251,8 @@ class MFBlock(object): Attributes ---------- block_headers : MFBlockHeaderIO - block header text (BEGIN/END), header variables, comments in the header + block header text (BEGIN/END), header variables, comments in the + header structure : MFBlockStructure structure describing block path : tuple @@ -1007,7 +1010,8 @@ def _find_data_by_keyword(self, line, fd, initial_comment): line, fd, self.block_headers[-1], initial_comment ) - # see if first item's name indicates a reference to another package + # see if first item's name indicates a reference to another + # package package_info_list = self._get_package_info(dataset) if package_info_list is not None: for package_info in package_info_list: @@ -2108,6 +2112,19 @@ def write(self, ext_file_action=ExtFileAction.copy_relative_paths): # open file fd = open(package_file_path, "w") + # write flopy header + if self.simulation_data.write_headers: + dt = datetime.datetime.now() + header = ( + "# File generated by Flopy version {} on {} at {}." + "\n".format( + __version__, + dt.strftime("%m/%d/%Y"), + dt.strftime("%H:%M:%S"), + ) + ) + fd.write(header) + # write blocks self._write_blocks(fd, ext_file_action) @@ -2207,7 +2224,9 @@ def _write_blocks(self, fd, ext_file_action): self.simulation_data.verbosity_level.value >= VerbosityLevel.verbose.value ): - print(" writing block {}...".format(block.structure.name)) + print( + " writing block {}.." ".".format(block.structure.name) + ) # write block block.write(fd, ext_file_action=ext_file_action) block_num += 1 @@ -2257,12 +2276,12 @@ def plot(self, **kwargs): **kwargs : dict filename_base : str - Base file name that will be used to automatically generate file - names for output image files. Plots will be exported as image - files if file_name_base is not None. (default is None) + Base file name that will be used to automatically generate + file names for output image files. Plots will be exported as + image files if file_name_base is not None. (default is None) file_extension : str - Valid matplotlib.pyplot file extension for savefig(). Only used - if filename_base is not None. (default is 'png') + Valid matplotlib.pyplot file extension for savefig(). Only + used if filename_base is not None. (default is 'png') mflay : int MODFLOW zero-based layer number to return. If None, then all all layers will be included. (default is None) diff --git a/flopy/mf6/modflow/mfsimulation.py b/flopy/mf6/modflow/mfsimulation.py index 5b8a4a0e1c..6917b23072 100644 --- a/flopy/mf6/modflow/mfsimulation.py +++ b/flopy/mf6/modflow/mfsimulation.py @@ -128,8 +128,8 @@ def find_in_path(self, key_path, key_leaf): data_item_structures = item.structure.data_item_structures for data_item_struct in data_item_structures: if data_item_struct.name == key_leaf: - # found key_leaf as a data item name in the data in - # the dictionary + # found key_leaf as a data item name in the data + # in the dictionary return item, data_item_index if data_item_struct.type != DatumType.keyword: data_item_index += 1 @@ -221,6 +221,9 @@ class MFSimulationData(object): number of decimal points to write for a floating point number float_characters : int number of characters a floating point number takes up + write_headers: bool + when true flopy writes a header to each package file indicating that + it was created by flopy sci_note_upper_thres : float numbers greater than this threshold are written in scientific notation sci_note_lower_thres : float @@ -242,6 +245,7 @@ def __init__(self, path): self.wrap_multidim_arrays = True self.float_precision = 8 self.float_characters = 15 + self.write_headers = True self._sci_note_upper_thres = 100000 self._sci_note_lower_thres = 0.001 self.fast_write = True @@ -345,6 +349,9 @@ class MFSimulation(PackageContainer): the total memory for each simulation component. ALL means print information for each variable stored in the memory manager. NONE is default if memory_print_option is not specified. + write_headers: bool + when true flopy writes a header to each package file indicating that + it was created by flopy Attributes ---------- @@ -377,8 +384,8 @@ class MFSimulation(PackageContainer): file paths get_model : (model_name : string) : [MFModel] - returns the models in the simulation with a given model name, name file - name, or model type + returns the models in the simulation with a given model name, name + file name, or model type add_model : (model : MFModel, sln_group : integer) add model to the simulation remove_model : (model_name : string) @@ -412,11 +419,13 @@ def __init__( continue_=None, nocheck=None, memory_print_option=None, + write_headers=True, ): super(MFSimulation, self).__init__(MFSimulationData(sim_ws), sim_name) self.simulation_data.verbosity_level = self._resolve_verbosity_level( verbosity_level ) + self.simulation_data.write_headers = write_headers # verify metadata fpdata = mfstructure.MFStructure() if not fpdata.valid: @@ -543,8 +552,10 @@ def __str__(self): def _get_data_str(self, formal): file_mgt = self.simulation_data.mfpath - data_str = "sim_name = {}\nsim_path = {}\nexe_name = " "{}\n\n".format( - self.name, file_mgt.get_sim_path(), self.exe_name + data_str = ( + "sim_name = {}\nsim_path = {}\nexe_name = " + "{}\n" + "\n".format(self.name, file_mgt.get_sim_path(), self.exe_name) ) for package in self._packagelist: @@ -604,6 +615,7 @@ def load( verbosity_level=1, load_only=None, verify_data=False, + write_headers=True, ): """Load an existing model. @@ -636,6 +648,9 @@ def load( example list: ['ic', 'maw', 'npf', 'oc', 'ims', 'gwf6-gwf6'] verify_data : bool verify data when it is loaded. this can slow down loading + write_headers: bool + when true flopy writes a header to each package file indicating + that it was created by flopy Returns ------- @@ -647,7 +662,14 @@ def load( """ # initialize - instance = cls(sim_name, version, exe_name, sim_ws, verbosity_level) + instance = cls( + sim_name, + version, + exe_name, + sim_ws, + verbosity_level, + write_headers=write_headers, + ) verbosity_level = instance.simulation_data.verbosity_level instance.simulation_data.verify_data = verify_data @@ -1099,7 +1121,8 @@ def register_ims_package(self, ims_file, model_list): # add ims package to simulation self._ims_files[ims_file.filename] = ims_file - # If ims file is being replaced, replace ims filename in solution group + # If ims file is being replaced, replace ims filename in + # solution group if pkg_with_same_name is not None and self._is_in_solution_group( pkg_with_same_name.filename, 1 ): @@ -1208,10 +1231,10 @@ def write_simulation( Parameters ext_file_action : ExtFileAction - defines what to do with external files when the simulation path - has changed. defaults to copy_relative_paths which copies only - files with relative paths, leaving files defined by absolute - paths fixed. + defines what to do with external files when the simulation + path has changed. defaults to copy_relative_paths which + copies only files with relative paths, leaving files defined + by absolute paths fixed. silent : bool writes out the simulation in silent mode (verbosity_level = 0) @@ -1244,7 +1267,8 @@ def write_simulation( >= VerbosityLevel.normal.value ): print( - " writing ims package {}...".format(ims_file._get_pname()) + " writing ims package {}.." + ".".format(ims_file._get_pname()) ) ims_file.write(ext_file_action=ext_file_action) @@ -2136,14 +2160,15 @@ def plot(self, model_list=None, SelPackList=None, **kwargs): all packages will be plotted kwargs: filename_base : str - Base file name that will be used to automatically generate file - names for output image files. Plots will be exported as image - files if file_name_base is not None. (default is None) + Base file name that will be used to automatically + generate file names for output image files. Plots will be + exported as image files if file_name_base is not None. + (default is None) file_extension : str - Valid matplotlib.pyplot file extension for savefig(). Only used - if filename_base is not None. (default is 'png') + Valid matplotlib.pyplot file extension for savefig(). + Only used if filename_base is not None. (default is 'png') mflay : int - MODFLOW zero-based layer number to return. If None, then all + MODFLOW zero-based layer number to return. If None, then all layers will be included. (default is None) kper : int MODFLOW zero-based stress period number to return.