Skip to content

Commit 08a2252

Browse files
committed
working?
1 parent 7ec1ff0 commit 08a2252

8 files changed

Lines changed: 470 additions & 312 deletions

File tree

flopy4/mf6/__init__.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,16 @@ class WriteError(Exception):
2828

2929
def _load_mf6(cls, path: Path) -> Component:
3030
"""Load MF6 format file into a component instance."""
31+
from flopy4.mf6.binding import resolve_bindings
32+
3133
with open(path, "r") as fp:
32-
return structure(load_mf6(fp), path, cls)
34+
# Parse and transform
35+
data = load_mf6(fp, component_type=cls)
36+
# Structure into component
37+
component = structure(data, path, cls)
38+
# Resolve any child component bindings
39+
component = resolve_bindings(component, path.parent)
40+
return component
3341

3442

3543
def _load_json(cls, path: Path) -> Component:

flopy4/mf6/binding.py

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,3 +135,76 @@ def to_component(cls, binding_tuple: tuple | list, workspace: Path) -> Component
135135
component.name = terms[0] # type: ignore[attr-defined]
136136

137137
return component # type: ignore[return-value]
138+
139+
140+
def resolve_bindings(component: Component, workspace: Path) -> Component:
141+
"""
142+
Recursively resolve binding tuples to component instances.
143+
144+
Processes a component after structuring to:
145+
1. Detect binding lists (list of tuples with type/fname/terms)
146+
2. Recursively load referenced components via Binding.to_component()
147+
3. Replace binding lists with dicts of component instances
148+
149+
Parameters
150+
----------
151+
component : Component
152+
Component to process (may contain binding lists)
153+
workspace : Path
154+
Workspace directory for resolving file paths
155+
156+
Returns
157+
-------
158+
Component
159+
Component with bindings resolved to component instances
160+
"""
161+
from flopy4.mf6.simulation import Simulation
162+
163+
# Check if this is a Simulation - resolve models, solutions, exchanges
164+
if isinstance(component, Simulation):
165+
# Resolve models: list of binding tuples → dict of Model instances
166+
if hasattr(component, "models") and isinstance(component.models, list):
167+
resolved_models = {}
168+
for binding_tuple in component.models:
169+
if isinstance(binding_tuple, (tuple, list)):
170+
model = Binding.to_component(binding_tuple, workspace)
171+
# Use the name from the binding terms as the key
172+
key = binding_tuple[2] if len(binding_tuple) > 2 else model.name # type: ignore
173+
resolved_models[key] = model
174+
component.models = resolved_models # type: ignore
175+
176+
# Resolve solutions: dict[group, list of tuples] → dict[name, Solution]
177+
if hasattr(component, "solutions") and isinstance(component.solutions, dict):
178+
resolved_solutions = {}
179+
for group_key, bindings in list(component.solutions.items()): # type: ignore
180+
if isinstance(bindings, list):
181+
for binding_tuple in bindings:
182+
if isinstance(binding_tuple, (tuple, list)):
183+
solution = Binding.to_component(binding_tuple, workspace)
184+
# Use the name from the binding terms as the key
185+
key = (
186+
binding_tuple[2] if len(binding_tuple) > 2 else solution.name # type: ignore
187+
)
188+
resolved_solutions[key] = solution
189+
component.solutions = resolved_solutions # type: ignore
190+
191+
# Resolve exchanges: list of binding tuples → dict of Exchange instances
192+
if hasattr(component, "exchanges") and isinstance(component.exchanges, list):
193+
resolved_exchanges = {}
194+
for binding_tuple in component.exchanges:
195+
if isinstance(binding_tuple, (tuple, list)):
196+
exchange = Binding.to_component(binding_tuple, workspace)
197+
# Use exchange type + model names as key
198+
key = f"{exchange.__class__.__name__}_{binding_tuple[2]}_{binding_tuple[3]}" # type: ignore
199+
resolved_exchanges[key] = exchange
200+
component.exchanges = resolved_exchanges # type: ignore
201+
202+
# Resolve TDIS if it's a binding
203+
if hasattr(component, "tdis") and isinstance(component.tdis, (tuple, list)):
204+
component.tdis = Binding.to_component(component.tdis, workspace) # type: ignore
205+
206+
# For Model components, resolve package bindings if needed
207+
# (currently packages are typically in griddata/perioddata fields, not as bindings)
208+
# This can be extended in the future if needed
209+
210+
return component
Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,61 @@
1-
from typing import IO, Any
1+
from typing import IO, TYPE_CHECKING, Any
22

33
from flopy4.mf6.codec.reader.parser import get_basic_parser
4-
from flopy4.mf6.codec.reader.transformer import BasicTransformer
4+
from flopy4.mf6.codec.reader.transformer import BasicTransformer, TypedTransformer
5+
6+
if TYPE_CHECKING:
7+
from flopy4.mf6.component import Component
58

69
BASIC_PARSER = get_basic_parser()
710
BASIC_TRANSFORMER = BasicTransformer()
811

912

10-
def load(fp: IO[str]) -> Any:
13+
def load(fp: IO[str], component_type: type["Component"] | None = None) -> Any:
1114
"""
1215
Load and parse an MF6 input file.
1316
1417
Parameters
1518
----------
16-
path : str | PathLike
17-
Path to the MF6 input file
19+
fp : IO[str]
20+
File-like object containing MF6 input file content
21+
component_type : type[Component], optional
22+
Component class to use for typed transformation.
23+
If provided, uses TypedTransformer with the component's DFN.
24+
If None, uses BasicTransformer for untyped parsing.
1825
1926
Returns
2027
-------
2128
Any
2229
Parsed MF6 input file structure
2330
"""
24-
return loads(fp.read())
31+
return loads(fp.read(), component_type=component_type)
2532

2633

27-
def loads(data: str) -> Any:
34+
def loads(data: str, component_type: type["Component"] | None = None) -> Any:
2835
"""
2936
Parse MF6 input file content from string.
3037
3138
Parameters
3239
----------
3340
data : str
3441
MF6 input file content as string
42+
component_type : type[Component], optional
43+
Component class to use for typed transformation.
44+
If provided, uses TypedTransformer with the component's attrs specification.
45+
If None, uses BasicTransformer for untyped parsing.
3546
3647
Returns
3748
-------
3849
Any
3950
Parsed MF6 input file structure
4051
"""
52+
tree = BASIC_PARSER.parse(data)
53+
54+
if component_type is not None:
55+
# Use TypedTransformer with the component's attrs specification
56+
transformer = TypedTransformer(component_type=component_type)
57+
else:
58+
# Fall back to BasicTransformer
59+
transformer = BASIC_TRANSFORMER
4160

42-
return BASIC_TRANSFORMER.transform(BASIC_PARSER.parse(data))
61+
return transformer.transform(tree)

0 commit comments

Comments
 (0)