From 39e77c29aa4bf629e41c3fff2e0f93dbdbdf1f9a Mon Sep 17 00:00:00 2001 From: jlarsen-usgs Date: Thu, 3 Sep 2026 10:29:55 -0700 Subject: [PATCH 1/6] Fix(test_save_load_node_mapping_structured): update node_map to node_map_arr to follow new convention * remove sklearn requirement --- autotest/test_model_splitter.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/autotest/test_model_splitter.py b/autotest/test_model_splitter.py index 743371607..3fa50564d 100644 --- a/autotest/test_model_splitter.py +++ b/autotest/test_model_splitter.py @@ -221,7 +221,6 @@ def test_metis_splitting_with_lak_sfr(function_tmpdir): @requires_exe("mf6") @requires_pkg("pymetis") @requires_pkg("h5py") -@requires_pkg("scikit-learn", name_map={"scikit-learn": "sklearn"}) def test_save_load_node_mapping_structured(function_tmpdir): import pymetis @@ -245,19 +244,18 @@ def test_save_load_node_mapping_structured(function_tmpdir): new_sim.set_sim_path(new_sim_path) new_sim.write_simulation() new_sim.run_simulation() - original_node_map = mfsplit._node_map + original_node_map = mfsplit._node_map_arr mfsplit.save_node_mapping(hdf_file) new_sim2 = MFSimulation.load(sim_ws=new_sim_path) mfsplit2 = Mf6Splitter.load_node_mapping(hdf_file) - saved_node_map = mfsplit2._node_map + saved_node_map = mfsplit2._node_map_arr - for k, v1 in original_node_map.items(): - v2 = saved_node_map[k] - if not v1 == v2: - raise AssertionError("Node map read/write not returning proper values") + np.testing.assert_allclose( + original_node_map, saved_node_map, err_msg="Node map read/write not returning proper values" + ) array_dict = {} for model in range(nparts): @@ -267,6 +265,7 @@ def test_save_load_node_mapping_structured(function_tmpdir): new_heads = mfsplit2.reconstruct_array(array_dict) err_msg = "Heads from original and split models do not match" + original_heads[sim.get_model().modelgrid.idomain == 0] = 0 np.testing.assert_allclose(new_heads, original_heads, err_msg=err_msg) From 9b24dfea214e2e6fc3699115aea78f3c35546ee0 Mon Sep 17 00:00:00 2001 From: jlarsen-usgs Date: Thu, 3 Sep 2026 10:41:29 -0700 Subject: [PATCH 2/6] linting --- autotest/test_model_splitter.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/autotest/test_model_splitter.py b/autotest/test_model_splitter.py index 3fa50564d..9ca3d9396 100644 --- a/autotest/test_model_splitter.py +++ b/autotest/test_model_splitter.py @@ -254,7 +254,9 @@ def test_save_load_node_mapping_structured(function_tmpdir): saved_node_map = mfsplit2._node_map_arr np.testing.assert_allclose( - original_node_map, saved_node_map, err_msg="Node map read/write not returning proper values" + original_node_map, + saved_node_map, + err_msg="Node map read/write not returning proper values", ) array_dict = {} From 7fa9db93b901d7ec92cd8473a8c7502261bef0c8 Mon Sep 17 00:00:00 2001 From: jlarsen-usgs Date: Thu, 3 Sep 2026 17:04:21 -0700 Subject: [PATCH 3/6] update(_remap_sfr): add more robust handling for (0, 0, 0) SFR cells * add logic to traverse the connectiondata graph to find the nearest connected GWF/SFR cell in which (0, 0, 0) cells are routed to --- flopy/mf6/utils/model_splitter.py | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/flopy/mf6/utils/model_splitter.py b/flopy/mf6/utils/model_splitter.py index d2676a712..ba2db0521 100644 --- a/flopy/mf6/utils/model_splitter.py +++ b/flopy/mf6/utils/model_splitter.py @@ -2109,8 +2109,25 @@ def _remap_sfr(self, package, mapped_data): messy_rch = packagedata.ifno[messy_idx] # store these for later rcids = [] for rch in messy_rch: - con = np.abs(connectiondata[connectiondata.ifno == rch].ic_0)[0] - cid = packagedata[packagedata.ifno == con].cellid[0] + rch_cons = tuple(connectiondata[connectiondata.ifno == rch][0])[1:] + stack = [np.abs(i) for i in rch_cons if not np.isnan(i)] + visited = [rch,] + while stack: + con = stack.pop(0) + cid = packagedata[packagedata.ifno == con].cellid[0] + if cid in (messy_val, "None", None): + rch_cons = tuple(connectiondata[connectiondata.ifno == con][0])[1:] + for nc in rch_cons: + nc = np.abs(nc) + if np.isnan(nc) or nc in visited: + continue + stack.append(nc) + visited.append(con) + else: + break + + # con = np.abs(connectiondata[connectiondata.ifno == rch].ic_0)[0] + # cid = packagedata[packagedata.ifno == con].cellid[0] # use absolute value in case this is connected to cell(s) that are # also not connected to the model rcids.append(tuple(np.abs(cid))) From 789e0b6f312e3c6aaf1bc1b2a64f9588471e9b06 Mon Sep 17 00:00:00 2001 From: jlarsen-usgs Date: Thu, 3 Sep 2026 17:07:59 -0700 Subject: [PATCH 4/6] Remove commented out code --- flopy/mf6/utils/model_splitter.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/flopy/mf6/utils/model_splitter.py b/flopy/mf6/utils/model_splitter.py index ba2db0521..cf6cceb62 100644 --- a/flopy/mf6/utils/model_splitter.py +++ b/flopy/mf6/utils/model_splitter.py @@ -2126,10 +2126,8 @@ def _remap_sfr(self, package, mapped_data): else: break - # con = np.abs(connectiondata[connectiondata.ifno == rch].ic_0)[0] - # cid = packagedata[packagedata.ifno == con].cellid[0] - # use absolute value in case this is connected to cell(s) that are - # also not connected to the model + # use absolute value in case there are no connected cells + # that exchange with the GWF system rcids.append(tuple(np.abs(cid))) cellids[messy_idx] = rcids From 12b2102be7331656a696e1b7578874a0649b6dc1 Mon Sep 17 00:00:00 2001 From: jlarsen-usgs Date: Fri, 4 Sep 2026 09:48:55 -0700 Subject: [PATCH 5/6] Add testing for SFR None cells which are disconnected from the GWF system --- autotest/test_model_splitter.py | 160 ++++++++++++++++++++++++++++++++ 1 file changed, 160 insertions(+) diff --git a/autotest/test_model_splitter.py b/autotest/test_model_splitter.py index 4246d0513..ed506fb4f 100644 --- a/autotest/test_model_splitter.py +++ b/autotest/test_model_splitter.py @@ -1981,3 +1981,163 @@ def test_reconstruct_recarray(function_tmpdir): vrec = vrecarray[ix] for name in recarray.dtype.names: assert rec[name] == vrec[name], "Recarray reconstruction failed" + + +@requires_exe("mf6") +def test_sfr_none_cells(function_tmpdir): + sim_ws = function_tmpdir / "sfr_none_test" + split_ws = sim_ws / "split_model" + + # build the model + sim = flopy.mf6.MFSimulation(sim_ws=sim_ws) + tdis = flopy.mf6.ModflowTdis(sim, perioddata=[(365.0, 365, 1.0)]) + ims = flopy.mf6.ModflowIms(sim, complexity="SIMPLE") + gwf = flopy.mf6.ModflowGwf(sim) + + nlay, nrow, ncol = (1, 10, 10) + delc = np.full((nrow,), 100) + delr = np.full((ncol,), 100) + top = np.ones((nrow, ncol)) * np.linspace(100, 97, 10) + botm = np.zeros((nlay, nrow, ncol)) + idomain = np.ones(botm.shape, dtype=int) + + dis = flopy.mf6.ModflowGwfdis( + gwf, + nlay=nlay, + nrow=nrow, + ncol=ncol, + delc=delc, + delr=delr, + top=top, + botm=botm, + idomain=idomain, + ) + + npf = flopy.mf6.ModflowGwfnpf(gwf, icelltype=1, k=5.0, k33=1.0) + + sto = flopy.mf6.ModflowGwfsto( + gwf, + iconvert=1, + ss=1e-06, + sy=0.12, + steady_state=[False], + transient=[ + True, + ], + ) + + ic = flopy.mf6.ModflowGwfic(gwf, strt=top - 10) + + recs = [(0, i, 0, 90.0) for i in range(nrow)] + chd_l = flopy.mf6.ModflowGwfchd(gwf, stress_period_data={0: recs}, pname="chd_l") + + recs = [(0, i, 9, 85.0, 100 * 100 * 0.01) for i in range(nrow)] + ghb_l = flopy.mf6.ModflowGwfghb(gwf, stress_period_data={0: recs}, pname="ghb_r") + + sfr_con = [ + (0, -1), + (1, 0, -2), + (2, 1, -3), + (3, 2, 10, -4), + (4, 3, -5), + (5, 4, -6), + (6, 5, -7), + (7, 6, 12, -8), + (8, 7, -9), + (9, 8), + (10, 11, -3), + (11, -10), + (12, 13, -7), + (13, 14, -12), + (14, -13), + ] + + packagedata = [] + rgrd = np.round((top[0, 0] - top[0, 1]) / 100.0, 4) + for c, cons in enumerate(sfr_con): + ncon = len(cons) - 1 + if c < 10: + rec = ( + c, + (0, 4, c), + 100, + 5, + rgrd, + top[4, c] - 4, + 1.0, + 0.005, + 0.025, + ncon, + 1, + 0, + ) + elif c < 12: + rec = ( + c, + (-1, -1, -1), + 100, + 5, + rgrd, + top[4, 3], + 1.0, + 0.005, + 0.025, + ncon, + 1, + 0, + ) + else: + rec = ( + c, + (-1, -1, -1), + 100, + 5, + rgrd, + top[4, 7], + 1.0, + 0.005, + 0.025, + ncon, + 1, + 0, + ) + packagedata.append(rec) + + nreaches = len(packagedata) + + perioddata = [(0, "inflow", 100), (11, "inflow", 50), (14, "inflow", 75)] + + sfr = flopy.mf6.ModflowGwfsfr( + gwf, + nreaches=nreaches, + packagedata=packagedata, + connectiondata=sfr_con, + perioddata={0: perioddata}, + ) + + sim.write_simulation() + sim.run_simulation() + + # split the model so each section has a connected segment + # (multiple reaches) of SFR NONE cells + arr = np.zeros(top.shape, dtype=int) + arr[:, (ncol // 2) :] = 1 + + mfs = Mf6Splitter(sim) + new_sim = mfs.split_model(arr, sim_ws=split_ws) + new_sim.write_simulation() + new_sim.run_simulation() + + valid_nreach = [7, 8] + sfr_none_cells = [2, 3] + for ix in range(2): + nreach = valid_nreach[ix] + none_cells = sfr_none_cells[ix] + recarray = new_sim.get_model(f"model_{ix}").sfr.packagedata.array + assert len(recarray) == nreach, "Wrong number of SFR reaches assinged to model" + + none_cnt = 0 + for cid in recarray.cellid: + if cid == (-1, -1, -1): + none_cnt += 1 + assert none_cnt == none_cells, "Splitter not correctly assigning SFR None cells" From 9a03b08816f62e33524fc5d38a1d2096d83a1958 Mon Sep 17 00:00:00 2001 From: jlarsen-usgs Date: Fri, 4 Sep 2026 09:50:22 -0700 Subject: [PATCH 6/6] fix spelling --- autotest/test_model_splitter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/autotest/test_model_splitter.py b/autotest/test_model_splitter.py index ed506fb4f..e8b3b0867 100644 --- a/autotest/test_model_splitter.py +++ b/autotest/test_model_splitter.py @@ -2134,7 +2134,7 @@ def test_sfr_none_cells(function_tmpdir): nreach = valid_nreach[ix] none_cells = sfr_none_cells[ix] recarray = new_sim.get_model(f"model_{ix}").sfr.packagedata.array - assert len(recarray) == nreach, "Wrong number of SFR reaches assinged to model" + assert len(recarray) == nreach, "Wrong number of SFR reaches assigned to model" none_cnt = 0 for cid in recarray.cellid: