-
Notifications
You must be signed in to change notification settings - Fork 3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add cleanup_layered_wel
function
#1472
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -37,7 +37,7 @@ | |
from imod.mf6.validation_context import ValidationContext | ||
from imod.mf6.write_context import WriteContext | ||
from imod.prepare import assign_wells | ||
from imod.prepare.cleanup import cleanup_wel | ||
from imod.prepare.cleanup import cleanup_wel, cleanup_wel_layered | ||
from imod.schemata import ( | ||
AllValueSchema, | ||
AnyNoDataSchema, | ||
|
@@ -946,6 +946,25 @@ def clip_box( | |
z_max = self._find_well_value_at_layer(ds, top, layer_max) | ||
z_min = self._find_well_value_at_layer(ds, bottom, layer_min) | ||
|
||
if (z_max is not None) or (z_min is not None): | ||
in_bounds_z_max = None | ||
in_bounds_z_min = None | ||
if (z_max is not None) and (is_spatial_grid(top)): | ||
in_bounds_z_max = np.full(ds.sizes["index"], False) | ||
in_bounds_z_max[z_max["index"]] = True | ||
z_max = z_max.drop_vars(lambda x: x.coords) | ||
if (z_min is not None) and (is_spatial_grid(bottom)): | ||
in_bounds_z_min = np.full(ds.sizes["index"], False) | ||
in_bounds_z_min[z_min["index"]] = True | ||
z_min = z_min.drop_vars(lambda x: x.coords) | ||
if (in_bounds_z_max is not None) or (in_bounds_z_min is not None): | ||
in_bounds = np.full(ds.sizes["index"], False) | ||
if in_bounds_z_max is not None: | ||
in_bounds = in_bounds | in_bounds_z_max | ||
if in_bounds_z_min is not None: | ||
in_bounds = in_bounds | in_bounds_z_min | ||
ds = ds.loc[{"index": in_bounds}] | ||
Comment on lines
+949
to
+966
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So if I'm not mistaken, this piece of code filters out values that are out of z-bounds? I expected that to be filtered out in the call to If not, I think it is best to pick this up in a separate PR, for which for my understanding I think it is good to:
Either way, let's discuss on Monday! |
||
|
||
if z_max is not None: | ||
ds["screen_top"] = ds["screen_top"].clip(None, z_max) | ||
if z_min is not None: | ||
|
@@ -979,7 +998,7 @@ def _find_well_value_at_layer( | |
x=well_dataset["x"].values, | ||
y=well_dataset["y"].values, | ||
out_of_bounds="ignore", | ||
).drop_vars(lambda x: x.coords) | ||
) | ||
|
||
return value | ||
|
||
|
@@ -1395,6 +1414,40 @@ def from_imod5_cap_data(cls, imod5_data: Imod5DataDict): | |
data = well_from_imod5_cap_data(imod5_data) | ||
return cls(**data) # type: ignore | ||
|
||
@standard_log_decorator() | ||
def cleanup(self, dis: StructuredDiscretization | VerticesDiscretization): | ||
""" | ||
Clean up package inplace. This method calls | ||
:func:`imod.prepare.cleanup_wel_layered`, see documentation of that | ||
function for details on cleanup. | ||
|
||
dis: imod.mf6.StructuredDiscretization | imod.mf6.VerticesDiscretization | ||
Model discretization package. | ||
""" | ||
# Top and bottom should be forced to grids with a x, y coordinates | ||
top, bottom = broadcast_to_full_domain(**dict(dis.dataset.data_vars)) | ||
# Collect point variable datanames | ||
point_varnames = list(self._write_schemata.keys()) | ||
if "concentration" not in self.dataset.keys(): | ||
point_varnames.remove("concentration") | ||
point_varnames.append("id") | ||
# Create dataset with purely point locations | ||
point_ds = self.dataset[point_varnames] | ||
# Take first item of irrelevant dimensions | ||
point_ds = point_ds.isel(time=0, species=0, drop=True, missing_dims="ignore") | ||
# Cleanup well dataframe | ||
wells = point_ds.to_dataframe() | ||
cleaned_wells = cleanup_wel_layered(wells, top.isel(layer=0), bottom) | ||
# Select with ids in cleaned dataframe to drop points outside grid. | ||
well_ids = cleaned_wells.index | ||
dataset_cleaned = self.dataset.swap_dims({"index": "id"}).sel(id=well_ids) | ||
# Ensure dtype of id is preserved | ||
id_type = self.dataset["id"].dtype | ||
dataset_cleaned = dataset_cleaned.swap_dims({"id": "index"}).reset_coords("id") | ||
dataset_cleaned["id"] = dataset_cleaned["id"].astype(id_type) | ||
# Override dataset | ||
self.dataset = dataset_cleaned | ||
Comment on lines
+1417
to
+1449
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice! I see you duplicated the logic in the class GridAgnosticWell(BoundaryCondition, IPointDataPackage, abc.ABC):
...
def _cleanup(self,
dis: StructuredDiscretization | VerticesDiscretization,
cleanup_func: Callable,
**cleanup_kwargs
) -> None:
# Top and bottom should be forced to grids with a x, y coordinates
top, bottom = broadcast_to_full_domain(**dict(dis.dataset.data_vars))
# Collect point variable datanames
point_varnames = list(self._write_schemata.keys())
if "concentration" not in self.dataset.keys():
point_varnames.remove("concentration")
point_varnames.append("id")
# Create dataset with purely point locations
point_ds = self.dataset[point_varnames]
# Take first item of irrelevant dimensions
point_ds = point_ds.isel(time=0, species=0, drop=True, missing_dims="ignore")
# Cleanup well dataframe
wells = point_ds.to_dataframe()
cleaned_wells = cleanup_func(wells, top.isel(layer=0), bottom, **cleanup_kwargs)
# Select with ids in cleaned dataframe to drop points outside grid.
well_ids = cleaned_wells.index
dataset_cleaned = self.dataset.swap_dims({"index": "id"}).sel(id=well_ids)
# Ensure dtype of id is preserved
id_type = self.dataset["id"].dtype
dataset_cleaned = dataset_cleaned.swap_dims({"id": "index"}).reset_coords("id")
dataset_cleaned["id"] = dataset_cleaned["id"].astype(id_type)
# Override dataset
self.dataset = dataset_cleaned
class Well(GridAgnosticWell):
...
def cleanup(self, dis: StructuredDiscretization | VerticesDiscretization):
minimum_thickness = float(self.dataset["minimum_thickness"])
self._cleanup(dis, cleanup_wel, minimum_thickness=minimum_thickness)
class LayeredWell(GridAgnosticWell):
...
def cleanup(self, dis: StructuredDiscretization | VerticesDiscretization):
self._cleanup(dis, cleanup_wel_layered) |
||
|
||
|
||
class WellDisStructured(DisStructuredBoundaryCondition): | ||
""" | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,18 +10,22 @@ def mf6wel_test_data_stationary(): | |
[ | ||
[1, 1, 9], | ||
[1, 2, 9], | ||
[1, 3, 9], | ||
[1, 1, 8], | ||
[1, 2, 8], | ||
[2, 3, 7], | ||
[1, 3, 8], | ||
[2, 4, 7], | ||
[2, 3, 6], | ||
[2, 5, 7], | ||
[2, 6, 7], | ||
[2, 4, 6], | ||
[2, 5, 6], | ||
[2, 6, 6], | ||
], | ||
) | ||
coords = {"ncellid": np.arange(8) + 1, "dim_cellid": ["layer", "row", "column"]} | ||
coords = {"ncellid": np.arange(12) + 1, "dim_cellid": ["layer", "row", "column"]} | ||
cellid = xr.DataArray(cellid_values, coords=coords, dims=("ncellid", "dim_cellid")) | ||
rate = xr.DataArray( | ||
[1.0] * 8, coords={"ncellid": np.arange(8) + 1}, dims=("ncellid",) | ||
[1.0] * 12, coords={"ncellid": np.arange(12) + 1}, dims=("ncellid",) | ||
) | ||
return cellid, rate | ||
|
||
|
@@ -32,18 +36,22 @@ def mf6wel_test_data_transient(): | |
[ | ||
[1, 1, 9], | ||
[1, 2, 9], | ||
[1, 3, 9], | ||
[1, 1, 8], | ||
[1, 2, 8], | ||
[2, 3, 7], | ||
[1, 3, 8], | ||
[2, 4, 7], | ||
[2, 3, 6], | ||
[2, 5, 7], | ||
[2, 6, 7], | ||
[2, 4, 6], | ||
[2, 5, 6], | ||
[2, 6, 6], | ||
], | ||
) | ||
coords = {"ncellid": np.arange(8) + 1, "dim_cellid": ["layer", "row", "column"]} | ||
coords = {"ncellid": np.arange(12) + 1, "dim_cellid": ["layer", "row", "column"]} | ||
cellid = xr.DataArray(cellid_values, coords=coords, dims=("ncellid", "dim_cellid")) | ||
|
||
rate = [1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0] | ||
rate = [1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0] | ||
|
||
globaltimes = pd.date_range("2000-01-01", "2000-01-06") | ||
weltimes = globaltimes[:-1] | ||
|
@@ -52,7 +60,7 @@ def mf6wel_test_data_transient(): | |
np.arange(len(weltimes)) + 1, coords={"time": weltimes}, dims=("time",) | ||
) | ||
rate_cells = xr.DataArray( | ||
rate, coords={"ncellid": np.arange(8) + 1}, dims=("ncellid",) | ||
rate, coords={"ncellid": np.arange(12) + 1}, dims=("ncellid",) | ||
) | ||
rate_wel = rate_time * rate_cells | ||
|
||
|
@@ -61,13 +69,26 @@ def mf6wel_test_data_transient(): | |
|
||
@pytest.fixture(scope="session") | ||
def well_high_lvl_test_data_stationary(): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please add a comment why we need 12 wells: To also test if there are no sorting issues because of the default assigned "id" as strings. Otherwise somebody might decide to clean this up later and reduce it to 5 wells or something ;-) |
||
screen_top = [0.0, 0.0, 0.0, 0.0, -6.0, -6.0, -6.0, -6.0] | ||
screen_bottom = [-2.0, -2.0, -2.0, -2.0, -20.0, -20.0, -20.0, -20.0] | ||
screen_top = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -6.0, -6.0, -6.0, -6.0, -6.0, -6.0] | ||
screen_bottom = [ | ||
-2.0, | ||
-2.0, | ||
-2.0, | ||
-2.0, | ||
-2.0, | ||
-2.0, | ||
-20.0, | ||
-20.0, | ||
-20.0, | ||
-20.0, | ||
-20.0, | ||
-20.0, | ||
] | ||
# fmt: off | ||
y = [83.0, 77.0, 82.0, 71.0, 62.0, 52.0, 66.0, 59.0] | ||
x = [81.0, 82.0, 75.0, 77.0, 68.0, 64.0, 52.0, 51.0] | ||
y = [83.0, 77.0, 66.0, 82.0, 71.0, 61.0, 59.0, 44.0, 33.0, 52.0, 41.0, 38.0] | ||
x = [81.0, 82.0, 88.0, 75.0, 77.0, 71.0, 68.0, 64.0, 61.0, 52.0, 51.0, 59.0] | ||
# fmt: on | ||
rate = [1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0] | ||
rate = [1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0] | ||
rate_wel = xr.DataArray(rate, dims=("index",)) | ||
|
||
da_species = xr.DataArray( | ||
|
@@ -83,13 +104,26 @@ def well_high_lvl_test_data_stationary(): | |
|
||
@pytest.fixture(scope="session") | ||
def well_high_lvl_test_data_transient(): | ||
screen_top = [0.0, 0.0, 0.0, 0.0, -6.0, -6.0, -6.0, -6.0] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. See my previous comment: idem dito here |
||
screen_bottom = [-2.0, -2.0, -2.0, -2.0, -20.0, -20.0, -20.0, -20.0] | ||
screen_top = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -6.0, -6.0, -6.0, -6.0, -6.0, -6.0] | ||
screen_bottom = [ | ||
-2.0, | ||
-2.0, | ||
-2.0, | ||
-2.0, | ||
-2.0, | ||
-2.0, | ||
-20.0, | ||
-20.0, | ||
-20.0, | ||
-20.0, | ||
-20.0, | ||
-20.0, | ||
] | ||
# fmt: off | ||
y = [83.0, 77.0, 82.0, 71.0, 62.0, 52.0, 66.0, 59.0] | ||
x = [81.0, 82.0, 75.0, 77.0, 68.0, 64.0, 52.0, 51.0] | ||
y = [83.0, 77.0, 66.0, 82.0, 71.0, 61.0, 59.0, 44.0, 33.0, 52.0, 41.0, 38.0] | ||
x = [81.0, 82.0, 88.0, 75.0, 77.0, 71.0, 68.0, 64.0, 61.0, 52.0, 51.0, 59.0] | ||
# fmt: on | ||
rate = [1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0] | ||
rate = [1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0] | ||
|
||
globaltimes = pd.date_range("2000-01-01", "2000-01-06") | ||
weltimes = globaltimes[:-1] | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you can also add under the "Fixed" header, as this is very important for users!
If we also decide to include the fixes made to the well clipping (see my other comment), these should also be added to the changelog)