From c43e3c5bc51f7dc931246753abc78799b44ef218 Mon Sep 17 00:00:00 2001 From: Phil Relton Date: Mon, 2 Dec 2024 11:43:19 +0000 Subject: [PATCH 1/9] Adding code and cli for VirtualTemperature calculation --- improver/cli/calculate_virtual_temperature.py | 30 ++++++++++ improver/virtual_temperature.py | 58 +++++++++++++++++++ 2 files changed, 88 insertions(+) create mode 100644 improver/cli/calculate_virtual_temperature.py create mode 100644 improver/virtual_temperature.py diff --git a/improver/cli/calculate_virtual_temperature.py b/improver/cli/calculate_virtual_temperature.py new file mode 100644 index 0000000000..ca368b4b97 --- /dev/null +++ b/improver/cli/calculate_virtual_temperature.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python +# (C) Crown Copyright, Met Office. All rights reserved. +# +# This file is part of 'IMPROVER' and is released under the BSD 3-Clause license. +# See LICENSE in the root of the repository for full licensing details. +"""CLI to calculate the virtual temperature from temperature and specific humidity.""" + +from improver import cli + + +@cli.clizefy +@cli.with_output +def process(*cubes: cli.inputcubelist): + """Calculate the virtual temperature from temperature and specific humidity. + + Args: + cubes (iris.cube.CubeList or list of iris.cube.Cube): + containing: + temperature (iris.cube.Cube): + Cube of temperature on pressure levels + specific_humidity (iris.cube.Cube): + Cube of specific humidity on pressure levels + + Returns: + iris.cube.Cube: + Cube of air_temperature. + """ + from improver.virtual_temperature import VirtualTemperature + + return VirtualTemperature(*cubes) diff --git a/improver/virtual_temperature.py b/improver/virtual_temperature.py new file mode 100644 index 0000000000..c5f02ce955 --- /dev/null +++ b/improver/virtual_temperature.py @@ -0,0 +1,58 @@ +# (C) Crown Copyright, Met Office. All rights reserved. +# +# This file is part of 'IMPROVER' and is released under the BSD 3-Clause license. +# See LICENSE in the root of the repository for full licensing details. +"""Calculate the gradient between two vertical levels.""" + +from typing import Union + +from iris.cube import Cube, CubeList + +from improver import BasePlugin +from improver.utilities.common_input_handle import as_cubelist + + +class VirtualTemperature(BasePlugin): + """Calculates the virtual temperature from temperature and specific humidity.""" + + @staticmethod + def get_virtual_temperature(temperature: Cube, specific_humidity: Cube) -> Cube: + """ + Calculate the virtual temperature from temperature and specific humidity. + + Args: + temperature: + Cube of temperature + specific_humidity: + Cube of specific humidity + + Returns: + Cube of virtual temperature + """ + # Calculate the virtual temperature + virtual_temperature = temperature * (1 + 0.61 * specific_humidity) + virtual_temperature.rename("air_temperature") + + return virtual_temperature + + def process(self, *cubes: Union[Cube, CubeList]) -> Cube: + """ + Main entry point for this class. + + Args: + cubes: + air_temperature: + Cube of temperature on pressure levels + specific_humidity: + Cube of specific humidity on pressure levels. + + Returns: + Cube of virtual temperature. + """ + + cubes = as_cubelist(*cubes) + temperature, specific_humidity = cubes.extract( + ["air_temperature", "specific_humidity"] + ) + + return self.get_virtual_temperature(temperature, specific_humidity) From 94f16472f8c1c228382b628a8eadd0bcc8e1d580 Mon Sep 17 00:00:00 2001 From: Phil Relton Date: Mon, 2 Dec 2024 15:26:12 +0000 Subject: [PATCH 2/9] Making cubes match CF conventions --- improver/cli/calculate_virtual_temperature.py | 14 +++---- improver/virtual_temperature.py | 41 +++++++++++-------- 2 files changed, 31 insertions(+), 24 deletions(-) diff --git a/improver/cli/calculate_virtual_temperature.py b/improver/cli/calculate_virtual_temperature.py index ca368b4b97..40dd2b6608 100644 --- a/improver/cli/calculate_virtual_temperature.py +++ b/improver/cli/calculate_virtual_temperature.py @@ -3,7 +3,7 @@ # # This file is part of 'IMPROVER' and is released under the BSD 3-Clause license. # See LICENSE in the root of the repository for full licensing details. -"""CLI to calculate the virtual temperature from temperature and specific humidity.""" +"""CLI to calculate the virtual temperature from temperature and humidity mixing ratio.""" from improver import cli @@ -11,20 +11,20 @@ @cli.clizefy @cli.with_output def process(*cubes: cli.inputcubelist): - """Calculate the virtual temperature from temperature and specific humidity. + """Calculate the virtual temperature from temperature and humidity mixing ratio. Args: cubes (iris.cube.CubeList or list of iris.cube.Cube): containing: temperature (iris.cube.Cube): - Cube of temperature on pressure levels - specific_humidity (iris.cube.Cube): - Cube of specific humidity on pressure levels + Cube of temperature. + humidity_mixing_ratio (iris.cube.Cube): + Cube of humidity mixing ratio. Returns: iris.cube.Cube: - Cube of air_temperature. + Cube of virtual_temperature. """ from improver.virtual_temperature import VirtualTemperature - return VirtualTemperature(*cubes) + return VirtualTemperature()(*cubes) diff --git a/improver/virtual_temperature.py b/improver/virtual_temperature.py index c5f02ce955..c5caf97ad2 100644 --- a/improver/virtual_temperature.py +++ b/improver/virtual_temperature.py @@ -2,7 +2,7 @@ # # This file is part of 'IMPROVER' and is released under the BSD 3-Clause license. # See LICENSE in the root of the repository for full licensing details. -"""Calculate the gradient between two vertical levels.""" +"""Calculate the virtual temperature from temperature and humidity mixing ratio.""" from typing import Union @@ -13,25 +13,28 @@ class VirtualTemperature(BasePlugin): - """Calculates the virtual temperature from temperature and specific humidity.""" + """Calculates the virtual temperature from temperature and .""" @staticmethod - def get_virtual_temperature(temperature: Cube, specific_humidity: Cube) -> Cube: + def get_virtual_temperature(temperature: Cube, humidity_mixing_ratio: Cube) -> Cube: """ - Calculate the virtual temperature from temperature and specific humidity. + Calculate the virtual temperature from temperature and humidity mixing ratio. Args: temperature: - Cube of temperature - specific_humidity: - Cube of specific humidity + Cube of temperature. + humidity_mixing_ratio: + Cube of humidity mixing ratio. Returns: - Cube of virtual temperature + Cube of virtual_temperature. """ # Calculate the virtual temperature - virtual_temperature = temperature * (1 + 0.61 * specific_humidity) - virtual_temperature.rename("air_temperature") + virtual_temperature = temperature * (1 + 0.61 * humidity_mixing_ratio) + + # Update the cube metadata + virtual_temperature.rename("virtual_temperature") + virtual_temperature.attributes["units_metadata"] = "on-scale" return virtual_temperature @@ -42,17 +45,21 @@ def process(self, *cubes: Union[Cube, CubeList]) -> Cube: Args: cubes: air_temperature: - Cube of temperature on pressure levels - specific_humidity: - Cube of specific humidity on pressure levels. + Cube of temperature. + humidity_mixing_ratio: + Cube of humidity mixing ratios. Returns: - Cube of virtual temperature. + Cube of virtual_temperature. """ + # Get the cubes into the correct format and extract the relevant cubes cubes = as_cubelist(*cubes) - temperature, specific_humidity = cubes.extract( - ["air_temperature", "specific_humidity"] + (self.temperature, self.humidity_mixing_ratio) = cubes.extract_cubes( + ["air_temperature", "humidity_mixing_ratio"] ) - return self.get_virtual_temperature(temperature, specific_humidity) + # Calculate the virtual temperature + return self.get_virtual_temperature( + self.temperature, self.humidity_mixing_ratio + ) From 90384570f81a13528da941d47ed16c7fe3412c4d Mon Sep 17 00:00:00 2001 From: Phil Relton Date: Mon, 2 Dec 2024 15:56:27 +0000 Subject: [PATCH 3/9] Adding tests for VirtualTemperature plugin --- .../virtual_temperature/__init__.py | 0 .../test_VirtualTemperature.py | 78 +++++++++++++++++++ 2 files changed, 78 insertions(+) create mode 100644 improver_tests/virtual_temperature/__init__.py create mode 100644 improver_tests/virtual_temperature/test_VirtualTemperature.py diff --git a/improver_tests/virtual_temperature/__init__.py b/improver_tests/virtual_temperature/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/improver_tests/virtual_temperature/test_VirtualTemperature.py b/improver_tests/virtual_temperature/test_VirtualTemperature.py new file mode 100644 index 0000000000..ab5af79288 --- /dev/null +++ b/improver_tests/virtual_temperature/test_VirtualTemperature.py @@ -0,0 +1,78 @@ +# (C) Crown Copyright, Met Office. All rights reserved. +# +# This file is part of 'IMPROVER' and is released under the BSD 3-Clause license. +# See LICENSE in the root of the repository for full licensing details. +"""Unit tests for the VirtualTemperature plugin""" + +from unittest.mock import patch, sentinel + +import numpy as np +import pytest +from iris.cube import Cube + +from improver.virtual_temperature import VirtualTemperature +from improver_tests.utilities.copy_attributes.test_CopyAttributes import HaltExecution + + +@pytest.fixture(name="temperature_cube") +def temperature_cube_fixture() -> Cube: + """ + Set up a temperature cube for use in tests. + Has 2 realizations, 7 latitudes spanning from 60S to 60N and 3 longitudes. + """ + data = np.full((2, 7, 3), dtype=np.float32, fill_value=273.15) + cube = Cube( + data, + standard_name="air_temperature", + units="K", + ) + return cube + + +@pytest.fixture(name="humidity_mixing_ratio_cube") +def humidity_mixing_ratio_cube_fixture() -> Cube: + """ + Set up a humidity mixing ratio cube for use in tests. + Has 2 realizations, 7 latitudes spanning from 60S to 60N and 3 longitudes. + """ + data = np.full((2, 7, 3), dtype=np.float32, fill_value=0.01) + cube = Cube( + data, + standard_name="humidity_mixing_ratio", + units="1", + ) + return cube + + +@pytest.fixture(name="virtual_temperature_cube") +def virtual_temperature_cube_fixture() -> Cube: + """ + Set up a virtual temperature cube for use in tests. + Has 2 realizations, 7 latitudes spanning from 60S to 60N and 3 longitudes. + """ + data = np.full((2, 7, 3), dtype=np.float32, fill_value=274.81622) + cube = Cube( + data, + standard_name="virtual_temperature", + units="K", + attributes={"units_metadata": "on-scale"}, + ) + return cube + + +@patch("improver.virtual_temperature.as_cubelist") +def test_as_cubelist_called(mock_as_cubelist): + mock_as_cubelist.side_effect = HaltExecution + try: + VirtualTemperature()(sentinel.cube1, sentinel.cube2) + except HaltExecution: + pass + mock_as_cubelist.assert_called_once_with(sentinel.cube1, sentinel.cube2) + + +def test_VirtualTemperature_get_virtual_temperature( + temperature_cube, humidity_mixing_ratio_cube, virtual_temperature_cube +): + """Test the get_virtual_temperature method""" + result = VirtualTemperature()(temperature_cube, humidity_mixing_ratio_cube) + assert result == virtual_temperature_cube From 03393e9535e068635434e894d486c73288790967 Mon Sep 17 00:00:00 2001 From: Phil Relton Date: Mon, 2 Dec 2024 16:20:09 +0000 Subject: [PATCH 4/9] Adding missing docstrings to tests --- improver_tests/virtual_temperature/test_VirtualTemperature.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/improver_tests/virtual_temperature/test_VirtualTemperature.py b/improver_tests/virtual_temperature/test_VirtualTemperature.py index ab5af79288..403f3243de 100644 --- a/improver_tests/virtual_temperature/test_VirtualTemperature.py +++ b/improver_tests/virtual_temperature/test_VirtualTemperature.py @@ -62,6 +62,7 @@ def virtual_temperature_cube_fixture() -> Cube: @patch("improver.virtual_temperature.as_cubelist") def test_as_cubelist_called(mock_as_cubelist): + """Test that the as_cubelist function is called with the input cubes""" mock_as_cubelist.side_effect = HaltExecution try: VirtualTemperature()(sentinel.cube1, sentinel.cube2) @@ -73,6 +74,6 @@ def test_as_cubelist_called(mock_as_cubelist): def test_VirtualTemperature_get_virtual_temperature( temperature_cube, humidity_mixing_ratio_cube, virtual_temperature_cube ): - """Test the get_virtual_temperature method""" + """Test the get_virtual_temperature method produces a virtual temperature cube""" result = VirtualTemperature()(temperature_cube, humidity_mixing_ratio_cube) assert result == virtual_temperature_cube From 5705e695d158d144fcfcbaac036cbbb319793622 Mon Sep 17 00:00:00 2001 From: Phil Relton Date: Tue, 3 Dec 2024 09:23:36 +0000 Subject: [PATCH 5/9] Updating CLA with my details --- CONTRIBUTING.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8dd2566698..74d1df65de 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -66,6 +66,7 @@ below: - Benjamin Owen (Bureau of Meteorology, Australia) - Carwyn Pelley (Met Office, UK) - Tim Pillinger (Met Office, UK) + - Philip Relton (Met Office, UK) - Fiona Rust (Met Office, UK) - Chris Sampson (Met Office, UK) - Caroline Sandford (Met Office, UK) From 673d9c0a6471f05c7adbbd74f716021130dc6daf Mon Sep 17 00:00:00 2001 From: Phil Relton Date: Tue, 3 Dec 2024 09:30:11 +0000 Subject: [PATCH 6/9] Removing CLI due to move to dag runner --- improver/cli/calculate_virtual_temperature.py | 30 ------------------- 1 file changed, 30 deletions(-) delete mode 100644 improver/cli/calculate_virtual_temperature.py diff --git a/improver/cli/calculate_virtual_temperature.py b/improver/cli/calculate_virtual_temperature.py deleted file mode 100644 index 40dd2b6608..0000000000 --- a/improver/cli/calculate_virtual_temperature.py +++ /dev/null @@ -1,30 +0,0 @@ -#!/usr/bin/env python -# (C) Crown Copyright, Met Office. All rights reserved. -# -# This file is part of 'IMPROVER' and is released under the BSD 3-Clause license. -# See LICENSE in the root of the repository for full licensing details. -"""CLI to calculate the virtual temperature from temperature and humidity mixing ratio.""" - -from improver import cli - - -@cli.clizefy -@cli.with_output -def process(*cubes: cli.inputcubelist): - """Calculate the virtual temperature from temperature and humidity mixing ratio. - - Args: - cubes (iris.cube.CubeList or list of iris.cube.Cube): - containing: - temperature (iris.cube.Cube): - Cube of temperature. - humidity_mixing_ratio (iris.cube.Cube): - Cube of humidity mixing ratio. - - Returns: - iris.cube.Cube: - Cube of virtual_temperature. - """ - from improver.virtual_temperature import VirtualTemperature - - return VirtualTemperature()(*cubes) From 88599e228864821d8e9267bd555aa29ff06fda57 Mon Sep 17 00:00:00 2001 From: Phil Relton Date: Tue, 3 Dec 2024 09:36:19 +0000 Subject: [PATCH 7/9] Adding myself to the .mailmap file --- .mailmap | 1 + 1 file changed, 1 insertion(+) diff --git a/.mailmap b/.mailmap index 4a4ca333f4..7c84082b4d 100644 --- a/.mailmap +++ b/.mailmap @@ -29,6 +29,7 @@ Meabh NicGuidhir <43375279+neilCrosswaite@users.noreply.github.com> Paul Abernethy Peter Jordan <52462411+mo-peterjordan@users.noreply.github.com> +Philip Relton Sam Griffiths <122271903+SamGriffithsMO@users.noreply.github.com> Shafiat Dewan <87321907+ShafiatDewan@users.noreply.github.com> <87321907+ShafiatDewan@users.noreply.github.com> Shubhendra Singh Chauhan From bf1e854f46ac6626df65dec78a88e4f4fd8b3a4a Mon Sep 17 00:00:00 2001 From: Phil Relton Date: Fri, 6 Dec 2024 09:13:05 +0000 Subject: [PATCH 8/9] Attempting to fix mailmap CI and fixes from first review --- .mailmap | 2 +- improver/virtual_temperature.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.mailmap b/.mailmap index 7c84082b4d..1140018352 100644 --- a/.mailmap +++ b/.mailmap @@ -29,7 +29,7 @@ Meabh NicGuidhir <43375279+neilCrosswaite@users.noreply.github.com> Paul Abernethy Peter Jordan <52462411+mo-peterjordan@users.noreply.github.com> -Philip Relton +Phil Relton Sam Griffiths <122271903+SamGriffithsMO@users.noreply.github.com> Shafiat Dewan <87321907+ShafiatDewan@users.noreply.github.com> <87321907+ShafiatDewan@users.noreply.github.com> Shubhendra Singh Chauhan diff --git a/improver/virtual_temperature.py b/improver/virtual_temperature.py index c5caf97ad2..a3c7a69766 100644 --- a/improver/virtual_temperature.py +++ b/improver/virtual_temperature.py @@ -13,7 +13,7 @@ class VirtualTemperature(BasePlugin): - """Calculates the virtual temperature from temperature and .""" + """Plugin class to handle virtual temperature calculations.""" @staticmethod def get_virtual_temperature(temperature: Cube, humidity_mixing_ratio: Cube) -> Cube: From 1738285fce074ec506058399de5819d369f9bce9 Mon Sep 17 00:00:00 2001 From: Phil Relton Date: Fri, 6 Dec 2024 09:15:44 +0000 Subject: [PATCH 9/9] Another attempt to get the CI to pass for my name --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 74d1df65de..96dc596c0e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -66,7 +66,7 @@ below: - Benjamin Owen (Bureau of Meteorology, Australia) - Carwyn Pelley (Met Office, UK) - Tim Pillinger (Met Office, UK) - - Philip Relton (Met Office, UK) + - Phil Relton (Met Office, UK) - Fiona Rust (Met Office, UK) - Chris Sampson (Met Office, UK) - Caroline Sandford (Met Office, UK)