diff --git a/improver/psychrometric_calculations/condensation_trails.py b/improver/psychrometric_calculations/condensation_trails.py index 72323c79c8..c420860340 100644 --- a/improver/psychrometric_calculations/condensation_trails.py +++ b/improver/psychrometric_calculations/condensation_trails.py @@ -11,6 +11,9 @@ from improver import BasePlugin from improver.constants import EARTH_REPSILON +from improver.psychrometric_calculations.psychrometric_calculations import ( + calculate_svp_in_air, +) from improver.utilities.common_input_handle import as_cubelist @@ -73,8 +76,32 @@ def _calculate_engine_mixing_ratios( / EARTH_REPSILON ) + def _find_local_vapour_pressure(self, pressure_levels: np.ndarray) -> np.ndarray: + """ + Calculate the local vapour pressure (svp) at the given pressure levels using the temperature and pressure data. + + Args: + pressure_levels (np.ndarray): Pressure levels (Pa). + + Returns: + np.ndarray: The localised vapour pressure at the given + pressure levels (Pa). + """ + # Pressure levels has to be reshaped to match the temperature and humidity dimensions + pressure_levels_reshaped = np.reshape( + pressure_levels, + (len(pressure_levels),) + (1,) * (self.temperature.ndim - 1), + ) + svp = calculate_svp_in_air( + temperature=self.temperature, pressure=pressure_levels_reshaped + ) + return self.relative_humidity * svp + def process_from_arrays( - self, temperature: np.ndarray, humidity: np.ndarray, pressure_levels: np.ndarray + self, + temperature: np.ndarray, + relative_humidity: np.ndarray, + pressure_levels: np.ndarray, ) -> np.ndarray: """ Main entry point of this class for data as Numpy arrays @@ -84,7 +111,7 @@ def process_from_arrays( Args: temperature (np.ndarray): Temperature data on pressure levels where pressure is the leading axis (K). - humidity (np.ndarray): Relative humidity data on pressure levels where pressure is the leading axis (kg kg-1). + relative_humidity (np.ndarray): Relative humidity data on pressure levels where pressure is the leading axis (kg kg-1). pressure_levels (np.ndarray): Pressure levels (Pa). Returns: @@ -92,11 +119,14 @@ def process_from_arrays( This is a placeholder until the full contrail formation logic is implemented. """ self.temperature = temperature - self.humidity = humidity + self.relative_humidity = relative_humidity self.pressure_levels = pressure_levels self.engine_mixing_ratios = self._calculate_engine_mixing_ratios( self.pressure_levels ) + self.local_vapour_pressure = self._find_local_vapour_pressure( + self.pressure_levels + ) return self.engine_mixing_ratios def process(self, *cubes: Union[Cube, CubeList]) -> Cube: diff --git a/improver_tests/psychrometric_calculations/condensation_trails/test_CondensationTrailFormation.py b/improver_tests/psychrometric_calculations/condensation_trails/test_CondensationTrailFormation.py index 860c154b53..23de4750b5 100644 --- a/improver_tests/psychrometric_calculations/condensation_trails/test_CondensationTrailFormation.py +++ b/improver_tests/psychrometric_calculations/condensation_trails/test_CondensationTrailFormation.py @@ -12,6 +12,9 @@ from improver.psychrometric_calculations.condensation_trails import ( CondensationTrailFormation, ) +from improver.psychrometric_calculations.psychrometric_calculations import ( + calculate_svp_in_air, +) from improver.synthetic_data.set_up_test_cubes import set_up_variable_cube LOCAL_MANDATORY_ATTRIBUTES = { @@ -168,3 +171,55 @@ def test_engine_mixing_ratio( # Check that _calculate_engine_mixing_ratios works after process mixing_ratios = plugin._calculate_engine_mixing_ratios(pressure_levels) np.testing.assert_array_equal(mixing_ratios, expected_mixing_ratios) + + +@pytest.mark.parametrize( + "temperature, relative_humidity, pressure_levels, expected_vapour_pressure", + [ + # Test with multiple pressure levels + ( + np.array([250.0, 260.0, 270.0], dtype=np.float32), + np.array([0.5, 0.6, 0.7], dtype=np.float32), + np.array([100000, 90000, 80000], dtype=np.float32), + calculate_svp_in_air( + np.array([250.0, 260.0, 270.0], dtype=np.float32), + np.array([100000, 90000, 80000], dtype=np.float32), + ) + * np.array([0.5, 0.6, 0.7], dtype=np.float32), + ), + # Test with single pressure level + ( + np.array([280.0], dtype=np.float32), + np.array([0.8], dtype=np.float32), + np.array([95000], dtype=np.float32), + calculate_svp_in_air( + np.array([280.0], dtype=np.float32), + np.array([95000], dtype=np.float32), + ) + * np.array([0.8], dtype=np.float32), + ), + ], +) +def test_find_local_vapour_pressure( + temperature: np.ndarray, + relative_humidity: np.ndarray, + pressure_levels: np.ndarray, + expected_vapour_pressure: np.ndarray, +) -> None: + """ + Test that _find_local_vapour_pressure returns the expected local vapour pressure values. + + This test sets the temperature and relative_humidity attributes on the plugin, + calls _find_local_vapour_pressure, and checks the output against expected values. + + Args: + temperature (np.ndarray): Array of temperature values (K). + relative_humidity (np.ndarray): Array of relative humidity values (fraction). + pressure_levels (np.ndarray): Array of pressure levels (Pa). + expected_vapour_pressure (np.ndarray): Expected vapour pressure output (Pa). + """ + plugin = CondensationTrailFormation() + plugin.temperature = temperature + plugin.relative_humidity = relative_humidity + result = plugin._find_local_vapour_pressure(pressure_levels) + np.testing.assert_allclose(result, expected_vapour_pressure)