From fad091540e3f75efa38668e74a304e6d7f6b715f Mon Sep 17 00:00:00 2001 From: spaulins-usgs Date: Fri, 3 Dec 2021 14:00:03 -0800 Subject: [PATCH] fix(array): getting array data (#1028) (#1290) * fix(array): Getting array data (.array) fixed so that empty stress periods repeat data from the last previous stress period with data. If there is no previous stress period with data, None is used for MFDataList arrays and an array of zeros is used for MFDataArray arrays. * fix(array): added test cases for getting array data (.array) * fix(array and getdata): array and getdata now return appropriate values for "nodata" and "repeating last stress period data" cases * fix(array and getdata): added tests for array and getdata for different types of empty stress periods * fix(array output): minor fix for how arrays output data * fix(test): fixed convergence issues with new test case --- autotest/t505_test.py | 232 +++++++++++++++++++++++++++++++- flopy/mf6/data/mfdata.py | 10 ++ flopy/mf6/data/mfdataarray.py | 87 ++++++++---- flopy/mf6/data/mfdatalist.py | 36 ++++- flopy/mf6/data/mfdatascalar.py | 13 +- flopy/mf6/data/mfdatastorage.py | 15 ++- flopy/mf6/mfpackage.py | 144 +++++++++++++------- 7 files changed, 443 insertions(+), 94 deletions(-) diff --git a/autotest/t505_test.py b/autotest/t505_test.py index ac53ac37a..71953c946 100644 --- a/autotest/t505_test.py +++ b/autotest/t505_test.py @@ -352,6 +352,234 @@ def test_multi_model(): sim.run_simulation() +def test_array(): + # get_data + # empty data in period block vs data repeating + # array + # aux values, test that they work the same as other arrays (is a value + # of zero always used even if aux is defined in a previous stress + # period?) + + import flopy + + mf6 = flopy.mf6 + sim_name = "test_array" + model_name = "test_array" + out_dir = os.path.join("temp", "t505_test_array") + + tdis_name = "{}.tdis".format(sim_name) + sim = mf6.MFSimulation( + sim_name=sim_name, version="mf6", exe_name=exe_name, sim_ws=out_dir + ) + tdis_rc = [(6.0, 2, 1.0), (6.0, 3, 1.0), (6.0, 3, 1.0), (6.0, 3, 1.0)] + tdis = mf6.ModflowTdis(sim, time_units="DAYS", nper=4, perioddata=tdis_rc) + ims_package = ModflowIms( + sim, + pname="my_ims_file", + filename=f"{sim_name}.ims", + print_option="ALL", + complexity="SIMPLE", + outer_dvclose=0.00001, + outer_maximum=50, + under_relaxation="NONE", + inner_maximum=30, + inner_dvclose=0.00001, + linear_acceleration="CG", + preconditioner_levels=7, + preconditioner_drop_tolerance=0.01, + number_orthogonalizations=2, + ) + model = mf6.ModflowGwf( + sim, modelname=model_name, model_nam_file="{}.nam".format(model_name) + ) + + dis = mf6.ModflowGwfdis( + model, + length_units="FEET", + nlay=4, + nrow=2, + ncol=2, + delr=5000.0, + delc=5000.0, + top=100.0, + botm=[50.0, 0.0, -50.0, -100.0], + filename="{}.dis".format(model_name), + ) + ic_package = mf6.ModflowGwfic( + model, strt=90.0, filename=f"{model_name}.ic" + ) + npf_package = mf6.ModflowGwfnpf( + model, + pname="npf_1", + save_flows=True, + alternative_cell_averaging="logarithmic", + icelltype=1, + k=50.0, + ) + + aux = {1: [[50.0], [1.3]], 3: [[200.0], [1.5]]} + irch = {1: [[0, 2], [2, 1]], 2: [[0, 1], [2, 3]]} + rcha = mf6.ModflowGwfrcha( + model, + print_input=True, + print_flows=True, + auxiliary=[("var1", "var2")], + irch=irch, + recharge={1: 0.0001, 2: 0.0002}, + aux=aux, + ) + val_irch = rcha.irch.array.sum(axis=(1, 2, 3)) + assert val_irch[0] == 4 + assert val_irch[1] == 5 + assert val_irch[2] == 6 + assert val_irch[3] == 6 + val_irch_2 = rcha.irch.get_data() + assert val_irch_2[0] is None + assert val_irch_2[1][1, 1] == 1 + assert val_irch_2[2][1, 1] == 3 + assert val_irch_2[3] is None + val_irch_2_3 = rcha.irch.get_data(3) + assert val_irch_2_3 is None + val_rch = rcha.recharge.array.sum(axis=(1, 2, 3)) + assert val_rch[0] == 0.0 + assert val_rch[1] == 0.0004 + assert val_rch[2] == 0.0008 + assert val_rch[3] == 0.0008 + val_rch_2 = rcha.recharge.get_data() + assert val_rch_2[0] is None + assert val_rch_2[1][0, 0] == 0.0001 + assert val_rch_2[2][0, 0] == 0.0002 + assert val_rch_2[3] is None + aux_data_0 = rcha.aux.get_data(0) + assert aux_data_0 is None + aux_data_1 = rcha.aux.get_data(1) + assert aux_data_1[0][0][0] == 50.0 + aux_data_2 = rcha.aux.get_data(2) + assert aux_data_2 is None + aux_data_3 = rcha.aux.get_data(3) + assert aux_data_3[0][0][0] == 200.0 + + welspdict = {1: [[(0, 0, 0), 0.25, 0.0]], 2: [[(0, 0, 0), 0.1, 0.0]]} + wel = flopy.mf6.ModflowGwfwel( + model, + print_input=True, + print_flows=True, + stress_period_data=welspdict, + save_flows=False, + auxiliary="CONCENTRATION", + pname="WEL-1", + ) + wel_array = wel.stress_period_data.array + assert wel_array[0] is None + assert wel_array[1][0][1] == 0.25 + assert wel_array[2][0][1] == 0.1 + assert wel_array[3][0][1] == 0.1 + + drnspdict = { + 0: [[(0, 0, 0), 60.0, 10.0]], + 2: [], + 3: [[(0, 0, 0), 55.0, 5.0]], + } + drn = flopy.mf6.ModflowGwfdrn( + model, + print_input=True, + print_flows=True, + stress_period_data=drnspdict, + save_flows=False, + pname="DRN-1", + ) + drn_array = drn.stress_period_data.array + assert drn_array[0][0][1] == 60.0 + assert drn_array[1][0][1] == 60.0 + assert drn_array[2] is None + assert drn_array[3][0][1] == 55.0 + drn_gd_0 = drn.stress_period_data.get_data(0) + assert drn_gd_0[0][1] == 60.0 + drn_gd_1 = drn.stress_period_data.get_data(1) + assert drn_gd_1 is None + drn_gd_2 = drn.stress_period_data.get_data(2) + assert drn_gd_2 == [] + drn_gd_3 = drn.stress_period_data.get_data(3) + assert drn_gd_3[0][1] == 55.0 + + ghbspdict = { + 0: [[(0, 1, 1), 60.0, 10.0]], + } + ghb = flopy.mf6.ModflowGwfghb( + model, + print_input=True, + print_flows=True, + stress_period_data=ghbspdict, + save_flows=False, + pname="GHB-1", + ) + # test writing and loading model + sim.write_simulation() + if run: + sim.run_simulation() + + test_sim = MFSimulation.load( + sim_name, + "mf6", + exe_name, + out_dir, + write_headers=False, + ) + model = test_sim.get_model() + rcha = model.get_package("rcha") + wel = model.get_package("wel") + drn = model.get_package("drn") + # do same tests as above + val_irch = rcha.irch.array.sum(axis=(1, 2, 3)) + assert val_irch[0] == 4 + assert val_irch[1] == 5 + assert val_irch[2] == 6 + assert val_irch[3] == 6 + val_irch_2 = rcha.irch.get_data() + assert val_irch_2[0] is None + assert val_irch_2[1][1, 1] == 1 + assert val_irch_2[2][1, 1] == 3 + assert val_irch_2[3] is None + val_rch = rcha.recharge.array.sum(axis=(1, 2, 3)) + assert val_rch[0] == 0.0 + assert val_rch[1] == 0.0004 + assert val_rch[2] == 0.0008 + assert val_rch[3] == 0.0008 + val_rch_2 = rcha.recharge.get_data() + assert val_rch_2[0] is None + assert val_rch_2[1][0, 0] == 0.0001 + assert val_rch_2[2][0, 0] == 0.0002 + assert val_rch_2[3] is None + aux_data_0 = rcha.aux.get_data(0) + assert aux_data_0 is None + aux_data_1 = rcha.aux.get_data(1) + assert aux_data_1[0][0][0] == 50.0 + aux_data_2 = rcha.aux.get_data(2) + assert aux_data_2 is None + aux_data_3 = rcha.aux.get_data(3) + assert aux_data_3[0][0][0] == 200.0 + + wel_array = wel.stress_period_data.array + assert wel_array[0] is None + assert wel_array[1][0][1] == 0.25 + assert wel_array[2][0][1] == 0.1 + assert wel_array[3][0][1] == 0.1 + + drn_array = drn.stress_period_data.array + assert drn_array[0][0][1] == 60.0 + assert drn_array[1][0][1] == 60.0 + assert drn_array[2] is None + assert drn_array[3][0][1] == 55.0 + drn_gd_0 = drn.stress_period_data.get_data(0) + assert drn_gd_0[0][1] == 60.0 + drn_gd_1 = drn.stress_period_data.get_data(1) + assert drn_gd_1 is None + drn_gd_2 = drn.stress_period_data.get_data(2) + assert drn_gd_2 == [] + drn_gd_3 = drn.stress_period_data.get_data(3) + assert drn_gd_3[0][1] == 55.0 + + def test_np001(): # init paths test_ex_name = "np001" @@ -758,6 +986,7 @@ def test_np001(): sim.rename_all_packages("file_rename") sim.set_sim_path(rename_folder) sim.write_simulation() + if run: sim.run_simulation() sim.delete_output_files() @@ -1161,7 +1390,7 @@ def test_np002(): md2 = sim2.get_model() ghb2 = md2.get_package("ghb") spd2 = ghb2.stress_period_data.get_data(1) - assert spd2 is None + assert spd2 == [] # test paths sim_path_test = os.path.join(run_folder, "sim_path") @@ -3580,6 +3809,7 @@ def test_transport(): if __name__ == "__main__": + test_array() test_multi_model() test_np001() test_np002() diff --git a/flopy/mf6/data/mfdata.py b/flopy/mf6/data/mfdata.py index 4a49eafa3..9f5e2a019 100644 --- a/flopy/mf6/data/mfdata.py +++ b/flopy/mf6/data/mfdata.py @@ -100,6 +100,12 @@ def get_data_prep(self, transient_key=0): self._verify_sp(transient_key) self._current_key = transient_key if transient_key not in self._data_storage: + if ( + isinstance(transient_key, tuple) + and transient_key[0] in self._data_storage + ): + self._current_key = transient_key[0] + return self.add_transient_key(transient_key) def _set_data_prep(self, data, transient_key=0): @@ -268,6 +274,10 @@ def __repr__(self): def __str__(self): return str(self._get_storage_obj()) + @property + def path(self): + return self._path + @property def array(self): kwargs = {"array": True} diff --git a/flopy/mf6/data/mfdataarray.py b/flopy/mf6/data/mfdataarray.py index 1c7e58a9c..cd4a24fa7 100644 --- a/flopy/mf6/data/mfdataarray.py +++ b/flopy/mf6/data/mfdataarray.py @@ -44,10 +44,12 @@ def __init__( enable=True, path=None, dimensions=None, + block=None, ): super().__init__( sim_data, model_or_sim, structure, enable, path, dimensions ) + self._block = block if self.structure.layered: try: self._layer_shape = self.layer_shape() @@ -725,6 +727,7 @@ def _get_data(self, layer=None, apply_mult=False, **kwargs): "array" in kwargs and kwargs["array"] and isinstance(self, MFTransientArray) + and data is not [] ): data = np.expand_dims(data, 0) return data @@ -1471,6 +1474,7 @@ def __init__( enable=True, path=None, dimensions=None, + block=None, ): super().__init__( sim_data=sim_data, @@ -1480,6 +1484,7 @@ def __init__( enable=enable, path=path, dimensions=dimensions, + block=block, ) self._transient_setup(self._data_storage) self.repeating = True @@ -1599,6 +1604,60 @@ def store_internal( check_data, ) + def _get_array(self, num_sp, apply_mult, **kwargs): + """Returns all data up to stress period num_sp in a single array. + If `layer` is None, returns all data for time `layer`. + + Parameters + ---------- + num_sp : int + Zero-based stress period of data to return all data up to + apply_mult : bool + Whether to apply multiplier to data prior to returning it + + """ + data = None + output = None + for sp in range(0, num_sp): + if sp in self._data_storage: + self.get_data_prep(sp) + data = super().get_data(apply_mult=apply_mult, **kwargs) + data = np.expand_dims(data, 0) + else: + # if there is no previous data provide array of + # zeros, otherwise provide last array of data found + if data is None: + # get any data + data_dimensions = None + for key in self._data_storage.keys(): + self.get_data_prep(key) + data = super().get_data( + apply_mult=apply_mult, **kwargs + ) + break + if data is not None: + if self.structure.type == DatumType.integer: + data = np.full_like(data, 1) + else: + data = np.full_like(data, 0.0) + data = np.expand_dims(data, 0) + if output is None or data is None: + output = data + else: + output = np.concatenate((output, data)) + return output + + def has_data(self, layer=None): + if layer is None: + for sto_key in self._data_storage.keys(): + self.get_data_prep(sto_key) + if super().has_data(): + return True + return False + else: + self.get_data_prep(layer) + return super().has_data() + def get_data(self, layer=None, apply_mult=True, **kwargs): """Returns the data associated with stress period key `layer`. If `layer` is None, returns all data for time `layer`. @@ -1613,38 +1672,14 @@ def get_data(self, layer=None, apply_mult=True, **kwargs): """ if self._data_storage is not None and len(self._data_storage) > 0: if layer is None: - output = None sim_time = self._data_dimensions.package_dim.model_dim[ 0 ].simulation_time num_sp = sim_time.get_num_stress_periods() if "array" in kwargs: - data = None - for sp in range(0, num_sp): - if sp in self._data_storage: - self.get_data_prep(sp) - data = super().get_data( - apply_mult=apply_mult, **kwargs - ) - data = np.expand_dims(data, 0) - else: - if data is None: - # get any data - self.get_data_prep(self._data_storage.key()[0]) - data = super().get_data( - apply_mult=apply_mult, **kwargs - ) - data = np.expand_dims(data, 0) - if self.structure.type == DatumType.integer: - data = np.full_like(data, 0) - else: - data = np.full_like(data, 0.0) - if output is None: - output = data - else: - output = np.concatenate((output, data)) - return output + return self._get_array(num_sp, apply_mult, **kwargs) else: + output = None for sp in range(0, num_sp): data = None if sp in self._data_storage: diff --git a/flopy/mf6/data/mfdatalist.py b/flopy/mf6/data/mfdatalist.py index 34308cbcd..02cadf92a 100644 --- a/flopy/mf6/data/mfdatalist.py +++ b/flopy/mf6/data/mfdatalist.py @@ -49,6 +49,7 @@ def __init__( path=None, dimensions=None, package=None, + block=None, ): super().__init__( sim_data, model_or_sim, structure, enable, path, dimensions @@ -72,6 +73,7 @@ def __init__( ex, ) self._package = package + self._block = block self._last_line_info = [] self._data_line = None self._temp_dict = {} @@ -316,7 +318,7 @@ def store_internal( } self._set_data(internal_data, check_data=check_data) - def has_data(self): + def has_data(self, key=None): """Returns whether this MFList has any data associated with it.""" try: if self._get_storage_obj() is None: @@ -343,7 +345,10 @@ def _get_data(self, apply_mult=False, **kwargs): try: if self._get_storage_obj() is None: return None - return self._get_storage_obj().get_data() + block_exists = self._block.header_exists( + self._current_key, self.path + ) + return self._get_storage_obj().get_data(block_exists=block_exists) except Exception as ex: type_, value_, traceback_ = sys.exc_info() raise MFDataException( @@ -1393,6 +1398,7 @@ def __init__( path=None, dimensions=None, package=None, + block=None, ): super().__init__( sim_data=sim_data, @@ -1403,6 +1409,7 @@ def __init__( path=path, dimensions=dimensions, package=package, + block=block, ) self._transient_setup(self._data_storage) self.repeating = True @@ -1618,6 +1625,19 @@ def store_internal( ) self._cache_model_grid = False + def has_data(self, key=None): + """Returns whether this MFList has any data associated with it in key + "key".""" + if key is None: + for sto_key in self._data_storage.keys(): + self.get_data_prep(sto_key) + if super().has_data(): + return True + return False + else: + self.get_data_prep(layer) + return super().has_data() + def get_data(self, key=None, apply_mult=False, **kwargs): """Returns the data for stress period `key`. @@ -1641,14 +1661,14 @@ def get_data(self, key=None, apply_mult=False, **kwargs): 0 ].simulation_time num_sp = sim_time.get_num_stress_periods() + data = None for sp in range(0, num_sp): if sp in self._data_storage: self.get_data_prep(sp) - output.append( - super().get_data(apply_mult=apply_mult) - ) - else: - output.append(None) + data = super().get_data(apply_mult=apply_mult) + elif self._block.header_exists(sp): + data = None + output.append(data) return output else: output = {} @@ -1968,6 +1988,7 @@ def __init__( path=None, dimensions=None, package=None, + block=None, ): super().__init__( sim_data=sim_data, @@ -1977,6 +1998,7 @@ def __init__( path=path, dimensions=dimensions, package=package, + block=block, ) def get_data(self, key=None, apply_mult=False, **kwargs): diff --git a/flopy/mf6/data/mfdatascalar.py b/flopy/mf6/data/mfdatascalar.py index 6bab86163..11e132736 100644 --- a/flopy/mf6/data/mfdatascalar.py +++ b/flopy/mf6/data/mfdatascalar.py @@ -82,7 +82,7 @@ def dtype(self): return np.int32 return None - def has_data(self): + def has_data(self, key=None): """Returns whether this object has data associated with it.""" try: return self._get_storage_obj().has_data() @@ -771,16 +771,13 @@ def add_one(self, key=0): def has_data(self, key=None): if key is None: - data_found = False for sto_key in self._data_storage.keys(): self.get_data_prep(sto_key) - data_found = data_found or super().has_data() - if data_found: - break + if super().has_data(): + return True else: self.get_data_prep(key) - data_found = super().has_data() - return data_found + return super().has_data() def get_data(self, key=0, **kwargs): """Returns the data for stress period `key`. @@ -852,7 +849,7 @@ def get_file_entry( ext_file_action=ext_file_action ) file_entry.append(text_entry) - if file_entry > 1: + if len(file_entry) > 1: return "\n\n".join(file_entry) elif file_entry == 1: return file_entry[0] diff --git a/flopy/mf6/data/mfdatastorage.py b/flopy/mf6/data/mfdatastorage.py index cd0627dfa..8153c9698 100644 --- a/flopy/mf6/data/mfdatastorage.py +++ b/flopy/mf6/data/mfdatastorage.py @@ -633,10 +633,14 @@ def get_const_val(self, layer=None): def has_data(self, layer=None): ret_val = self._access_data(layer, False) - return ret_val is not None and ret_val != False + return ret_val is not None and ret_val is not False - def get_data(self, layer=None, apply_mult=True): - return self._access_data(layer, True, apply_mult=apply_mult) + def get_data(self, layer=None, apply_mult=True, block_exists=False): + data = self._access_data(layer, True, apply_mult=apply_mult) + if data is None and block_exists: + return [] + else: + return data def _access_data(self, layer, return_data=False, apply_mult=True): layer_check = self._resolve_layer(layer) @@ -2347,7 +2351,10 @@ def _build_full_data(self, apply_multiplier=False): ): full_data = data_out else: - full_data[layer] = data_out + if is_aux and full_data.shape == data_out.shape: + full_data = data_out + else: + full_data[layer] = data_out if is_aux: if full_data is not None: all_none = False diff --git a/flopy/mf6/mfpackage.py b/flopy/mf6/mfpackage.py index 8b6543c55..2b2bce532 100644 --- a/flopy/mf6/mfpackage.py +++ b/flopy/mf6/mfpackage.py @@ -56,10 +56,17 @@ class MFBlockHeader: """ def __init__( - self, name, variable_strings, comment, simulation_data=None, path=None + self, + name, + variable_strings, + comment, + simulation_data=None, + path=None, + block=None, ): self.name = name self.variable_strings = variable_strings + self.block = block if not ( (simulation_data is None and path is None) or (simulation_data is not None and path is not None) @@ -103,6 +110,17 @@ def __init__( self.blk_trailing_comment_path = ("blk_trailing_comment",) self.blk_post_comment_path = ("blk_post_comment",) + def __lt__(self, other): + transient_key = self.get_transient_key() + if transient_key is None: + return True + else: + other_key = other.get_transient_key() + if other_key is None: + return False + else: + return transient_key < other_key + def build_header_variables( self, simulation_data, @@ -132,7 +150,7 @@ def build_header_variables( if len(fixed_data) > 0: fixed_data = [tuple(fixed_data)] # create data object - new_data = MFBlock.data_factory( + new_data = self.block.data_factory( simulation_data, None, block_header_structure[0], @@ -257,11 +275,14 @@ def write_footer(self, fd): fd.write(str(entry.rstrip())) fd.write("\n") - def get_transient_key(self): + def get_transient_key(self, data_path=None): """Get transient key associated with this block header.""" transient_key = None for index in range(0, len(self.data_items)): if self.data_items[index].structure.type != DatumType.keyword: + if data_path == self.data_items[index].path: + # avoid infinite recursion + return True transient_key = self.data_items[index].get_data() if isinstance(transient_key, np.recarray): item_struct = self.data_items[index].structure @@ -342,6 +363,7 @@ def __init__( MFComment("", path, simulation_data, 0), simulation_data, path, + self, ) ] self.structure = structure @@ -378,8 +400,8 @@ def _get_data_str(self, formal): return data_str # return an MFScalar, MFList, or MFArray - @staticmethod def data_factory( + self, sim_data, model_or_sim, structure, @@ -424,10 +446,17 @@ def data_factory( enable, path, dimensions, + self, ) elif data_type == mfstructure.DataType.array_transient: trans_array = mfdataarray.MFTransientArray( - sim_data, model_or_sim, structure, enable, path, dimensions + sim_data, + model_or_sim, + structure, + enable, + path, + dimensions, + self, ) if data is not None: trans_array.set_data(data, key=0) @@ -442,6 +471,7 @@ def data_factory( path, dimensions, package, + self, ) elif data_type == mfstructure.DataType.list_transient: trans_list = mfdatalist.MFTransientList( @@ -452,6 +482,7 @@ def data_factory( path, dimensions, package, + self, ) if data is not None: trans_list.set_data(data, key=0, autofill=True) @@ -465,6 +496,7 @@ def data_factory( path, dimensions, package, + self, ) if data is not None: mult_list.set_data(data, key=0, autofill=True) @@ -594,7 +626,7 @@ def add_dataset(self, dataset_struct, data, var_path): return self.datasets[var_path[-1]] def _build_repeating_header(self, header_data): - if self._header_exists(header_data[0]): + if self.header_exists(header_data[0]): return if ( len(self.block_headers[-1].data_items) == 1 @@ -607,6 +639,7 @@ def _build_repeating_header(self, header_data): MFComment("", self.path, self._simulation_data, 0), self._simulation_data, block_header_path, + self, ) self.block_headers.append(block_header) else: @@ -651,7 +684,7 @@ def _new_dataset( else: initial_val_path = initial_val try: - new_data = MFBlock.data_factory( + new_data = self.data_factory( self._simulation_data, self._model_or_sim, dataset_struct, @@ -765,6 +798,7 @@ def load(self, block_header, fd, strict=True): self.enabled = True if not self.loaded: self.block_headers = [] + block_header.block = self self.block_headers.append(block_header) # process any header variable @@ -1154,25 +1188,27 @@ def write(self, fd, ext_file_action=ExtFileAction.copy_relative_paths): for repeating_dataset in repeating_datasets: # resolve any missing block headers self._add_missing_block_headers(repeating_dataset) - for block_header in self.block_headers: + for block_header in sorted(self.block_headers): # write block self._write_block(fd, block_header, ext_file_action) - else: self._write_block(fd, self.block_headers[0], ext_file_action) def _add_missing_block_headers(self, repeating_dataset): for key in repeating_dataset.get_active_key_list(): - if not self._header_exists(key[0]): + data = repeating_dataset.get_data(key) + if not self.header_exists(key[0]) and data is not None: self._build_repeating_header([key[0]]) - def _header_exists(self, key): + def header_exists(self, key, data_path=None): if not isinstance(key, list): comp_key_list = [key] else: comp_key_list = key for block_header in self.block_headers: - transient_key = block_header.get_transient_key() + transient_key = block_header.get_transient_key(data_path) + if transient_key is True: + return for comp_key in comp_key_list: if transient_key is not None and transient_key == comp_key: return True @@ -1259,40 +1295,13 @@ def _find_repeating_datasets(self): return repeating_datasets def _write_block(self, fd, block_header, ext_file_action): - # write block header - block_header.write_header(fd) transient_key = None if len(block_header.data_items) > 0: transient_key = block_header.get_transient_key() - if self.external_file_name is not None: - # write block contents to external file - indent_string = self._simulation_data.indent_string - fd.write(f"{indent_string}open/close {self.external_file_name}\n") - fd_main = fd - fd_path = os.path.split(os.path.realpath(fd.name))[0] - try: - fd = open(os.path.join(fd_path, self.external_file_name), "w") - except: - type_, value_, traceback_ = sys.exc_info() - message = ( - f'Error reading external file "{self.external_file_name}"' - ) - raise MFDataException( - self._container_package.model_name, - self._container_package._get_pname(), - self.path, - "reading external file", - self.structure.name, - inspect.stack()[0][3], - type_, - value_, - traceback_, - message, - self._simulation_data.debug, - ) - - # write data sets + # gather data sets to write + data_set_output = [] + data_found = False for key, dataset in self.datasets.items(): try: if transient_key is None: @@ -1303,9 +1312,10 @@ def _write_block(self, fd, block_header, ext_file_action): print( f" writing data {dataset.structure.name}..." ) - fd.write( + data_set_output.append( dataset.get_file_entry(ext_file_action=ext_file_action) ) + data_found = True else: if ( self._simulation_data.verbosity_level.value @@ -1316,17 +1326,19 @@ def _write_block(self, fd, block_header, ext_file_action): ".".format(dataset.structure.name, transient_key) ) if dataset.repeating: - fd.write( - dataset.get_file_entry( - transient_key, ext_file_action=ext_file_action - ) + output = dataset.get_file_entry( + transient_key, ext_file_action=ext_file_action ) + if output is not None: + data_set_output.append(output) + data_found = True else: - fd.write( + data_set_output.append( dataset.get_file_entry( ext_file_action=ext_file_action ) ) + data_found = True except MFDataException as mfde: raise MFDataException( mfdata_except=mfde, @@ -1338,6 +1350,42 @@ def _write_block(self, fd, block_header, ext_file_action): f'"{self.structure.name}" to file "{fd.name}"' ), ) + if not data_found: + return + # write block header + block_header.write_header(fd) + + if self.external_file_name is not None: + # write block contents to external file + indent_string = self._simulation_data.indent_string + fd.write(f"{indent_string}open/close {self.external_file_name}\n") + fd_main = fd + fd_path = os.path.split(os.path.realpath(fd.name))[0] + try: + fd = open(os.path.join(fd_path, self.external_file_name), "w") + except: + type_, value_, traceback_ = sys.exc_info() + message = ( + f'Error reading external file "{self.external_file_name}"' + ) + raise MFDataException( + self._container_package.model_name, + self._container_package._get_pname(), + self.path, + "reading external file", + self.structure.name, + inspect.stack()[0][3], + type_, + value_, + traceback_, + message, + self._simulation_data.debug, + ) + + # write data sets + for output in data_set_output: + fd.write(output) + # write trailing comments pth = block_header.blk_trailing_comment_path if pth in self._simulation_data.mfdata: