Coverage for C:\src\imod-python\imod\mf6\validation.py: 100%
34 statements
« prev ^ index » next coverage.py v7.5.1, created at 2024-05-08 14:15 +0200
« prev ^ index » next coverage.py v7.5.1, created at 2024-05-08 14:15 +0200
1"""
2This module contains specific validation utilities for Modflow 6.
3"""
5from typing import cast
7import numpy as np
9from imod.mf6.statusinfo import NestedStatusInfo, StatusInfo, StatusInfoBase
10from imod.schemata import DimsSchema, NoDataComparisonSchema, ValidationError
11from imod.typing import GridDataArray
13PKG_DIMS_SCHEMA = (
14 DimsSchema("layer", "y", "x")
15 | DimsSchema("layer", "{face_dim}")
16 | DimsSchema("layer")
17 | DimsSchema()
18)
20BOUNDARY_DIMS_SCHEMA = (
21 DimsSchema("time", "layer", "y", "x")
22 | DimsSchema("layer", "y", "x")
23 | DimsSchema("time", "layer", "{face_dim}")
24 | DimsSchema("layer", "{face_dim}")
25 # Layer dim not necessary, as long as there is a layer coordinate present.
26 | DimsSchema("time", "y", "x")
27 | DimsSchema("y", "x")
28 | DimsSchema("time", "{face_dim}")
29 | DimsSchema("{face_dim}")
30)
32CONC_DIMS_SCHEMA = (
33 DimsSchema("species", "time", "layer", "y", "x")
34 | DimsSchema("species", "layer", "y", "x")
35 | DimsSchema("species", "time", "layer", "{face_dim}")
36 | DimsSchema("species", "layer", "{face_dim}")
37 # Layer dim not necessary, as long as there is a layer coordinate present.
38 | DimsSchema("species", "time", "y", "x")
39 | DimsSchema("species", "y", "x")
40 | DimsSchema("species", "time", "{face_dim}")
41 | DimsSchema("species", "{face_dim}")
42)
45class DisBottomSchema(NoDataComparisonSchema):
46 """
47 Custom schema for the bottoms as these require some additional logic,
48 because of how Modflow 6 computes cell thicknesses.
49 """
51 def validate(self, obj: GridDataArray, **kwargs) -> None:
52 other_obj = kwargs[self.other]
54 active = self.is_other_notnull(other_obj)
55 bottom = obj
57 # Only check for multi-layered models
58 if bottom.coords["layer"].size > 1:
59 # Check if zero thicknesses occur in active cells. The difference across
60 # layers is a "negative thickness"
61 thickness = bottom.diff(dim="layer") * -1.0
62 if (thickness.where(active.isel(layer=slice(1, None))) <= 0.0).any():
63 raise ValidationError("found thickness <= 0.0")
65 # To compute thicknesses properly, Modflow 6 requires bottom data in the
66 # layer above the active cell in question.
67 overlaying_top_inactive = cast(GridDataArray, np.isnan(bottom)).shift(
68 layer=1, fill_value=False
69 )
70 if (overlaying_top_inactive & active).any():
71 raise ValidationError("inactive bottom above active cell")
74def validation_pkg_error_message(pkg_errors):
75 messages = []
76 for var, var_errors in pkg_errors.items():
77 messages.append(f"* {var}")
78 messages.extend(f"\t- {error}" for error in var_errors)
79 return "\n" + "\n".join(messages)
82def pkg_errors_to_status_info(
83 pkg_name: str, pkg_errors: dict[str, list[ValidationError]]
84) -> StatusInfoBase:
85 pkg_status_info = NestedStatusInfo(f"{pkg_name} package")
86 for var_name, var_errors in pkg_errors.items():
87 var_status_info = StatusInfo(var_name)
88 for var_error in var_errors:
89 var_status_info.add_error(str(var_error))
90 pkg_status_info.add(var_status_info)
92 return pkg_status_info