Coverage for src/CSET/operators/ensembles.py: 100%

27 statements  

« prev     ^ index     » next       coverage.py v7.10.6, created at 2025-09-05 21:08 +0000

1# © Crown copyright, Met Office (2022-2025) and CSET contributors. 

2# 

3# Licensed under the Apache License, Version 2.0 (the "License"); 

4# you may not use this file except in compliance with the License. 

5# You may obtain a copy of the License at 

6# 

7# http://www.apache.org/licenses/LICENSE-2.0 

8# 

9# Unless required by applicable law or agreed to in writing, software 

10# distributed under the License is distributed on an "AS IS" BASIS, 

11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 

12# See the License for the specific language governing permissions and 

13# limitations under the License. 

14 

15""" 

16A module containing different diagnostics for ensembles. 

17 

18The diagnostics here are applicable to ensembles and apply generally to 

19ensembles. They are not just limited to considering ensemble spread. 

20""" 

21 

22import iris 

23import iris.cube 

24import numpy as np 

25 

26 

27def DKE(u: iris.cube.Cube, v: iris.cube.Cube) -> iris.cube.Cube: 

28 r"""Calculate the Difference Kinetic Energy (DKE). 

29 

30 Parameters 

31 ---------- 

32 u: iris.cube.Cube 

33 Iris cube of the u component of the wind field for all members. Must 

34 include a realization coordinate; realization must be the first 

35 coordinate. 

36 v: iris.cube.Cube 

37 Iris cube of the v component of the wind field same format as u. 

38 

39 Returns 

40 ------- 

41 DKE: iris.cube.Cube 

42 An iris cube of the DKE for each of the control - member comparisons. 

43 

44 Notes 

45 ----- 

46 The Difference Kinetic Energy, or DKE was first used in an ensemble sense 

47 by [Zhangetal2002]_. It is calculated as 

48 

49 .. math:: \frac{1}{2}u'u' + \frac{1}{2}v'v' 

50 

51 where 

52 

53 .. math:: x' = x_{control} - x_{perturbed} 

54 

55 for each member of the ensemble. 

56 

57 The DKE is used to show links between the perturbation growth (growth of 

58 ensemble spread) or errors with dynamical features. It can be particularly 

59 useful in understanding differences in physical mechanisms between ensemble 

60 members. The larger the DKE the greater the difference between the two 

61 members being considered. 

62 

63 The DKE can be viewed as a domain average, horizontal integration (to 

64 produce profiles), or vertical integration/subsampling (to provide spatial maps). 

65 Furthermore, weightings based on volume or mass can be applied to these 

66 integrations. However, initially the DKE per unit mass is implemented here. 

67 

68 The DKE is often considered in the form of power spectra to identify the 

69 presence of upscale error growth or the dominant scales of error growth. It 

70 is often plotted on a logarithmic scale. 

71 

72 References 

73 ---------- 

74 .. [Zhangetal2002] Zhang, F., Snyder, C., and Rotunno, R., (2002) 

75 "Mesoscale Predictability of the 'Surprise' Snowstorm of 24-25 January 

76 2000." Monthly Weather Review, vol. 130, 1617-1632, 

77 doi: 10.1175/1520-0493(2002)130<1617:MPOTSS>2.0.CO;2 

78 

79 Examples 

80 -------- 

81 >>> DKE = ensembles.DKE(u, v) 

82 """ 

83 for cube in [u, v]: 

84 # Use dim_map to store the dimensions and check the realization 

85 # coordinate is the first coordinate. 

86 dim_map = {} 

87 for coord in cube.dim_coords: 

88 dim_index = cube.coord_dims(coord.name())[0] 

89 dim_map[coord.name()] = dim_index 

90 if "realization" not in dim_map: 

91 raise ValueError("Cube should have a realization coordinate.") 

92 if not dim_map["realization"] == 0: 

93 raise ValueError("Realization should be the first coordinate in cube.") 

94 

95 # Check cubes are the same shape. 

96 if u.shape != v.shape: 

97 raise ValueError("Cubes should be the same shape.") 

98 

99 # Check coordinates are identical. 

100 for coord_u, coord_v in zip(u.dim_coords, v.dim_coords, strict=True): 

101 if not u.coord(coord_u.name()) == v.coord(coord_v.name()): 

102 raise ValueError( 

103 f"u and v should have matching coordinates for {coord_u.name()}." 

104 ) 

105 # Define control member and perturbed members. 

106 u_ctrl = u[np.where(u.coord("realization").points[:] == 0)[0][0], :] 

107 u_mem = u[np.where(u.coord("realization").points[:] != 0)[0][:], :] 

108 v_ctrl = v[np.where(v.coord("realization").points[:] == 0)[0][0], :] 

109 v_mem = v[np.where(v.coord("realization").points[:] != 0)[0][:], :] 

110 

111 # Calculate the DKE 

112 DKE = u_mem.copy() 

113 DKE.data[:] = 0.0 

114 DKE.data = ( 

115 0.5 * (u_ctrl.data - u_mem.data) ** 2 + 0.5 * (v_ctrl.data - v_mem.data) ** 2 

116 ) 

117 DKE.rename("Difference Kinetic Energy") 

118 return DKE