diff --git a/docs/examples/circle.ipynb b/docs/examples/circle.ipynb index 7cf0e632..11b5f9b1 100644 --- a/docs/examples/circle.ipynb +++ b/docs/examples/circle.ipynb @@ -863,8 +863,8 @@ "sim = flopy4.mf6.simulation.Simulation(\n", " name=\"circle\",\n", " tdis=tdis,\n", - " models={\"gwf\": gwf},\n", - " solutiongroup={\"ims\": ims},\n", + " models=[gwf],\n", + " solutiongroup=[ims],\n", " workspace=workspace,\n", ")" ] diff --git a/docs/examples/circle.py b/docs/examples/circle.py index 348bffd8..4b3390fc 100644 --- a/docs/examples/circle.py +++ b/docs/examples/circle.py @@ -264,8 +264,8 @@ def plot_head_ugrid(head, cbc, workspace): sim = flopy4.mf6.simulation.Simulation( name="circle", tdis=tdis, - models={"gwf": gwf}, - solutiongroup={"ims": ims}, + models=[gwf], + solutiongroup=[ims], workspace=workspace, ) diff --git a/docs/examples/frenchman-flat.ipynb b/docs/examples/frenchman-flat.ipynb index 42f45def..9d9f28f3 100644 --- a/docs/examples/frenchman-flat.ipynb +++ b/docs/examples/frenchman-flat.ipynb @@ -962,6 +962,7 @@ "source": [ "# assemble GWF model from all packages defined above.\n", "gwf = flopy4.mf6.gwf.Gwf(\n", + " name=\"ff\",\n", " dis=dis,\n", " ic=ic,\n", " npf=npf,\n", @@ -1094,8 +1095,8 @@ "sim = flopy4.mf6.simulation.Simulation(\n", " name=\"ff\",\n", " tdis=tdis,\n", - " models={\"ff\": gwf},\n", - " solutiongroup={\"ims\": ims},\n", + " models=[gwf],\n", + " solutiongroup=[ims],\n", " workspace=workspace,\n", ")" ] diff --git a/docs/examples/frenchman-flat.py b/docs/examples/frenchman-flat.py index b54fdc60..1e21a248 100644 --- a/docs/examples/frenchman-flat.py +++ b/docs/examples/frenchman-flat.py @@ -614,6 +614,7 @@ def plot_head_ugrid(head, cbc, grid, workspace): # assemble GWF model from all packages defined above. gwf = flopy4.mf6.gwf.Gwf( + name="ff", dis=dis, ic=ic, npf=npf, @@ -662,8 +663,8 @@ def plot_head_ugrid(head, cbc, grid, workspace): sim = flopy4.mf6.simulation.Simulation( name="ff", tdis=tdis, - models={"ff": gwf}, - solutiongroup={"ims": ims}, + models=[gwf], + solutiongroup=[ims], workspace=workspace, ) diff --git a/docs/examples/twri.ipynb b/docs/examples/twri.ipynb index 8e3440d9..285beece 100644 --- a/docs/examples/twri.ipynb +++ b/docs/examples/twri.ipynb @@ -605,8 +605,8 @@ "sim = flopy4.mf6.simulation.Simulation(\n", " name=\"twri\",\n", " tdis=tdis,\n", - " models={\"gwf\": gwf},\n", - " solutiongroup={\"ims\": ims},\n", + " models=[gwf],\n", + " solutiongroup=[ims],\n", " workspace=workspace,\n", ")" ] diff --git a/docs/examples/twri.py b/docs/examples/twri.py index f176cfd2..7bb3a97d 100644 --- a/docs/examples/twri.py +++ b/docs/examples/twri.py @@ -224,8 +224,8 @@ def plot_head(head, workspace): sim = flopy4.mf6.simulation.Simulation( name="twri", tdis=tdis, - models={"gwf": gwf}, - solutiongroup={"ims": ims}, + models=[gwf], + solutiongroup=[ims], workspace=workspace, ) diff --git a/docs/profile/diag_list_scaling.py b/docs/profile/diag_list_scaling.py index 15b39022..c3b77578 100644 --- a/docs/profile/diag_list_scaling.py +++ b/docs/profile/diag_list_scaling.py @@ -61,6 +61,7 @@ def build_sim(n: int, ws: Path): dims = {"nper": nper, "ncpl": nrow * n, **dict(grid.dataset.sizes)} rch = flopy4.mf6.gwf.Rch(recharge={0: {(0, 0, c): 0.001 for c in range(n)}}, dims=dims) gwf = flopy4.mf6.gwf.Gwf( + name="diag", dis=grid, ic=flopy4.mf6.gwf.Ic(strt=0.0, dims=dims), npf=flopy4.mf6.gwf.Npf( @@ -86,8 +87,8 @@ def build_sim(n: int, ws: Path): tdis=flopy4.mf6.simulation.Tdis.from_time( flopy4.mf6.utils.time.Time(perlen=[1.0], nstp=[1], tsmult=[1.0]) ), - models={"diag": gwf}, - solutiongroup={"ims": ims}, + models=[gwf], + solutiongroup=[ims], workspace=ws, ) diff --git a/docs/profile/ff_write.py b/docs/profile/ff_write.py index 54e9cf04..910fb3fb 100644 --- a/docs/profile/ff_write.py +++ b/docs/profile/ff_write.py @@ -272,6 +272,7 @@ def build_flopy4_base(k, k33, ss): for name, data in zip(_WEL_NAMES, _WEL_DICTS) ] gwf = flopy4.mf6.gwf.Gwf( + name="ff", dis=grid, ic=ic, npf=npf, @@ -321,8 +322,8 @@ def main(): sim = flopy4.mf6.simulation.Simulation( name="ff", tdis=tdis, - models={"ff": gwf}, - solutiongroup={"ims": ims}, + models=[gwf], + solutiongroup=[ims], workspace=ws, ) if args.profile: diff --git a/docs/profile/test1000_write.py b/docs/profile/test1000_write.py index 9515dbe9..2701cac3 100644 --- a/docs/profile/test1000_write.py +++ b/docs/profile/test1000_write.py @@ -130,12 +130,13 @@ def make_base4(): return dis, ic, npf, sto, oc def make_sim4(ws, gwf): + gwf.name = "test1000" ws.mkdir(parents=True, exist_ok=True) return flopy4.mf6.simulation.Simulation( name="test1000", tdis=flopy4.mf6.simulation.Tdis.from_time(time4), - models={"test1000": gwf}, - solutiongroup={"ims": make_ims4()}, + models=[gwf], + solutiongroup=[make_ims4(name="ims")], workspace=ws, ) diff --git a/docs/profile/test1005_write.py b/docs/profile/test1005_write.py index 69f7bef5..c7bf5ae5 100644 --- a/docs/profile/test1005_write.py +++ b/docs/profile/test1005_write.py @@ -121,12 +121,13 @@ def make_base4(): return dis, ic, npf, sto, oc def make_sim4(ws, gwf): + gwf.name = "test1005" ws.mkdir(parents=True, exist_ok=True) return flopy4.mf6.simulation.Simulation( name="test1005", tdis=flopy4.mf6.simulation.Tdis.from_time(time4), - models={"test1005": gwf}, - solutiongroup={"ims": make_ims4()}, + models=[gwf], + solutiongroup=[make_ims4(name="ims")], workspace=ws, ) diff --git a/flopy4/attrs_xarray.py b/flopy4/attrs_xarray.py index e116ae30..f2bcc1ac 100644 --- a/flopy4/attrs_xarray.py +++ b/flopy4/attrs_xarray.py @@ -21,12 +21,10 @@ did, it would need a different name or a second, explicit exclusion mechanism here. -Known limitation: a "dict"-kind child field (e.g. `Simulation.models`) -can't be reconstructed from a plain `DataTree` alone -- the dict's real -keys are caller-given names with no recoverable relationship to the -field name, unlike "list"-kind's positional `f"{field_name}{index}"` -convention. Namefile binding rows carry this information separately; -reconstruction should use that rather than guessing here. Likewise, a +Known limitation: a "dict"-kind child field can't be reconstructed +from a plain `DataTree` alone -- the dict's real keys are caller-given +names with no recoverable relationship to the field name, unlike +"list"-kind's positional `f"{field_name}{index}"` convention. Likewise, a "list"-kind field whose element type is itself a `Union` of attrs classes (e.g. `list[Union[Chd, Chdg]]`) isn't resolved to a concrete arm. @@ -325,8 +323,9 @@ def child_field_candidates(field: attrs.Attribute) -> "tuple[str, tuple[type, .. annotation, or a type/collection-element that resolves to no attrs-decorated candidate at all. - Kind is `"only"`, `"list"`, or `"dict"`, matching the child-collection - vocabulary used throughout `flopy4/mf6/converter/`. + Kind is `"only"` or `"list"`, matching the child-collection + vocabulary used throughout `flopy4/mf6/converter/`. Components keep + collections of children in lists, so a dict isn't a child field. """ tp = field.type if tp is None or isinstance(tp, str): @@ -347,8 +346,6 @@ def child_field_candidates(field: attrs.Attribute) -> "tuple[str, tuple[type, .. candidates = tuple(a for a in get_args(elem) if attrs.has(a)) return ("list", candidates) if candidates else None return ("list", (elem,)) if attrs.has(elem) else None - if origin is dict and len(args) == 2 and attrs.has(args[1]): - return ("dict", (args[1],)) return None diff --git a/flopy4/mf6/component.py b/flopy4/mf6/component.py index 96458719..07a5dfe3 100644 --- a/flopy4/mf6/component.py +++ b/flopy4/mf6/component.py @@ -73,7 +73,21 @@ def _is_default_child_name(child: "Component") -> bool: return child.name == type(child).__name__.lower() # type: ignore[attr-defined] -def _resolve_child_name(used: "set[str]", kind: str, field_name: str, child: "Component") -> str: +def _names_by_class(candidates: "tuple[type, ...]") -> bool: + """Whether a list field's unnamed children are named after their own + class rather than the field: true for a field of an abstract base + (`Simulation.models`, a list of `Model`), whose name says nothing + about which kind of child it holds.""" + return not any("dfn_name" in c.__dict__ for c in candidates) + + +def _resolve_child_name( + used: "set[str]", + kind: str, + field_name: str, + child: "Component", + by_class: bool = False, +) -> str: """Resolve the name `child` should be attached under (stored as its own `.name`), given the set of names already claimed by any of the parent's other children (`used`). @@ -85,8 +99,10 @@ def _resolve_child_name(used: "set[str]", kind: str, field_name: str, child: "Co field pair like `chd: list[Union[Chd, Chdg]]` sharing one sequence, since both arms share one real MF6 namefile ftype (see `converter/binding.py`'s - `component_ftype()`). "dict"-kind isn't handled here -- its name is - the mapping key itself, resolved by the caller. + `component_ftype()`). With `by_class` (see `_names_by_class()`), an + unnamed child keeps its class-name default if it's free, else gets + `f"{name}{i}"` from 1, e.g. "gwf", "gwf1". The default can't be told + apart from the same name given explicitly, so it never collides. """ if kind not in ("only", "list"): raise TypeError(f"Bad child collection kind '{kind}'") @@ -99,6 +115,14 @@ def _resolve_child_name(used: "set[str]", kind: str, field_name: str, child: "Co return child.name # type: ignore[attr-defined] if kind == "only": return field_name + if by_class: + name = child.name # type: ignore[attr-defined] + if name not in used: + return name + i = 1 + while f"{name}{i}" in used: + i += 1 + return f"{name}{i}" i = 0 while f"{field_name}{i}" in used: i += 1 @@ -258,7 +282,7 @@ def _children(self) -> "dict[str, Component]": invalidation. Re-runs `_set_child_parents()` first (idempotent) so every reader - sees correctly-named children even if a list/dict field was + sees correctly-named children even if a list field was reassigned via plain attribute set (`gwf.wel = [...]`) rather than construction or `__setitem__` -- the one case that otherwise skips naming, leaving siblings collided on the shared class-name default @@ -294,15 +318,11 @@ def _children(self) -> "dict[str, Component]": result[child.name] = child for c in _row_children(child): result[c.name] = c - elif kind == "dict": - for child in value.values(): - if isinstance(child, Component): - result[child.name] = child return result def _set_child_parents(self) -> None: """Stamp `_parent` on every already-populated Component-typed - field (single, list, or dict); see `_parent`'s own docstring + field (single or list); see `_parent`'s own docstring above. Also resolves and stamps each child's `.name` -- `_resolve_child_name()`'s top-down counterpart to `_attach_to_parent_field()`'s bottom-up one. @@ -329,7 +349,7 @@ def _set_child_parents(self) -> None: value = getattr(self, f.name, None) if value is None: continue - kind, _ = spec + kind, candidates = spec if kind == "only": if isinstance(value, Component): @@ -337,25 +357,17 @@ def _set_child_parents(self) -> None: value.name = _resolve_child_name(used, kind, f.name, value) # type: ignore[attr-defined] used.add(value.name) # type: ignore[attr-defined] elif kind == "list": + by_class = _names_by_class(candidates) for child in value: for c in [child] if isinstance(child, Component) else _row_children(child): if id(c) in stamped: continue stamped.add(id(c)) c.__dict__["_parent"] = self - c.name = _resolve_child_name(used, kind, f.name, c) # type: ignore[attr-defined] + c.name = _resolve_child_name( # type: ignore[attr-defined] + used, kind, f.name, c, by_class=by_class and c is child + ) used.add(c.name) # type: ignore[attr-defined] - elif kind == "dict": - for key, child in value.items(): - if isinstance(child, Component): - child.__dict__["_parent"] = self - if key in used: - raise ValueError( - f"Child name '{key}' collides with an " - "existing child on the same parent." - ) - child.name = key # type: ignore[attr-defined] - used.add(child.name) # type: ignore[attr-defined] @property def path(self) -> Path: @@ -421,19 +433,14 @@ def _attach_to_parent_field(self, parent: "Component") -> None: self.name = _resolve_child_name(used, kind, target_field.name, self) # type: ignore[attr-defined] setattr(parent, target_field.name, self) elif kind == "list": - self.name = _resolve_child_name(used, kind, target_field.name, self) # type: ignore[attr-defined] + from flopy4.attrs_xarray import child_field_candidates + + spec = child_field_candidates(target_field) + assert spec is not None + self.name = _resolve_child_name( # type: ignore[attr-defined] + used, kind, target_field.name, self, by_class=_names_by_class(spec[1]) + ) getattr(parent, target_field.name).append(self) - elif kind == "dict": - # No positional auto-key to fall back on for an unnamed child, - # unlike "only"/"list" -- see `_set_child_parents`'s "dict" - # branch: the child's own `.name` (explicit, or its - # class-name default) is the key. - key = self.name # type: ignore[attr-defined] - if key in used: - raise ValueError( - f"Child name '{key}' collides with an existing child on the same parent." - ) - getattr(parent, target_field.name)[key] = self @classmethod def __attrs_init_subclass__(cls): @@ -482,12 +489,6 @@ def __setitem__(self, key, value): value.__dict__["_parent"] = self current[i] = value return - elif kind == "dict": - if current and key in current: - value.name = key # type: ignore[attr-defined] - value.__dict__["_parent"] = self - current[key] = value - return match = _find_child_field(type(self), type(value)) if match is None: @@ -499,8 +500,6 @@ def __setitem__(self, key, value): setattr(self, target_field.name, value) elif kind == "list": getattr(self, target_field.name).append(value) - elif kind == "dict": - getattr(self, target_field.name)[key] = value def __delitem__(self, key): """Detach the child named `key`, from whatever field/slot @@ -522,10 +521,6 @@ def __delitem__(self, key): if isinstance(child, Component) and child.name == key: # type: ignore[attr-defined] del value[i] return - elif kind == "dict": - if value and key in value: - del value[key] - return raise KeyError(key) def __iter__(self): diff --git a/flopy4/mf6/converter/egress/unstructure.py b/flopy4/mf6/converter/egress/unstructure.py index a20ba4e9..4a070f38 100644 --- a/flopy4/mf6/converter/egress/unstructure.py +++ b/flopy4/mf6/converter/egress/unstructure.py @@ -76,12 +76,6 @@ def _make_binding_blocks(value: Component) -> dict[str, dict[str, list[tuple[str match child: case Component(): blocks[block_name][child_name] = [Binding.from_component(child).to_tuple()] - case Mapping(): - bindings = [ - Binding.from_component(c).to_tuple() for c in child.values() if c is not None - ] - if bindings: - blocks[block_name][child_name] = bindings case Iterable(): bindings = [Binding.from_component(c).to_tuple() for c in child if c is not None] if bindings: diff --git a/flopy4/mf6/converter/ingress/structure.py b/flopy4/mf6/converter/ingress/structure.py index e1997cdd..587e98f7 100644 --- a/flopy4/mf6/converter/ingress/structure.py +++ b/flopy4/mf6/converter/ingress/structure.py @@ -475,10 +475,10 @@ def _disambiguate_ga_variant(candidates: "list[type[Component]]", path: Path) -> return candidates[0] -def _exchange_models(model_names: list, models: Mapping[str, Any]) -> tuple[Any, Any] | None: +def _exchange_models(model_names: list, models: list) -> tuple[Any, Any] | None: """The models an exchange row names, looked up case-insensitively (as MF6 does) among those loaded so far, or None if either isn't.""" - by_name = {str(name).lower(): model for name, model in models.items()} + by_name = {str(model.name).lower(): model for model in models} found = [by_name.get(str(name).lower()) for name in model_names[:2]] if len(found) < 2 or None in found: return None @@ -619,7 +619,7 @@ def _resolve_bindings(cls: type, raw_lower: dict, context: LoadContext) -> dict[ dims=dims, parent=None, exchange=( - _exchange_models(row[2:], kwargs.get("models", {})) + _exchange_models(row[2:], kwargs.get("models", [])) if issubclass(target_cls, Exchange) else None ), @@ -634,16 +634,6 @@ def _resolve_bindings(cls: type, raw_lower: dict, context: LoadContext) -> dict[ collectors[child_name] = child elif kind == "list": collectors.setdefault(child_name, []).append(child) - elif kind == "dict": - # pname when there is one (matches the child's own real - # name, e.g. Simulation.models); row fname as a fallback - # for rows with no pname (e.g. solutiongroup, whose row[2:] - # are applicable model names, not a pname -- see pname - # above). This key is NOT cosmetic: child attachment - # reconciles a dict-kind child's .name to match the key - # it's placed under, overriding whatever name= was passed - # to load() above (see Component._attach_to_parent_field). - collectors.setdefault(child_name, {})[pname or fname] = child kwargs.update(collectors) diff --git a/flopy4/mf6/simulation.py b/flopy4/mf6/simulation.py index 018f4703..0c8a5083 100644 --- a/flopy4/mf6/simulation.py +++ b/flopy4/mf6/simulation.py @@ -55,12 +55,12 @@ class Simulation(SimulationMethods, Context): ) hpc: Optional[Hpc] = child(block="options", keyword="hpc6", direction="in") tdis: Tdis = child(block="timing", keyword="tdis6", default=attrs.Factory(Tdis)) - models: dict[str, Model] = child(block="models", default=attrs.Factory(dict)) - exchanges: dict[str, Exchange] = child(block="exchanges", default=attrs.Factory(dict)) + models: list[Model] = child(block="models", default=attrs.Factory(list)) + exchanges: list[Exchange] = child(block="exchanges", default=attrs.Factory(list)) mxiter: Optional[int] = field( default=None, block="solutiongroup", optional=True, longname="maximum solution group iterations", ) - solutiongroup: dict[str, Solution] = child(block="solutiongroup", default=attrs.Factory(dict)) + solutiongroup: list[Solution] = child(block="solutiongroup", default=attrs.Factory(list)) diff --git a/flopy4/mf6/spec.py b/flopy4/mf6/spec.py index 2982c1bd..875f1c61 100644 --- a/flopy4/mf6/spec.py +++ b/flopy4/mf6/spec.py @@ -211,18 +211,20 @@ def _coerce_one(value, candidates: tuple[type, ...]): def coerce_child(value, field: Attribute): """Convert a child field's value with its candidate classes' `coerce` classmethods, which return an instance or `None` if they don't accept - the value. Lists and dicts are converted element-wise.""" + the value. Lists are converted element-wise, and a single value given + to a list field is wrapped in one.""" from flopy4.attrs_xarray import child_field_candidates if (spec := child_field_candidates(field)) is None: return value kind, candidates = spec - if kind == "list" and isinstance(value, (list, tuple)): + if kind == "list": + if value is None: + return value + if not isinstance(value, (list, tuple)): + return [_coerce_one(value, candidates)] items = [_coerce_one(v, candidates) for v in value] return value if all(a is b for a, b in zip(items, value)) else items - if kind == "dict" and isinstance(value, dict): - coerced = {k: _coerce_one(v, candidates) for k, v in value.items()} - return value if all(coerced[k] is v for k, v in value.items()) else coerced return _coerce_one(value, candidates) diff --git a/flopy4/mf6/utils/codegen/make.py b/flopy4/mf6/utils/codegen/make.py index 206a2ba0..00f0f12e 100644 --- a/flopy4/mf6/utils/codegen/make.py +++ b/flopy4/mf6/utils/codegen/make.py @@ -1223,8 +1223,8 @@ def _child_field_spec( return FieldSpec( dfn_name=f.name, py_name=filters.safe_name(f.name), - type_annotation=f"dict[str, {base}]", - spec_call=f'child(block="{block_name}", default=attrs.Factory(dict))', + type_annotation=f"list[{base}]", + spec_call=f'child(block="{block_name}", default=attrs.Factory(list))', generatable=True, ), [f"from {module} import {base}"] (target,) = targets diff --git a/test/mf6/test_mf6_adapters.py b/test/mf6/test_mf6_adapters.py index 8dfc240f..dacece84 100644 --- a/test/mf6/test_mf6_adapters.py +++ b/test/mf6/test_mf6_adapters.py @@ -23,7 +23,7 @@ def quickstart_model(): inner_dvclose=1e-6, linear_acceleration="cg", ) - sim = Simulation(name=name, tdis=tdis, solutiongroup={"ims": ims}) + sim = Simulation(name=name, tdis=tdis, solutiongroup=[ims]) dis = Dis(nrow=10, ncol=10) gwf = Gwf(parent=sim, name=name, save_flows=True, dis=dis) ic = Ic(parent=gwf) diff --git a/test/mf6/test_mf6_codec.py b/test/mf6/test_mf6_codec.py index be82b6b1..06a2d920 100644 --- a/test/mf6/test_mf6_codec.py +++ b/test/mf6/test_mf6_codec.py @@ -1404,9 +1404,9 @@ def test_dumps_simulation(): # Create simulation sim = Simulation( name="test_sim", - models={"model1": gwf}, - exchanges={}, - solutiongroup={}, + models=[gwf], + exchanges=[], + solutiongroup=[], tdis=tdis, ) diff --git a/test/mf6/test_mf6_codegen.py b/test/mf6/test_mf6_codegen.py index c547d4d2..eb1db922 100644 --- a/test/mf6/test_mf6_codegen.py +++ b/test/mf6/test_mf6_codegen.py @@ -388,9 +388,9 @@ def test_simulation_spec(all_dfns): types = {f.py_name: f.type_annotation for f in spec.fields} # binding lists become typed child fields assert types["tdis"] == "Tdis" - assert types["models"] == "dict[str, Model]" - assert types["exchanges"] == "dict[str, Exchange]" - assert types["solutiongroup"] == "dict[str, Solution]" + assert types["models"] == "list[Model]" + assert types["exchanges"] == "list[Exchange]" + assert types["solutiongroup"] == "list[Solution]" assert not spec.item_classes assert {"continue_", "nocheck", "maxerrors", "mxiter"} <= set(types) diff --git a/test/mf6/test_mf6_component.py b/test/mf6/test_mf6_component.py index a10cd16b..90edf8af 100644 --- a/test/mf6/test_mf6_component.py +++ b/test/mf6/test_mf6_component.py @@ -285,10 +285,10 @@ def test_init_sim_explicit_dims(): dims=dims, ) tdis = Tdis(dims=dims) - sim = Simulation(tdis=tdis, models={"gwf": gwf}) + sim = Simulation(tdis=tdis, models=[gwf]) assert sim.tdis is tdis - assert sim.models["gwf"] is gwf + assert sim["gwf"] is gwf assert sim._children["tdis"] is tdis assert sim._children["gwf"] is gwf assert gwf.dis is dis @@ -297,8 +297,66 @@ def test_init_sim_explicit_dims(): assert gwf.npf is npf assert gwf.chd[0] is chd # k is stored as a plain attrs field; use to_xarray() for Dataset access - assert np.array_equal(sim.models["gwf"].npf.k, np.ones(100)) - assert np.array_equal(sim.models["gwf"].npf.to_xarray()["k"].values, np.ones((1, 10, 10))) + assert np.array_equal(sim["gwf"].npf.k, np.ones(100)) + assert np.array_equal(sim["gwf"].npf.to_xarray()["k"].values, np.ones((1, 10, 10))) + + +def test_sim_children_list(): + gwf = Gwf(name="flow") + ims = Ims(name="solver") + sim = Simulation(models=[gwf], solutiongroup=[ims]) + + assert sim.models == [gwf] + assert sim.solutiongroup == [ims] + assert sim["flow"] is gwf + assert sim["solver"] is ims + assert gwf.parent is sim + + +def test_sim_children_single(): + gwf = Gwf(name="flow") + ims = Ims() + sim = Simulation(models=gwf, solutiongroup=ims) + + assert sim.models == [gwf] + assert sim.solutiongroup == [ims] + assert sim["flow"] is gwf + + +def test_sim_children_unnamed(): + from flopy4.mf6.gwt import Gwt + + sim = Simulation(models=[Gwf(), Gwt(), Gwf()], solutiongroup=[Ims()]) + + assert [m.name for m in sim.models] == ["gwf", "gwt", "gwf1"] + assert sim.solutiongroup[0].name == "ims" + + +def test_sim_children_name_collision(): + with pytest.raises(ValueError, match="collides"): + Simulation(models=[Gwf(name="flow"), Gwf(name="flow")]) + + +def test_sim_children_bottom_up(): + sim = Simulation() + gwf = Gwf(parent=sim, name="flow") + unnamed = Gwf(parent=sim) + + assert sim.models == [gwf, unnamed] + assert unnamed.name == "gwf" + with pytest.raises(ValueError, match="collides"): + Gwf(parent=sim, name="flow") + + +def test_sim_children_setitem_delitem(): + sim = Simulation(models=[Gwf(name="flow")]) + replacement = Gwf() + sim["flow"] = replacement + + assert sim.models == [replacement] + assert replacement.name == "flow" + del sim["flow"] + assert sim.models == [] def test_init_big_sim(): @@ -315,7 +373,7 @@ def test_init_big_sim(): stress_period_data={0: [[(0, 0, 0), 1.0], [(0, 9999, 9999), 0.0]]}, ) - assert sim.models["gwf"] is gwf + assert sim["gwf"] is gwf assert sim._children["gwf"] is gwf assert gwf.ic is ic assert gwf.oc is oc @@ -323,7 +381,7 @@ def test_init_big_sim(): assert gwf.chd[0] is chd # Without explicit dims, griddata scalars stay compact (no 100M allocation) - assert sim.models["gwf"].npf.k == 1.0 + assert sim["gwf"].npf.k == 1.0 # SPD is a recarray with only 2 rows — no full-grid allocation spd = chd.stress_period_data[0] @@ -364,7 +422,7 @@ def test_write_ascii(function_tmpdir): tdis=time, workspace=function_tmpdir, name=sim_name, - solutiongroup={"ims": ims}, + solutiongroup=[ims], ) gwf = Gwf(parent=sim, dis=dis, name=gwf_name) ic = Ic(parent=gwf) @@ -477,7 +535,7 @@ def test_to_dict_on_context(): inner_dvclose=1e-6, linear_acceleration="cg", ) - sim = Simulation(tdis=time, solutiongroup={"ims": ims}) + sim = Simulation(tdis=time, solutiongroup=[ims]) result = sim.to_dict() @@ -535,7 +593,7 @@ def test_to_xarray_on_context(function_tmpdir): inner_dvclose=1e-6, linear_acceleration="cg", ) - sim = Simulation(tdis=time, solutiongroup={"ims": ims}, workspace=function_tmpdir) + sim = Simulation(tdis=time, solutiongroup=[ims], workspace=function_tmpdir) dt = sim.to_xarray() assert isinstance(dt, xr.DataTree) assert isinstance(dt.kper, xr.DataArray) @@ -1363,13 +1421,13 @@ def test_ncf_subpackage_simulation_load(function_tmpdir): name="sim", workspace=function_tmpdir, tdis=Tdis(nper=1), - models={"gwf": gwf}, - solutiongroup={"ims": Ims()}, + models=[gwf], + solutiongroup=[Ims(name="ims")], ) sim.write() loaded = Simulation.load(function_tmpdir / "mfsim.nam") - dis = loaded.models["gwf"].dis + dis = loaded["gwf"].dis assert isinstance(dis.ncf, Ncf) assert dis.ncf.filename == ncf.filename np.testing.assert_array_equal(dis.ncf.latitude, ncf.latitude) @@ -1944,7 +2002,7 @@ def make(head): ic=Ic(strt=1.0), chd=[Chd(stress_period_data={0: [[(0, 0, 0), head]]})], ) - return Simulation(tdis=Tdis(nper=1), models={"gwf": gwf}, solutiongroup={"ims": Ims()}) + return Simulation(tdis=Tdis(nper=1), models=[gwf], solutiongroup=[Ims(name="ims")]) assert make(1.0) == make(1.0) assert make(1.0) != make(2.0) @@ -2112,8 +2170,8 @@ def _gwt_sim(workspace, **packages): name="sim", workspace=workspace, tdis=Tdis(nper=1), - models={"gwt": gwt}, - solutiongroup={"ims": Ims()}, + models=[gwt], + solutiongroup=[Ims(name="ims")], ) @@ -2134,7 +2192,7 @@ def test_gwt_fmi_ist_api_round_trip(function_tmpdir): assert text.count("IST6") == 2 assert text.count("API6") == 2 - gwt = Simulation.load(function_tmpdir / "mfsim.nam").models["gwt"] + gwt = Simulation.load(function_tmpdir / "mfsim.nam")["gwt"] assert gwt.fmi.flow_imbalance_correction assert [r.flowtype for r in gwt.fmi.packagedata] == ["GWFHEAD"] assert [r.fname for r in gwt.fmi.packagedata] == [Path("gwf.hds")] diff --git a/test/mf6/test_mf6_dimensions.py b/test/mf6/test_mf6_dimensions.py index f228e9b1..ba8f81f5 100644 --- a/test/mf6/test_mf6_dimensions.py +++ b/test/mf6/test_mf6_dimensions.py @@ -183,7 +183,7 @@ def test_model_in_simulation_can_access_tdis_dimensions(self): dis = Dis(nlay=3, nrow=10, ncol=20) gwf = Gwf(name="test", dis=dis) # Construct Simulation with models dict - parent is set automatically - sim = Simulation(name="test", tdis=tdis, models={"test": gwf}) + sim = Simulation(name="test", tdis=tdis, models=[gwf]) # Model should access its own grid dimensions assert gwf.resolve_dims("nlay") == {"nlay": 3} @@ -205,7 +205,7 @@ def test_package_resolves_both_grid_and_time_dimensions(self): tdis = Tdis(nper=10) dis = Dis(nlay=3, nrow=10, ncol=20) gwf = Gwf(name="test", dis=dis) - sim = Simulation(name="test", tdis=tdis, models={"test": gwf}) + sim = Simulation(name="test", tdis=tdis, models=[gwf]) # Model should resolve grid dimensions from its Dis assert gwf.resolve_dims("nlay") == {"nlay": 3} @@ -274,7 +274,7 @@ def test_model_resolve_grid_and_time_together(self): tdis = Tdis(nper=10) dis = Dis(nlay=3, nrow=10, ncol=20) gwf = Gwf(name="test", dis=dis) - sim = Simulation(name="test", tdis=tdis, models={"test": gwf}) + sim = Simulation(name="test", tdis=tdis, models=[gwf]) # Request both grid and time dimensions together result = gwf.resolve_dims("nlay", "nrow", "ncol", "nper", "nodes") diff --git a/test/mf6/test_mf6_integration.py b/test/mf6/test_mf6_integration.py index b39a70c1..50930bd5 100644 --- a/test/mf6/test_mf6_integration.py +++ b/test/mf6/test_mf6_integration.py @@ -82,7 +82,7 @@ def test_gwf_chd01(function_tmpdir): tdis=time, workspace=function_tmpdir, name=sim_name, - solutiongroup={"ims": ims}, + solutiongroup=[ims], ) dis = Dis( @@ -168,7 +168,7 @@ def test_gwf_disv(function_tmpdir): tdis=time, workspace=function_tmpdir, name=sim_name, - solutiongroup={"ims": ims}, + solutiongroup=[ims], ) nlay = 3 @@ -280,7 +280,7 @@ def test_gwf_disv_uzf(function_tmpdir): tdis=time, workspace=function_tmpdir, name=sim_name, - solutiongroup={"ims": ims}, + solutiongroup=[ims], ) cells = [ @@ -594,7 +594,7 @@ def test_quickstart(function_tmpdir): tdis=time, workspace=function_tmpdir, name=sim_name, - solutiongroup={"ims": ims}, + solutiongroup=[ims], ) gwf = Gwf(parent=sim, dis=dis, name=gwf_name) ic = Ic(parent=gwf) @@ -641,7 +641,7 @@ def test_quickstart_grid(function_tmpdir): tdis=time, workspace=function_tmpdir, name=sim_name, - solutiongroup={"ims": ims}, + solutiongroup=[ims], ) gwf = Gwf(parent=sim, dis=dis, name=gwf_name) ic = Ic(parent=gwf) @@ -706,7 +706,7 @@ def test_quickstart_grid_explicit_maxbound(function_tmpdir): tdis=time, workspace=function_tmpdir, name=sim_name, - solutiongroup={"ims": ims}, + solutiongroup=[ims], ) gwf = Gwf(parent=sim, dis=dis, name=gwf_name) Ic(parent=gwf) @@ -772,7 +772,7 @@ def test_quickstart_netcdf(function_tmpdir): tdis=time, workspace=function_tmpdir, name=sim_name, - solutiongroup={"ims": ims}, + solutiongroup=[ims], ) gwf = Gwf(parent=sim, dis=dis, name=gwf_name) ic = Ic(parent=gwf) @@ -885,7 +885,7 @@ def test_quickstart_netcdf_mesh(function_tmpdir): tdis=time, workspace=function_tmpdir, name=sim_name, - solutiongroup={"ims": ims}, + solutiongroup=[ims], ) gwf = Gwf(parent=sim, dis=dis, name=gwf_name) ic = Ic(parent=gwf) @@ -986,7 +986,7 @@ def test_gwf_wel(function_tmpdir): tdis=time, workspace=function_tmpdir, name=sim_name, - solutiongroup={"ims": ims}, + solutiongroup=[ims], ) dis = Dis(nlay=1, nrow=1, ncol=10, delr=10.0, delc=10.0, top=10.0, botm=0.0) @@ -1032,7 +1032,7 @@ def test_gwf_drn(function_tmpdir): tdis=time, workspace=function_tmpdir, name=sim_name, - solutiongroup={"ims": ims}, + solutiongroup=[ims], ) dis = Dis(nlay=1, nrow=1, ncol=10, delr=10.0, delc=10.0, top=10.0, botm=0.0) @@ -1080,7 +1080,7 @@ def test_gwf_riv(function_tmpdir): tdis=time, workspace=function_tmpdir, name=sim_name, - solutiongroup={"ims": ims}, + solutiongroup=[ims], ) dis = Dis(nlay=1, nrow=1, ncol=10, delr=10.0, delc=10.0, top=10.0, botm=0.0) @@ -1128,7 +1128,7 @@ def test_gwf_rch(function_tmpdir): tdis=time, workspace=function_tmpdir, name=sim_name, - solutiongroup={"ims": ims}, + solutiongroup=[ims], ) dis = Dis(nlay=1, nrow=1, ncol=10, delr=10.0, delc=10.0, top=10.0, botm=0.0) @@ -1187,7 +1187,7 @@ def test_gwf_rcha(function_tmpdir): tdis=time, workspace=function_tmpdir, name=sim_name, - solutiongroup={"ims": ims}, + solutiongroup=[ims], ) dis = Dis(nlay=1, nrow=nrow, ncol=ncol, delr=10.0, delc=10.0, top=10.0, botm=0.0) @@ -1241,7 +1241,7 @@ def test_gwf_evt(function_tmpdir): tdis=time, workspace=function_tmpdir, name=sim_name, - solutiongroup={"ims": ims}, + solutiongroup=[ims], ) dis = Dis(nlay=1, nrow=1, ncol=10, delr=10.0, delc=10.0, top=10.0, botm=0.0) @@ -1302,7 +1302,7 @@ def test_gwf_evta(function_tmpdir): tdis=time, workspace=function_tmpdir, name=sim_name, - solutiongroup={"ims": ims}, + solutiongroup=[ims], ) dis = Dis(nlay=1, nrow=nrow, ncol=ncol, delr=10.0, delc=10.0, top=10.0, botm=0.0) @@ -1367,7 +1367,7 @@ def test_gwf_mvr(function_tmpdir): tdis=time, workspace=function_tmpdir, name=sim_name, - solutiongroup={"ims": ims}, + solutiongroup=[ims], ) dis = Dis(nlay=1, nrow=1, ncol=10, delr=10.0, delc=10.0, top=10.0, botm=0.0) @@ -1475,7 +1475,7 @@ def test_gwt_basic(function_tmpdir): tdis=time, workspace=function_tmpdir, name=sim_name, - solutiongroup={"gwf_ims": ims_gwf, "gwt_ims": ims_gwt}, + solutiongroup=[ims_gwf, ims_gwt], ) # GWF model: uniform left-to-right flow @@ -1560,7 +1560,7 @@ def test_gwe_basic(function_tmpdir): tdis=time, workspace=function_tmpdir, name=sim_name, - solutiongroup={"gwf_ims": ims_gwf, "gwe_ims": ims_gwe}, + solutiongroup=[ims_gwf, ims_gwe], ) # GWF model: uniform left-to-right flow @@ -1646,7 +1646,7 @@ def test_gwf_buy(function_tmpdir): tdis=time, workspace=function_tmpdir, name=sim_name, - solutiongroup={"gwf_ims": ims_gwf, "gwt_ims": ims_gwt}, + solutiongroup=[ims_gwf, ims_gwt], ) gwf_dis = Dis(nlay=nlay, nrow=nrow, ncol=ncol, delr=1.0, delc=1.0, top=1.0, botm=0.0) @@ -1736,7 +1736,7 @@ def test_gwf_vsc(function_tmpdir): tdis=time, workspace=function_tmpdir, name=sim_name, - solutiongroup={"gwf_ims": ims_gwf, "gwe_ims": ims_gwe}, + solutiongroup=[ims_gwf, ims_gwe], ) gwf_dis = Dis(nlay=nlay, nrow=nrow, ncol=ncol, delr=1.0, delc=1.0, top=1.0, botm=0.0) @@ -1817,8 +1817,9 @@ def test_prt_basic(function_tmpdir): tdis=time, workspace=function_tmpdir, name="gwf_sim", - solutiongroup={ - "ims": Ims( + solutiongroup=[ + Ims( + name="ims", filename="gwf.ims", models=[gwf_name], outer_dvclose=1e-6, @@ -1828,7 +1829,7 @@ def test_prt_basic(function_tmpdir): rclose=Ims.Rclose(inner_rclose=1e-3), linear_acceleration="cg", ) - }, + ], ) dis = Dis(nlay=nlay, nrow=nrow, ncol=ncol, delr=1.0, delc=1.0, top=10.0, botm=0.0) gwf = Gwf(parent=gwf_sim, save_flows=True, dis=dis, name=gwf_name) @@ -1857,7 +1858,7 @@ def test_prt_basic(function_tmpdir): tdis=time, workspace=function_tmpdir, name="prt_sim", - solutiongroup={"ems": Ems(filename="prt.ems", models=[prt_name])}, + solutiongroup=[Ems(filename="prt.ems", models=[prt_name], name="ems")], ) prt_dis = PrtDis(nlay=nlay, nrow=nrow, ncol=ncol, delr=1.0, delc=1.0, top=10.0, botm=0.0) prt = Prt(parent=prt_sim, dis=prt_dis, name=prt_name) @@ -1927,7 +1928,7 @@ def test_gwf_oc_period_variations(function_tmpdir): tdis=time, workspace=function_tmpdir, name=sim_name, - solutiongroup={"ims": ims}, + solutiongroup=[ims], ) dis = Dis(nlay=1, nrow=3, ncol=3, delr=1.0, delc=1.0, top=1.0, botm=0.0, idomain=1) @@ -2004,7 +2005,7 @@ def test_gwt_ssm_sources(function_tmpdir): tdis=time, workspace=function_tmpdir, name=sim_name, - solutiongroup={"gwf_ims": ims_gwf, "gwt_ims": ims_gwt}, + solutiongroup=[ims_gwf, ims_gwt], ) # GWF: left boundary head=1, right=0; CHD carries auxiliary "conc" = 1.0 at inflow @@ -2114,7 +2115,7 @@ def test_gwf_lak_status(function_tmpdir): tdis=time, workspace=function_tmpdir, name=sim_name, - solutiongroup={"ims": ims}, + solutiongroup=[ims], ) dis = Dis( @@ -2301,7 +2302,7 @@ def test_gwt_lkt01(function_tmpdir): tdis=time, workspace=function_tmpdir, name=sim_name, - solutiongroup={"gwf_ims": ims_gwf, "gwt_ims": ims_gwt}, + solutiongroup=[ims_gwf, ims_gwt], ) # GWF model @@ -2510,7 +2511,7 @@ def test_gwt_lkt_flow_package_auxiliary_name(function_tmpdir): tdis=time, workspace=function_tmpdir, name=sim_name, - solutiongroup={"gwf_ims": ims_gwf, "gwt_ims": ims_gwt}, + solutiongroup=[ims_gwf, ims_gwt], ) # GWF model @@ -2718,7 +2719,7 @@ def test_gwe_lke_flow_package_auxiliary_name(function_tmpdir): tdis=time, workspace=function_tmpdir, name=sim_name, - solutiongroup={"gwf_ims": ims_gwf, "gwe_ims": ims_gwe}, + solutiongroup=[ims_gwf, ims_gwe], ) # GWF model @@ -2878,7 +2879,7 @@ def test_rcha_tas_reference_netcdf(function_tmpdir): tdis=Time(perlen=[1.0, 1.0], nstp=[1, 1], tsmult=[1.0, 1.0]), workspace=function_tmpdir, name=name, - solutiongroup={"ims": Ims(models=[name], linear_acceleration="cg")}, + solutiongroup=[Ims(models=[name], linear_acceleration="cg", name="ims")], ) gwf = Gwf(parent=sim, name=name) Dis(parent=gwf, nlay=1, nrow=nrow, ncol=ncol, top=10.0, botm=0.0) diff --git a/test/mf6/test_mf6_io_roundtrip.py b/test/mf6/test_mf6_io_roundtrip.py index 7ffde700..dfa80c9e 100644 --- a/test/mf6/test_mf6_io_roundtrip.py +++ b/test/mf6/test_mf6_io_roundtrip.py @@ -182,7 +182,7 @@ def test_tvk_child_kept(tmp_path): "BEGIN PERIOD 1\n 1 1 3 K 5.0\n 1 1 4 K33 2.5\nEND PERIOD\n" ) sim = Simulation.load(workspace / "mfsim.nam") - tvk = sim.models["flow15"].npf.tvk + tvk = sim["flow15"].npf.tvk rows = tvk.stress_period_data[0] assert [(type(r).__name__, r.cellid) for r in rows] == [("K", (0, 0, 2)), ("K33", (0, 0, 3))] assert (rows[0].k, rows[1].k33) == (5.0, 2.5) @@ -194,7 +194,7 @@ def test_tvk_child_kept(tmp_path): assert "TVK6 FILEIN flow15.tvk" in (out / "flow15.npf").read_text() reloaded = Simulation.load(out / "mfsim.nam") diffs: list[str] = [] - _diff(rows, reloaded.models["flow15"].npf.tvk.stress_period_data[0], "tvk", diffs) + _diff(rows, reloaded["flow15"].npf.tvk.stress_period_data[0], "tvk", diffs) assert not diffs, "\n".join(diffs) @@ -207,7 +207,7 @@ def test_tvs_child_kept(tmp_path): "BEGIN PERIOD 2\n 1 1 1 SS 2.0e-5\n 1 1 2 SY 0.1\nEND PERIOD\n" ) sim = Simulation.load(workspace / "mfsim.nam") - rows = sim.models["GWF_1"].sto.tvs.stress_period_data[1] + rows = sim["GWF_1"].sto.tvs.stress_period_data[1] assert [(type(r).__name__, r.cellid) for r in rows] == [("Ss", (0, 0, 0)), ("Sy", (0, 0, 1))] assert (rows[0].ss, rows[1].sy) == (2.0e-5, 0.1) @@ -218,7 +218,7 @@ def test_tvs_child_kept(tmp_path): assert "TVS6 FILEIN model.tvs" in (out / "model.sto").read_text() reloaded = Simulation.load(out / "mfsim.nam") diffs: list[str] = [] - _diff(rows, reloaded.models["GWF_1"].sto.tvs.stress_period_data[1], "tvs", diffs) + _diff(rows, reloaded["GWF_1"].sto.tvs.stress_period_data[1], "tvs", diffs) assert not diffs, "\n".join(diffs) diff --git a/test/mf6/test_mf6_load_all_models.py b/test/mf6/test_mf6_load_all_models.py index a490564e..2a66b648 100644 --- a/test/mf6/test_mf6_load_all_models.py +++ b/test/mf6/test_mf6_load_all_models.py @@ -76,8 +76,6 @@ `KNOWN_PASSING`. """ -from collections.abc import Mapping - import pytest from modflow_devtools.models import copy_to, get_models @@ -390,4 +388,4 @@ def test_load_simulation(tmp_path, model_name): assert isinstance(sim, Simulation) assert sim.tdis is not None for children in (sim.models, sim.exchanges, sim.solutiongroup): - assert children is None or isinstance(children, Mapping) + assert isinstance(children, list) diff --git a/test/mf6/test_mf6_namefile_load.py b/test/mf6/test_mf6_namefile_load.py index 16070a8f..5cd2a70e 100644 --- a/test/mf6/test_mf6_namefile_load.py +++ b/test/mf6/test_mf6_namefile_load.py @@ -66,17 +66,17 @@ def test_load_simulation_resolves_model_and_solution(written_sim): loaded = Simulation.load(written_sim / "mfsim.nam") assert len(loaded.models) == 1 - gwf = next(iter(loaded.models.values())) + gwf = next(iter(loaded.models)) assert isinstance(gwf, Gwf) assert len(loaded.solutiongroup) == 1 - ims = next(iter(loaded.solutiongroup.values())) + ims = next(iter(loaded.solutiongroup)) assert isinstance(ims, Ims) assert ims.models == ["mymodel"] def test_load_simulation_resolves_dis_and_dims(written_sim): - gwf = next(iter(Simulation.load(written_sim / "mfsim.nam").models.values())) + gwf = next(iter(Simulation.load(written_sim / "mfsim.nam").models)) assert isinstance(gwf.dis, Dis) assert gwf.dis.get_dims() == { @@ -93,7 +93,7 @@ def test_load_simulation_propagates_dims_to_griddata_siblings(written_sim): """Npf's griddata arrays need dims resolved from the dis sibling loaded moments earlier in the same "packages" block -- the dims-provider-first ordering _resolve_bindings implements.""" - gwf = next(iter(Simulation.load(written_sim / "mfsim.nam").models.values())) + gwf = next(iter(Simulation.load(written_sim / "mfsim.nam").models)) assert isinstance(gwf.npf, Npf) assert gwf.npf.k.shape == (100,) @@ -105,7 +105,7 @@ def test_load_simulation_propagates_dims_to_griddata_siblings(written_sim): def test_load_simulation_resolves_list_package_rows(written_sim): - gwf = next(iter(Simulation.load(written_sim / "mfsim.nam").models.values())) + gwf = next(iter(Simulation.load(written_sim / "mfsim.nam").models)) assert len(gwf.chd) == 1 chd = gwf.chd[0] @@ -126,13 +126,10 @@ def test_load_gwf_directly(written_sim): def test_load_preserves_model_pname(tmp_path): - """A dict-kind binding field (Simulation.models/exchanges/solutiongroup) - round-trips a custom pname via the child's own `.name` -- child - attachment reconciles a dict child's name to the key it's attached - under, so the namefile row's pname (not the referenced file's name, - which the row's pname needn't match) has to become that key. See - `test_load_preserves_list_package_pname` below for the equivalent - round trip on a list-kind package field.""" + """A model's custom pname round-trips via the child's own `.name`: + the namefile row's pname, not the referenced file's name, which the + row's pname needn't match. See `test_load_preserves_list_package_pname` + below for the same round trip on a model's package.""" import numpy as np from flopy.discretization.structuredgrid import StructuredGrid @@ -153,8 +150,8 @@ def test_load_preserves_model_pname(tmp_path): loaded = Simulation.load(workspace / "mfsim.nam") - assert list(loaded.models.keys()) == ["a_custom_model_name"] - gwf = loaded.models["a_custom_model_name"] + assert [m.name for m in loaded.models] == ["a_custom_model_name"] + gwf = loaded["a_custom_model_name"] assert gwf.name == "a_custom_model_name" @@ -168,7 +165,7 @@ def test_load_preserves_list_package_pname(written_sim): nam_path.read_text().replace("CHD6 mymodel.chd chd0", "CHD6 mymodel.chd boundary_west") ) - gwf = next(iter(Simulation.load(written_sim / "mfsim.nam").models.values())) + gwf = next(iter(Simulation.load(written_sim / "mfsim.nam").models)) chd = gwf.chd[0] assert isinstance(chd, Chd) assert chd.name == "boundary_west" @@ -269,7 +266,7 @@ def test_load_exchange_between_grids(tmp_path): ) sim.write() - exg = next(iter(Simulation.load(tmp_path / "mfsim.nam").exchanges.values())) + exg = next(iter(Simulation.load(tmp_path / "mfsim.nam").exchanges)) (row,) = exg.exchangedata assert (row.cellidm1, row.cellidm2) == ((0, 0, 1), (0, 0)) @@ -308,7 +305,7 @@ def test_load_model_in_subdirectory(subdir_sim, tmp_path, monkeypatch): them nor the cwd.""" monkeypatch.chdir(tmp_path) - gwf = Simulation.load(subdir_sim / "mfsim.nam").models["m"] + gwf = Simulation.load(subdir_sim / "mfsim.nam")["m"] assert isinstance(gwf.dis, Dis) assert gwf.dis.get_dims()["nodes"] == 4 @@ -327,7 +324,7 @@ def test_write_model_in_subdirectory(subdir_sim, tmp_path): assert (tmp_path / "copy" / "gwf" / "m.nam").is_file() assert (tmp_path / "copy" / "gwf" / "m.dis").is_file() - gwf = Simulation.load(tmp_path / "copy" / "mfsim.nam").models["m"] + gwf = Simulation.load(tmp_path / "copy" / "mfsim.nam")["m"] assert gwf.filename.as_posix() == "gwf/m.nam" assert gwf.dis.filename.as_posix() == "gwf/m.dis" assert gwf.dis.get_dims()["nodes"] == 4 diff --git a/test/mf6/test_mf6_output_readers.py b/test/mf6/test_mf6_output_readers.py index aca9b836..5d1598d9 100644 --- a/test/mf6/test_mf6_output_readers.py +++ b/test/mf6/test_mf6_output_readers.py @@ -28,9 +28,7 @@ def dis_model_output(function_tmpdir): inner_dvclose=1e-6, linear_acceleration="cg", ) - sim = Simulation( - tdis=time, workspace=function_tmpdir, name=sim_name, solutiongroup={"ims": ims} - ) + sim = Simulation(tdis=time, workspace=function_tmpdir, name=sim_name, solutiongroup=[ims]) nlay, nrow, ncol = 2, 3, 4 botm = np.stack([np.full((nrow, ncol), 0.0), np.full((nrow, ncol), -10.0)]) @@ -99,9 +97,7 @@ def disv_model_output(function_tmpdir): inner_dvclose=1e-6, linear_acceleration="cg", ) - sim = Simulation( - tdis=time, workspace=function_tmpdir, name=sim_name, solutiongroup={"ims": ims} - ) + sim = Simulation(tdis=time, workspace=function_tmpdir, name=sim_name, solutiongroup=[ims]) nlay = 2 ncpl = 9 diff --git a/test/mf6/test_mf6_write_context.py b/test/mf6/test_mf6_write_context.py index 894a846e..ddab5e0d 100644 --- a/test/mf6/test_mf6_write_context.py +++ b/test/mf6/test_mf6_write_context.py @@ -125,7 +125,7 @@ def test_component_write_with_context(function_tmpdir): name="test", workspace=function_tmpdir, tdis=time, - solutiongroup={"ims": ims}, + solutiongroup=[ims], ) gwf = Gwf(parent=sim, name="gwf", dis=dis) @@ -160,7 +160,7 @@ def test_write_context_manager_with_component(function_tmpdir): name="test", workspace=function_tmpdir, tdis=time, - solutiongroup={"ims": ims}, + solutiongroup=[ims], ) gwf = Gwf(parent=sim, name="gwf", dis=dis)