diff --git a/autotest/t505_test.py b/autotest/t505_test.py index ec914b26a9..b113105082 100644 --- a/autotest/t505_test.py +++ b/autotest/t505_test.py @@ -62,6 +62,300 @@ os.makedirs(cpth) +def get_gwf_model(sim, gwfname, gwfpath, modelshape, chdspd=None, welspd=None): + nlay, nrow, ncol = modelshape + delr = 1.0 + delc = 1.0 + top = 1.0 + botm = [0.0] + strt = 1.0 + hk = 1.0 + laytyp = 0 + + gwf = flopy.mf6.ModflowGwf( + sim, + modelname=gwfname, + save_flows=True, + ) + gwf.set_model_relative_path(gwfpath) + + dis = flopy.mf6.ModflowGwfdis( + gwf, + nlay=nlay, + nrow=nrow, + ncol=ncol, + delr=delr, + delc=delc, + top=top, + botm=botm, + ) + + # initial conditions + ic = flopy.mf6.ModflowGwfic(gwf, strt=strt) + + # node property flow + npf = flopy.mf6.ModflowGwfnpf( + gwf, + icelltype=laytyp, + k=hk, + save_specific_discharge=True, + ) + + # chd files + if chdspd is not None: + chd = flopy.mf6.modflow.mfgwfchd.ModflowGwfchd( + gwf, + stress_period_data=chdspd, + save_flows=False, + pname="CHD-1", + ) + + # wel files + if welspd is not None: + wel = flopy.mf6.ModflowGwfwel( + gwf, + print_input=True, + print_flows=True, + stress_period_data=welspd, + save_flows=False, + auxiliary="CONCENTRATION", + pname="WEL-1", + ) + + # output control + oc = flopy.mf6.ModflowGwfoc( + gwf, + budget_filerecord="{}.cbc".format(gwfname), + head_filerecord="{}.hds".format(gwfname), + headprintrecord=[("COLUMNS", 10, "WIDTH", 15, "DIGITS", 6, "GENERAL")], + saverecord=[("HEAD", "LAST"), ("BUDGET", "LAST")], + printrecord=[("HEAD", "LAST"), ("BUDGET", "LAST")], + ) + return gwf + + +def get_gwt_model(sim, gwtname, gwtpath, modelshape, sourcerecarray=None): + nlay, nrow, ncol = modelshape + delr = 1.0 + delc = 1.0 + top = 1.0 + botm = [0.0] + strt = 1.0 + hk = 1.0 + laytyp = 0 + + gwt = flopy.mf6.MFModel( + sim, + model_type="gwt6", + modelname=gwtname, + model_rel_path=gwtpath, + ) + gwt.name_file.save_flows = True + + dis = flopy.mf6.ModflowGwtdis( + gwt, + nlay=nlay, + nrow=nrow, + ncol=ncol, + delr=delr, + delc=delc, + top=top, + botm=botm, + ) + + # initial conditions + ic = flopy.mf6.ModflowGwtic(gwt, strt=0.0) + + # advection + adv = flopy.mf6.ModflowGwtadv(gwt, scheme="upstream") + + # mass storage and transfer + mst = flopy.mf6.ModflowGwtmst(gwt, porosity=0.1) + + # sources + ssm = flopy.mf6.ModflowGwtssm(gwt, sources=sourcerecarray) + + # output control + oc = flopy.mf6.ModflowGwtoc( + gwt, + budget_filerecord="{}.cbc".format(gwtname), + concentration_filerecord="{}.ucn".format(gwtname), + concentrationprintrecord=[ + ("COLUMNS", 10, "WIDTH", 15, "DIGITS", 6, "GENERAL") + ], + saverecord=[("CONCENTRATION", "LAST"), ("BUDGET", "LAST")], + printrecord=[("CONCENTRATION", "LAST"), ("BUDGET", "LAST")], + ) + return gwt + + +def test_multi_model(): + # init paths + test_ex_name = "test_multi_model" + model_names = ["gwf_model_1", "gwf_model_2", "gwt_model_1", "gwt_model_2"] + + run_folder = os.path.join(cpth, test_ex_name) + if not os.path.isdir(run_folder): + os.makedirs(run_folder) + + # temporal discretization + nper = 1 + perlen = [5.0] + nstp = [200] + tsmult = [1.0] + tdis_rc = [] + for i in range(nper): + tdis_rc.append((perlen[i], nstp[i], tsmult[i])) + + # build MODFLOW 6 files + ws = dir + sim = flopy.mf6.MFSimulation( + sim_name=test_ex_name, version="mf6", exe_name="mf6", sim_ws=run_folder + ) + # create tdis package + tdis = flopy.mf6.ModflowTdis( + sim, time_units="DAYS", nper=nper, perioddata=tdis_rc, pname="sim.tdis" + ) + + # grid information + nlay, nrow, ncol = 1, 1, 50 + + # Create gwf1 model + welspd = {0: [[(0, 0, 0), 1.0, 1.0]]} + chdspd = None + gwf1 = get_gwf_model( + sim, + model_names[0], + model_names[0], + (nlay, nrow, ncol), + chdspd=chdspd, + welspd=welspd, + ) + + # Create gwf2 model + welspd = {0: [[(0, 0, 1), 0.5, 0.5]]} + chdspd = {0: [[(0, 0, ncol - 1), 0.0000000]]} + gwf2 = get_gwf_model( + sim, + model_names[1], + model_names[1], + (nlay, nrow, ncol), + chdspd=chdspd, + welspd=welspd, + ) + + # gwf-gwf + gwfgwf_data = [[(0, 0, ncol - 1), (0, 0, 0), 1, 0.5, 0.5, 1.0, 0.0, 1.0]] + gwfgwf = flopy.mf6.ModflowGwfgwf( + sim, + exgtype="GWF6-GWF6", + nexg=len(gwfgwf_data), + exgmnamea=gwf1.name, + exgmnameb=gwf2.name, + exchangedata=gwfgwf_data, + auxiliary=["ANGLDEGX", "CDIST"], + filename="flow1_flow2.gwfgwf", + ) + + # Create gwt model + sourcerecarray = [("WEL-1", "AUX", "CONCENTRATION")] + gwt = get_gwt_model( + sim, + model_names[2], + model_names[2], + (nlay, nrow, ncol), + sourcerecarray=sourcerecarray, + ) + + # GWF GWT exchange + gwfgwt = flopy.mf6.ModflowGwfgwt( + sim, + exgtype="GWF6-GWT6", + exgmnamea=model_names[0], + exgmnameb=model_names[2], + filename="flow1_transport1.gwfgwt", + ) + + # solver settings + nouter, ninner = 100, 300 + hclose, rclose, relax = 1e-6, 1e-6, 1.0 + + # create iterative model solution and register the gwf model with it + imsgwf = flopy.mf6.ModflowIms( + sim, + print_option="SUMMARY", + outer_dvclose=hclose, + outer_maximum=nouter, + under_relaxation="NONE", + inner_maximum=ninner, + inner_dvclose=hclose, + rcloserecord=rclose, + linear_acceleration="CG", + scaling_method="NONE", + reordering_method="NONE", + relaxation_factor=relax, + filename="flow.ims", + ) + + # create iterative model solution and register the gwt model with it + imsgwt = flopy.mf6.ModflowIms( + sim, + print_option="SUMMARY", + outer_dvclose=hclose, + outer_maximum=nouter, + under_relaxation="NONE", + inner_maximum=ninner, + inner_dvclose=hclose, + rcloserecord=rclose, + linear_acceleration="BICGSTAB", + scaling_method="NONE", + reordering_method="NONE", + relaxation_factor=relax, + filename="transport.ims", + ) + sim.register_ims_package(imsgwt, [gwt.name]) + + sim.write_simulation() + if run: + sim.run_simulation() + + # reload simulation + sim2 = MFSimulation.load(sim_ws=run_folder) + + # check ims registration + solution_recarray = sim2.name_file.solutiongroup + for solution_group_num in solution_recarray.get_active_key_list(): + rec_array = solution_recarray.get_data(solution_group_num[0]) + assert rec_array[0][1] == "flow.ims" + assert rec_array[0][2] == model_names[0] + assert rec_array[0][3] == model_names[1] + assert rec_array[1][1] == "transport.ims" + assert rec_array[1][2] == model_names[2] + + # create a new gwt model + sourcerecarray = [("WEL-1", "AUX", "CONCENTRATION")] + gwt_2 = get_gwt_model( + sim, + model_names[3], + model_names[3], + (nlay, nrow, ncol), + sourcerecarray=sourcerecarray, + ) + # register gwt model with transport.ims + sim.register_ims_package(imsgwt, gwt_2.name) + # flow and transport exchange + gwfgwt = flopy.mf6.ModflowGwfgwt( + sim, + exgtype="GWF6-GWT6", + exgmnamea=model_names[1], + exgmnameb=model_names[3], + filename="flow2_transport2.gwfgwt", + ) + # save and run updated model + sim.write_simulation() + sim.run_simulation() + + def test_np001(): # init paths test_ex_name = "np001" @@ -3251,6 +3545,7 @@ def test_transport(): if __name__ == "__main__": + test_multi_model() test_np001() test_np002() test004_bcfss() diff --git a/flopy/mf6/data/mfdatalist.py b/flopy/mf6/data/mfdatalist.py index eae0a66b7b..34308cbcd8 100644 --- a/flopy/mf6/data/mfdatalist.py +++ b/flopy/mf6/data/mfdatalist.py @@ -1721,12 +1721,21 @@ def set_data(self, data, key=None, autofill=False): if isinstance(data, list) and len(data) == 0: self.empty_keys[key] = True else: + check = True + if ( + isinstance(data, list) + and len(data) > 0 + and data[0] == "no_check" + ): + # not checking data + check = False + data = data[1:] self.empty_keys[key] = False if data is None: self.remove_transient_key(key) else: self._set_data_prep(data, key) - super().set_data(data, autofill) + super().set_data(data, autofill, check_data=check) self._cache_model_grid = False def get_file_entry( diff --git a/flopy/mf6/mfmodel.py b/flopy/mf6/mfmodel.py index 32b25bfda7..d12c3f570f 100644 --- a/flopy/mf6/mfmodel.py +++ b/flopy/mf6/mfmodel.py @@ -1043,13 +1043,13 @@ def set_model_relative_path(self, model_ws): # update package file locations in model name file packages = self.name_file.packages packages_data = packages.get_data() - for index, entry in enumerate(packages_data): - old_package_name = os.path.split(entry[1])[1] - packages_data[index][1] = os.path.join( - path, old_package_name - ) - packages.set_data(packages_data) - + if packages_data is not None: + for index, entry in enumerate(packages_data): + old_package_name = os.path.split(entry[1])[1] + packages_data[index][1] = os.path.join( + path, old_package_name + ) + packages.set_data(packages_data) # update files referenced from within packages for package in self.packagelist: package.set_model_relative_path(model_ws) @@ -1109,7 +1109,8 @@ def remove_package(self, package_name): try: new_rec_array = None for item in package_data: - if item[1] != package._filename: + filename = os.path.basename(item[1]) + if filename != package.filename: if new_rec_array is None: new_rec_array = np.rec.array( [item.tolist()], package_data.dtype @@ -1397,10 +1398,15 @@ def register_package( pkg_type = pkg_type[0:-1] # Model Assumption - assuming all name files have a package # recarray + file_mgr = self.simulation_data.mfpath + model_rel_path = file_mgr.model_relative_path[self.name] + package_rel_path = os.path.join( + model_rel_path, package.filename + ) self.name_file.packages.update_record( [ f"{pkg_type}6", - package._filename, + package_rel_path, package.package_name, ], 0, diff --git a/flopy/mf6/mfpackage.py b/flopy/mf6/mfpackage.py index c5b33f9925..8b6543c551 100644 --- a/flopy/mf6/mfpackage.py +++ b/flopy/mf6/mfpackage.py @@ -1546,9 +1546,17 @@ def __init__( self.package_name = None if filename is None: - self._filename = MFFileMgmt.string_to_file_path( - f"{self.model_or_sim.name}.{package_type}" - ) + if model_or_sim.type == "Simulation": + # filename uses simulation base name + base_name = os.path.basename( + os.path.normpath(self.model_or_sim.name) + ) + self._filename = f"{base_name}.{package_type}" + else: + # filename uses model base name + self._filename = MFFileMgmt.string_to_file_path( + f"{self.model_or_sim.name}.{package_type}" + ) else: if not isinstance(filename, str): message = ( diff --git a/flopy/mf6/modflow/mfsimulation.py b/flopy/mf6/modflow/mfsimulation.py index b19fa03bcd..38eb72682b 100644 --- a/flopy/mf6/modflow/mfsimulation.py +++ b/flopy/mf6/modflow/mfsimulation.py @@ -1065,6 +1065,12 @@ def register_ims_package(self, ims_file, model_list): self.simulation_data.debug, ) + # remove models from existing solution groups + if model_list is not None: + for model in model_list: + self._remove_from_all_ims_solution_groups(model) + + # register ims package with model list in_simulation = False pkg_with_same_name = None for file in self._ims_files.values(): @@ -2010,7 +2016,7 @@ def register_package( # unregistered model unregistered_models = [] for model in self._models: - model_registered = self._is_in_solution_group(model, 2) + model_registered = self._is_in_solution_group(model, 2, True) if not model_registered: unregistered_models.append(model) if unregistered_models: @@ -2125,7 +2131,7 @@ def get_ims_package(self, key): Parameters ---------- key : str - ims package key + ims package file name Returns -------- @@ -2244,7 +2250,38 @@ def _update_ims_solution_group(self, ims_file, new_name=None): solution_recarray.set_data(new_array, solution_group_num[0]) + def _remove_from_all_ims_solution_groups(self, modelname): + solution_recarray = self.name_file.solutiongroup + for solution_group_num in solution_recarray.get_active_key_list(): + try: + rec_array = solution_recarray.get_data(solution_group_num[0]) + except MFDataException as mfde: + message = ( + "An error occurred while getting solution group" + '"{}" from the simulation name file' + ".".format(solution_group_num[0]) + ) + raise MFDataException( + mfdata_except=mfde, package="nam", message=message + ) + new_array = ["no_check"] + for index, record in enumerate(rec_array): + new_record = [] + new_record.append(record[0]) + new_record.append(record[1]) + for item in list(record)[2:]: + if item is not None and item.lower() != modelname.lower(): + new_record.append(item) + new_array.append(tuple(new_record)) + solution_recarray.set_data(new_array, solution_group_num[0]) + def _append_to_ims_solution_group(self, ims_file, new_models): + # clear models out of solution groups + if new_models is not None: + for model in new_models: + self._remove_from_all_ims_solution_groups(model) + + # append models to ims_file solution_recarray = self.name_file.solutiongroup for solution_group_num in solution_recarray.get_active_key_list(): try: @@ -2263,7 +2300,9 @@ def _append_to_ims_solution_group(self, ims_file, new_models): new_record = [] rec_model_dict = {} for index, item in enumerate(record): - if record[1] == ims_file or item not in new_models: + if ( + record[1] == ims_file or item not in new_models + ) and item is not None: new_record.append(item) if index > 1 and item is not None: rec_model_dict[item.lower()] = 1 @@ -2298,7 +2337,7 @@ def _replace_ims_in_solution_group(self, item, index, new_item): if rec_item[index] == item: rec_item[index] = new_item - def _is_in_solution_group(self, item, index): + def _is_in_solution_group(self, item, index, any_idx_after=False): solution_recarray = self.name_file.solutiongroup for solution_group_num in solution_recarray.get_active_key_list(): try: @@ -2317,8 +2356,13 @@ def _is_in_solution_group(self, item, index): if rec_array is not None: for rec_item in rec_array: - if rec_item[index] == item: - return True + if any_idx_after: + for idx in range(index, len(rec_item)): + if rec_item[idx] == item: + return True + else: + if rec_item[index] == item: + return True return False def plot(self, model_list=None, SelPackList=None, **kwargs):