|
2 | 2 |
|
3 | 3 | import numpy as np |
4 | 4 | import sparse |
| 5 | +import xattree |
5 | 6 | from numpy.typing import NDArray |
6 | 7 | from xarray import DataArray |
7 | 8 | from xattree import get_xatspec |
8 | 9 |
|
| 10 | +from flopy4.mf6.component import Component |
9 | 11 | from flopy4.mf6.config import SPARSE_THRESHOLD |
10 | 12 | from flopy4.mf6.constants import FILL_DNODATA |
| 13 | +from flopy4.mf6.spec import get_blocks |
11 | 14 |
|
12 | 15 |
|
13 | 16 | # TODO: convert to a cattrs structuring hook so we don't have to |
@@ -108,22 +111,90 @@ def unstructure_array(value: DataArray) -> dict: |
108 | 111 | MF6 list-based input format. |
109 | 112 | """ |
110 | 113 | # make sure dim 'kper' is present |
111 | | - if "kper" not in value.dims: |
112 | | - raise ValueError("array must have 'kper' dimension") |
| 114 | + time_dim = "nper" |
| 115 | + if time_dim not in value.dims: |
| 116 | + raise ValueError(f"Array must have dimension '{time_dim}'") |
113 | 117 |
|
114 | 118 | if isinstance(value.data, sparse.COO): |
115 | 119 | coords = value.coords |
116 | 120 | data = value.data |
117 | 121 | else: |
118 | | - coords = np.array(np.nonzero(value)).T # type: ignore |
119 | | - data = value[tuple(coords.T)] # type: ignore |
| 122 | + coords = np.array(np.nonzero(value.data)).T # type: ignore |
| 123 | + data = value.data[tuple(coords.T)] # type: ignore |
120 | 124 | if not coords.size: # type: ignore |
121 | 125 | return {} |
122 | 126 | match value.ndim: |
123 | 127 | case 1: |
124 | | - return {k: v for k, v in zip(coords[:, 0], data)} # type: ignore |
| 128 | + return {int(k): v for k, v in zip(coords[:, 0], data)} # type: ignore |
125 | 129 | case 2: |
126 | | - return {(k, j): v for (k, j), v in zip(coords, data)} # type: ignore |
| 130 | + return {(int(k), int(j)): v for (k, j), v in zip(coords, data)} # type: ignore |
127 | 131 | case 3: |
128 | | - return {(k, i, j): v for (k, i, j), v in zip(coords, data)} # type: ignore |
| 132 | + return {(int(k), int(i), int(j)): v for (k, i, j), v in zip(coords, data)} # type: ignore |
129 | 133 | return {} |
| 134 | + |
| 135 | + |
| 136 | +def unstructure_component(value: Component) -> dict[str, Any]: |
| 137 | + data = xattree.asdict(value) |
| 138 | + for block in get_blocks(value.dfn).values(): |
| 139 | + for field_name, field in block.items(): |
| 140 | + # unstructure arrays destined for list-based input |
| 141 | + if field["type"] == "recarray" and field["reader"] != "readarray": |
| 142 | + data[field_name] = unstructure_array(data[field_name]) |
| 143 | + return data |
| 144 | + |
| 145 | + |
| 146 | +def unstructure_oc(value: Any) -> dict[str, Any]: |
| 147 | + data = xattree.asdict(value) |
| 148 | + for block_name, block in get_blocks(value.dfn).items(): |
| 149 | + if block_name == "perioddata": |
| 150 | + # Unstructure all four arrays |
| 151 | + save_head = unstructure_array(data.get("save_head", {})) |
| 152 | + save_budget = unstructure_array(data.get("save_budget", {})) |
| 153 | + print_head = unstructure_array(data.get("print_head", {})) |
| 154 | + print_budget = unstructure_array(data.get("print_budget", {})) |
| 155 | + |
| 156 | + # Collect all unique periods |
| 157 | + all_periods = set() # type: ignore |
| 158 | + for d in (save_head, save_budget, print_head, print_budget): |
| 159 | + if isinstance(d, dict): |
| 160 | + all_periods.update(d.keys()) |
| 161 | + all_periods = sorted(all_periods) # type: ignore |
| 162 | + |
| 163 | + saverecord = {} # type: ignore |
| 164 | + printrecord = {} # type: ignore |
| 165 | + for kper in all_periods: |
| 166 | + # Save head |
| 167 | + if kper in save_head: |
| 168 | + v = save_head[kper] |
| 169 | + if kper not in saverecord: |
| 170 | + saverecord[kper] = [] |
| 171 | + saverecord[kper].append({"action": "save", "type": "head", "ocsetting": v}) |
| 172 | + # Save budget |
| 173 | + if kper in save_budget: |
| 174 | + v = save_budget[kper] |
| 175 | + if kper not in saverecord: |
| 176 | + saverecord[kper] = [] |
| 177 | + saverecord[kper].append({"action": "save", "type": "budget", "ocsetting": v}) |
| 178 | + # Print head |
| 179 | + if kper in print_head: |
| 180 | + v = print_head[kper] |
| 181 | + if kper not in printrecord: |
| 182 | + printrecord[kper] = [] |
| 183 | + printrecord[kper].append({"action": "print", "type": "head", "ocsetting": v}) |
| 184 | + # Print budget |
| 185 | + if kper in print_budget: |
| 186 | + v = print_budget[kper] |
| 187 | + if kper not in printrecord: |
| 188 | + printrecord[kper] = [] |
| 189 | + printrecord[kper].append({"action": "print", "type": "budget", "ocsetting": v}) |
| 190 | + |
| 191 | + data["saverecord"] = saverecord |
| 192 | + data["printrecord"] = printrecord |
| 193 | + data["save"] = "save" |
| 194 | + data["print"] = "print" |
| 195 | + else: |
| 196 | + for field_name, field in block.items(): |
| 197 | + # unstructure arrays destined for list-based input |
| 198 | + if field["type"] == "recarray" and field["reader"] != "readarray": |
| 199 | + data[field_name] = unstructure_array(data[field_name]) |
| 200 | + return data |
0 commit comments