Coverage for src/CSET/operators/mesoscale.py: 100%
18 statements
« prev ^ index » next coverage.py v7.15.3, created at 2026-08-04 08:32 +0000
« prev ^ index » next coverage.py v7.15.3, created at 2026-08-04 08:32 +0000
1# © Crown copyright, Met Office (2022-2024) 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.
15"""A module containing different diagnostics for mesoscales.
17The diagnostics here are applicable at mesoscales and apply generally
18rather than for specific aspects of mesoscale meteorology (e.g. convection).
19For specific aspects, the user is referred to other modules available in
20CSET.
22"""
24import logging
26import iris
27from scipy.ndimage import gaussian_filter, uniform_filter
29from CSET.operators._utils import get_cube_yxcoordname
31logger = logging.getLogger(__name__)
34def spatial_perturbation_field(
35 original_field: iris.cube.Cube,
36 apply_gaussian_filter: bool = True,
37 filter_scale: int = 40,
38) -> iris.cube.Cube:
39 """Calculate a spatial perturbation field.
41 Parameters
42 ----------
43 original_field: iris.cube.Cube
44 Iris cube containing data to smooth, supporting multiple dimensions
45 (at least two spatial dimensions must be supplied, i.e. 2D).
46 apply_gaussian_filter: boolean, optional
47 If set to True a Gaussian filter is applied; if set to False
48 a Uniform filter is applied.
49 Default is True.
50 filter_scale: int, optional
51 Scale at which to define the filter in grid boxes. If the
52 filter is a Gaussian convolution this value represents the
53 standard deviation of the Gaussian kernel.
54 Default is 40 grid boxes.
56 Returns
57 -------
58 pert_field: iris.cube.Cube
59 An iris cube of the spatial perturbation field.
61 Notes
62 -----
63 In mesoscale meteorology the perturbation field is more important than the
64 balanced flows for process understanding. This function is designed
65 to create spatial perturbation fields based on smoothing with a Gaussian
66 kernel or a uniform kernel.
68 The kernels are defined by the filter_scale, which for mesoscale
69 perturbations should be between an approximate cloud separation distance,
70 of 30 km, and synoptic scale variations (1000 km). In practice any
71 value between these ranges should provided broadly consistent results (e.g.
72 [Flacketal2016]_). The Gaussian kernel will give greater importance to
73 areas closer to the event and will produce a smooth perturbation field.
74 The uniform kernel will produce a smooth perturbation field but will not
75 give local features as much prominence.
77 Caution should be applied to boundaries, particularly if the domain is of
78 variable resolution, as some numerical artifacts could be introduced.
80 References
81 ----------
82 .. [Flacketal2016] Flack, D.L.A., Plant, R.S., Gray, S.L., Lean, H.W.,
83 Keil, C. and Craig, G.C. (2016) "Characterisation of Convective
84 Regimes over the British Isles." Quarterly Journal of the Royal
85 Meteorological Society, vol. 142, 1541-1553. doi:10.1002/qj.2758
87 Examples
88 --------
89 >>> Temperature_perturbation = meso.spatial_perturbation_fields(Temp,
90 gaussian_filter=True,filter_scale=40)
91 >>> iplt.pcolormesh(Temperature_perturabtion[0,:,:],cmap=mpl.cm.bwr)
92 >>> plt.gca().coastlines('10m')
93 >>> plt.clim(-5,5)
94 >>> plt.colorbar()
95 >>> plt.show()
97 """
98 pert_field = original_field.copy()
99 # find axes of spatial coordinates in field
100 coords = [coord.name() for coord in original_field.coords()]
101 # axes tuple containing latitude, longitude coordinate name.
102 axes = (
103 coords.index(get_cube_yxcoordname(original_field)[0]),
104 coords.index(get_cube_yxcoordname(original_field)[1]),
105 )
106 # apply convolution depending on type used
107 if apply_gaussian_filter:
108 filter_type = "Gaussian"
109 logger.info("Gaussian filter applied.")
110 pert_field.data -= gaussian_filter(original_field.data, filter_scale, axes=axes)
111 else:
112 logger.info("Uniform filter applied.")
113 filter_type = "Uniform"
114 pert_field.data -= uniform_filter(original_field.data, filter_scale, axes=axes)
115 # provide attributes to cube to indicate spatial perturbation field
116 pert_field.attributes["perturbation_field"] = (
117 f"{filter_type}_with_{filter_scale}_grid_point_filter_scale"
118 )
119 return pert_field