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

1""" 

2This module contains specific validation utilities for Modflow 6. 

3""" 

4 

5from typing import cast 

6 

7import numpy as np 

8 

9from imod.mf6.statusinfo import NestedStatusInfo, StatusInfo, StatusInfoBase 

10from imod.schemata import DimsSchema, NoDataComparisonSchema, ValidationError 

11from imod.typing import GridDataArray 

12 

13PKG_DIMS_SCHEMA = ( 

14 DimsSchema("layer", "y", "x") 

15 | DimsSchema("layer", "{face_dim}") 

16 | DimsSchema("layer") 

17 | DimsSchema() 

18) 

19 

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) 

31 

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) 

43 

44 

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 """ 

50 

51 def validate(self, obj: GridDataArray, **kwargs) -> None: 

52 other_obj = kwargs[self.other] 

53 

54 active = self.is_other_notnull(other_obj) 

55 bottom = obj 

56 

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") 

64 

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") 

72 

73 

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) 

80 

81 

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) 

91 

92 return pkg_status_info