|
3 | 3 | from enum import Enum |
4 | 4 | from typing import Optional |
5 | 5 |
|
| 6 | +import pandas as pd |
6 | 7 | import xarray as xr |
7 | 8 |
|
8 | 9 | from imod.mf6.utilities.mask import mask_arrays |
| 10 | +from imod.prepare.wells import locate_wells, validate_well_columnnames |
9 | 11 | from imod.schemata import scalar_None |
10 | | -from imod.typing import GridDataArray, GridDataset |
| 12 | +from imod.typing import GridDataArray |
11 | 13 |
|
12 | 14 |
|
13 | 15 | class AlignLevelsMode(Enum): |
@@ -83,7 +85,7 @@ def cleanup_riv( |
83 | 85 | idomain: xarray.DataArray | xugrid.UgridDataArray |
84 | 86 | MODFLOW 6 model domain. idomain==1 is considered active domain. |
85 | 87 | bottom: xarray.DataArray | xugrid.UgridDataArray |
86 | | - Grid with`model bottoms |
| 88 | + Grid with model bottoms |
87 | 89 | stage: xarray.DataArray | xugrid.UgridDataArray |
88 | 90 | Grid with river stages |
89 | 91 | conductance: xarray.DataArray | xugrid.UgridDataArray |
@@ -209,11 +211,128 @@ def cleanup_ghb( |
209 | 211 | return _cleanup_robin_boundary(idomain, output_dict) |
210 | 212 |
|
211 | 213 |
|
212 | | -def cleanup_wel(wel_ds: GridDataset): |
| 214 | +def _locate_wells_in_bounds( |
| 215 | + wells: pd.DataFrame, top: GridDataArray, bottom: GridDataArray |
| 216 | +) -> tuple[pd.DataFrame, pd.Series, pd.Series]: |
213 | 217 | """ |
214 | | - Clean up wells |
| 218 | + Locate wells in model bounds, wells outside bounds are dropped. Returned |
| 219 | + dataframes and series have well "id" as index. |
215 | 220 |
|
216 | | - - Removes wells where the screen bottom elevation exceeds screen top. |
| 221 | + Returns |
| 222 | + ------- |
| 223 | + wells_in_bounds: pd.DataFrame |
| 224 | + wells in model boundaries. Has "id" as index. |
| 225 | + xy_top_series: pd.Series |
| 226 | + model top at well xy location. Has "id" as index. |
| 227 | + xy_base_series: pd.Series |
| 228 | + model base at well xy location. Has "id" as index. |
217 | 229 | """ |
218 | | - deactivate = wel_ds["screen_top"] < wel_ds["screen_bottom"] |
219 | | - return wel_ds.where(~deactivate, drop=True) |
| 230 | + id_in_bounds, xy_top, xy_bottom, _ = locate_wells( |
| 231 | + wells, top, bottom, validate=False |
| 232 | + ) |
| 233 | + xy_base_model = xy_bottom.isel(layer=-1, drop=True) |
| 234 | + |
| 235 | + # Assign id as coordinates |
| 236 | + xy_top = xy_top.assign_coords(id=("index", id_in_bounds)) |
| 237 | + xy_base_model = xy_base_model.assign_coords(id=("index", id_in_bounds)) |
| 238 | + # Create pandas dataframes/series with "id" as index. |
| 239 | + xy_top_series = xy_top.to_dataframe(name="top").set_index("id")["top"] |
| 240 | + xy_base_series = xy_base_model.to_dataframe(name="bottom").set_index("id")["bottom"] |
| 241 | + wells_in_bounds = wells.set_index("id").loc[id_in_bounds] |
| 242 | + return wells_in_bounds, xy_top_series, xy_base_series |
| 243 | + |
| 244 | + |
| 245 | +def _clip_filter_screen_to_surface_level( |
| 246 | + cleaned_wells: pd.DataFrame, xy_top_series: pd.Series |
| 247 | +) -> pd.DataFrame: |
| 248 | + cleaned_wells["screen_top"] = cleaned_wells["screen_top"].clip(upper=xy_top_series) |
| 249 | + return cleaned_wells |
| 250 | + |
| 251 | + |
| 252 | +def _drop_wells_below_model_base( |
| 253 | + cleaned_wells: pd.DataFrame, xy_base_series: pd.Series |
| 254 | +) -> pd.DataFrame: |
| 255 | + is_below_base = cleaned_wells["screen_top"] >= xy_base_series |
| 256 | + return cleaned_wells.loc[is_below_base] |
| 257 | + |
| 258 | + |
| 259 | +def _clip_filter_bottom_to_model_base( |
| 260 | + cleaned_wells: pd.DataFrame, xy_base_series: pd.Series |
| 261 | +) -> pd.DataFrame: |
| 262 | + cleaned_wells["screen_bottom"] = cleaned_wells["screen_bottom"].clip( |
| 263 | + lower=xy_base_series |
| 264 | + ) |
| 265 | + return cleaned_wells |
| 266 | + |
| 267 | + |
| 268 | +def _set_inverted_filters_to_point_filters(cleaned_wells: pd.DataFrame) -> pd.DataFrame: |
| 269 | + # Convert all filters where screen bottom exceeds screen top to |
| 270 | + # point filters |
| 271 | + cleaned_wells["screen_bottom"] = cleaned_wells["screen_bottom"].clip( |
| 272 | + upper=cleaned_wells["screen_top"] |
| 273 | + ) |
| 274 | + return cleaned_wells |
| 275 | + |
| 276 | + |
| 277 | +def _set_ultrathin_filters_to_point_filters( |
| 278 | + cleaned_wells: pd.DataFrame, minimum_thickness: float |
| 279 | +) -> pd.DataFrame: |
| 280 | + not_ultrathin_layer = ( |
| 281 | + cleaned_wells["screen_top"] - cleaned_wells["screen_bottom"] |
| 282 | + ) > minimum_thickness |
| 283 | + cleaned_wells["screen_bottom"] = cleaned_wells["screen_bottom"].where( |
| 284 | + not_ultrathin_layer, cleaned_wells["screen_top"] |
| 285 | + ) |
| 286 | + return cleaned_wells |
| 287 | + |
| 288 | + |
| 289 | +def cleanup_wel( |
| 290 | + wells: pd.DataFrame, |
| 291 | + top: GridDataArray, |
| 292 | + bottom: GridDataArray, |
| 293 | + minimum_thickness: float = 0.05, |
| 294 | +) -> pd.DataFrame: |
| 295 | + """ |
| 296 | + Clean up dataframe with wells, fixes some common mistakes in the following |
| 297 | + order: |
| 298 | +
|
| 299 | + 1. Wells outside grid bounds are dropped |
| 300 | + 2. Filters above surface level are set to surface level |
| 301 | + 3. Drop wells with filters entirely below base |
| 302 | + 4. Clip filter screen_bottom to model base |
| 303 | + 5. Clip filter screen_bottom to screen_top |
| 304 | + 6. Well filters thinner than minimum thickness are made point filters |
| 305 | +
|
| 306 | + Parameters |
| 307 | + ---------- |
| 308 | + wells: pandas.Dataframe |
| 309 | + Dataframe with wells to be cleaned up. Requires columns ``"x", "y", |
| 310 | + "id", "screen_top", "screen_bottom"`` |
| 311 | + top: xarray.DataArray | xugrid.UgridDataArray |
| 312 | + Grid with model top |
| 313 | + bottom: xarray.DataArray | xugrid.UgridDataArray |
| 314 | + Grid with model bottoms |
| 315 | + minimum_thickness: float |
| 316 | + Minimum thickness, filter thinner than this thickness are set to point |
| 317 | + filters |
| 318 | +
|
| 319 | + Returns |
| 320 | + ------- |
| 321 | + pandas.DataFrame |
| 322 | + Cleaned well dataframe. |
| 323 | + """ |
| 324 | + validate_well_columnnames( |
| 325 | + wells, names={"x", "y", "id", "screen_top", "screen_bottom"} |
| 326 | + ) |
| 327 | + |
| 328 | + cleaned_wells, xy_top_series, xy_base_series = _locate_wells_in_bounds( |
| 329 | + wells, top, bottom |
| 330 | + ) |
| 331 | + cleaned_wells = _clip_filter_screen_to_surface_level(cleaned_wells, xy_top_series) |
| 332 | + cleaned_wells = _drop_wells_below_model_base(cleaned_wells, xy_base_series) |
| 333 | + cleaned_wells = _clip_filter_bottom_to_model_base(cleaned_wells, xy_base_series) |
| 334 | + cleaned_wells = _set_inverted_filters_to_point_filters(cleaned_wells) |
| 335 | + cleaned_wells = _set_ultrathin_filters_to_point_filters( |
| 336 | + cleaned_wells, minimum_thickness |
| 337 | + ) |
| 338 | + return cleaned_wells |
0 commit comments