Skip to content

Commit 90eed65

Browse files
committed
Adds basic CFAD calculation and tests
Fixes #1010
1 parent e480d36 commit 90eed65

File tree

2 files changed

+82
-0
lines changed

2 files changed

+82
-0
lines changed

src/CSET/operators/plot.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -844,6 +844,60 @@ def _spatial_plot(
844844
_make_plot_html_page(complete_plot_index)
845845

846846

847+
def _calculate_CFAD(
848+
cube: iris.cube.Cube, vertical_coordinate: str, bin_edges: list[float]
849+
) -> iris.cube.Cube:
850+
"""Calculate a Contour Frequency by Altitude Diagram (CFAD).
851+
852+
Parameters
853+
----------
854+
cube: iris.cube.Cube
855+
A cube of the data to be turned into a CFAD. It should be a minimum
856+
of two dimensions with one being a user specified vertical coordinate.
857+
vertical_coordinate: str
858+
The vertical coordinate of the cube for the CFAD to be calculated over.
859+
bin_edges: list[float]
860+
The bin edges for the histogram. The bins need to be specified to
861+
ensure consistency across the CFAD, otherwise it cannot be interpreted.
862+
"""
863+
# Setup empty array for containing the CFAD data.
864+
CFAD_values = np.zeros(
865+
(len(cube.coord(vertical_coordinate).points), len(bin_edges) - 1)
866+
)
867+
868+
# Set iterator for CFAD values.
869+
i = 0
870+
871+
# Calculate the CFAD as a histogram summing to one for each level.
872+
for level_cube in cube.slices_over(vertical_coordinate):
873+
# Note setting density to True does not produce the correct
874+
# normalization for a CFAD, where each row must sum to one.
875+
CFAD_values[i, :] = (
876+
np.histogram(level_cube.data.reshape(level_cube.data.size), bins=bin_edges)[
877+
0
878+
]
879+
/ level_cube.data.size
880+
)
881+
i += 1
882+
# calculate central points for bins
883+
bins = (np.array(bin_edges[:-1]) + np.array(bin_edges[1:])) / 2.0
884+
bin_bounds = np.array((bin_edges[:-1], bin_edges[1:])).T
885+
# Now construct the coordinates for the cube.
886+
vert_coord = cube.coord(vertical_coordinate)
887+
bin_coord = iris.coords.DimCoord(
888+
bins, bounds=bin_bounds, standard_name=cube.standard_name, units=cube.units
889+
)
890+
# Now construct the cube that is to be output.
891+
CFAD = iris.cube.Cube(
892+
CFAD_values,
893+
dim_coords_and_dims=[(vert_coord, 0), (bin_coord, 1)],
894+
standard_name=cube.standard_name,
895+
units="1",
896+
)
897+
CFAD.attributes["type"] = "Contour Frequency by Altitude Diagram (CFAD)"
898+
return CFAD
899+
900+
847901
####################
848902
# Public functions #
849903
####################

tests/operators/test_plots.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,18 @@
1717
from pathlib import Path
1818

1919
import iris.cube
20+
import numpy as np
2021
import pytest
2122

2223
from CSET.operators import collapse, plot, read
2324

2425

26+
@pytest.fixture()
27+
def xwind() -> iris.cube.Cube:
28+
"""Get regridded xwind to run tests on."""
29+
return iris.load_cube("tests/test_data/ageofair/aoa_in_rgd.nc", "x_wind")
30+
31+
2532
def test_check_single_cube():
2633
"""Conversion to a single cube, and rejection where not possible."""
2734
cube = iris.cube.Cube([0.0])
@@ -318,3 +325,24 @@ def test_invalid_plotting_method_postage_stamp_spatial_plot(cube, tmp_working_di
318325
plot._plot_and_save_postage_stamp_spatial_plot(
319326
cube, "filename", "realization", "title", "invalid"
320327
)
328+
329+
330+
def test_calculate_CFAD(xwind):
331+
"""Test calculating a CFAD."""
332+
bins = np.array([-50, -40, -30, -20, -10, 0, 10, 20, 30, 40, 50])
333+
calculated_CFAD = np.zeros((len(xwind.coord("pressure").points), len(bins) - 1))
334+
j = 0
335+
for level_cube in xwind.slices_over("pressure"):
336+
calculated_CFAD[j, :] = (
337+
np.histogram(level_cube.data.reshape(level_cube.data.size), bins=bins)[0]
338+
/ level_cube.data.size
339+
)
340+
j += 1
341+
assert np.allclose(
342+
plot._calculate_CFAD(
343+
xwind, "pressure", [-50, -40, -30, -20, -10, 0, 10, 20, 30, 40, 50]
344+
).data,
345+
calculated_CFAD,
346+
rtol=1e-06,
347+
atol=1e-02,
348+
)

0 commit comments

Comments
 (0)