Skip to content

Commit

Permalink
MAINT: changes based on feedback
Browse files Browse the repository at this point in the history
  • Loading branch information
cpelley committed Jul 5, 2024
1 parent 1f56e8d commit 5c9c797
Show file tree
Hide file tree
Showing 7 changed files with 56 additions and 112 deletions.
13 changes: 2 additions & 11 deletions improver/cli/extract.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,15 +54,6 @@ def process(
"""
from improver.utilities.cube_extraction import ExtractSubCube

plugin = ExtractSubCube(constraints, units=units)
try:
result = plugin.process(cube)
except ValueError as err:
if (
err.args[0] == "Constraint(s) could not be matched in input cube"
and ignore_failure
):
return cube
else:
raise
plugin = ExtractSubCube(constraints, units=units, ignore_failure=ignore_failure)
result = plugin.process(cube)
return result
3 changes: 1 addition & 2 deletions improver/utilities/complex_conversion.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@ def deg_to_complex(
) -> Union[ndarray, float]:
"""Converts degrees to complex values.
The radius value can be used to weigh values - but it is set
to 1 for now.
The radius argument can be used to weight values. Defaults to 1.
Args:
angle_deg:
Expand Down
15 changes: 11 additions & 4 deletions improver/utilities/cube_extraction.py
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,7 @@ def __init__(
constraints: List[str],
units: Optional[List[str]] = None,
use_original_units: bool = True,
ignore_failure: bool = False,
) -> None:
"""
Set up the ExtractSubCube plugin.
Expand All @@ -290,10 +291,14 @@ def __init__(
should be converted back to their original units. The default is
True, indicating that the units should be converted back to the
original units.
ignore_failure:
Option to ignore constraint match failure and return the input
cube.
"""
self._constraints = constraints
self._units = units
self._use_original_units = use_original_units
self._ignore_failure = ignore_failure

def process(self, cube: Cube):
"""Perform the subcube extraction.
Expand All @@ -309,12 +314,14 @@ def process(self, cube: Cube):
ValueError: If the constraint(s) could not be matched to the input cube.
"""
cube = as_cube(cube)
cube = extract_subcube(
res = extract_subcube(
cube, self._constraints, self._units, self._use_original_units
)
if cube is None:
raise ValueError("Constraint(s) could not be matched in input cube")
return cube
if res is None:
res = cube
if not self._ignore_failure:
raise ValueError("Constraint(s) could not be matched in input cube")
return res


def thin_cube(cube: Cube, thinning_dict: Dict[str, int]) -> Cube:
Expand Down
47 changes: 0 additions & 47 deletions improver/wind_calculations/wind_direction.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,6 @@
# This file is part of IMPROVER and is released under a BSD 3-Clause license.
# See LICENSE in the root of the repository for full licensing details.
"""Module containing wind direction averaging plugins."""

from typing import Union

import iris
import numpy as np
from iris.coords import CellMethod
Expand Down Expand Up @@ -105,50 +102,6 @@ def _reset(self) -> None:
self.wdir_mean_complex = None
self.r_vals_slice = None

@staticmethod
def deg_to_complex(
angle_deg: Union[ndarray, float], radius: Union[ndarray, float] = 1
) -> Union[ndarray, float]:
"""Converts degrees to complex values.
See deg_to_complex
The radius value can be used to weigh values - but it is set
to 1 for now.
Args:
angle_deg:
3D array or float - wind direction angles in degrees.
radius:
3D array or float - radius value for each point, default=1.
Returns:
3D array or float - wind direction translated to
complex numbers.
"""
return deg_to_complex(angle_deg, radius)

@staticmethod
def complex_to_deg(complex_in: ndarray) -> ndarray:
"""Converts complex to degrees.
The "np.angle" function returns negative numbers when the input
is greater than 180. Therefore additional processing is needed
to ensure that the angle is between 0-359.
Args:
complex_in:
3D array - wind direction angles in
complex number form.
Returns:
3D array - wind direction in angle form
Raises:
TypeError: If complex_in is not an array.
"""
return complex_to_deg(complex_in)

def calc_wind_dir_mean(self) -> None:
"""Find the mean wind direction using complex average which actually
signifies a point between all of the data points in POLAR
Expand Down
25 changes: 25 additions & 0 deletions improver_tests/utilities/complex_conversion/test_integration.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# (C) Crown copyright, Met Office. All rights reserved.
#
# This file is part of IMPROVER and is released under a BSD 3-Clause license.
# See LICENSE in the root of the repository for full licensing details.
import numpy as np
from numpy.testing import assert_array_almost_equal

from improver.utilities.complex_conversion import complex_to_deg, deg_to_complex

from . import COMPLEX_ANGLES


def test_roundtrip_complex_deg_complex():
"""Tests that array of values are converted to degrees and back."""
tmp_degrees = complex_to_deg(COMPLEX_ANGLES)
result = deg_to_complex(tmp_degrees)
assert_array_almost_equal(result, COMPLEX_ANGLES)


def test_roundtrip_deg_complex_deg():
"""Tests that array of values are converted to complex and back."""
src_degrees = np.arange(0.0, 360, 10)
tmp_complex = deg_to_complex(src_degrees)
result = complex_to_deg(tmp_complex)
assert_array_almost_equal(result, src_degrees)
13 changes: 12 additions & 1 deletion improver_tests/utilities/cube_extraction/test_ExtractSubCube.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ def test_ui(mock_extract_subcube, mock_as_cube):
sentinel.constraints,
units=sentinel.units,
use_original_units=sentinel.use_original_units,
ignore_failure=sentinel.ignore_failure,
)
plugin(sentinel.cube)
mock_as_cube.assert_called_once_with(sentinel.cube)
Expand All @@ -31,7 +32,17 @@ def test_no_matching_constraint_exception():
"""
Test that a ValueError is raised when no constraints match.
"""
plugin = ExtractSubCube(["dummy_name=dummy_value"])
plugin = ExtractSubCube(["dummy_name=dummy_value"], ignore_failure=False)
with pytest.raises(ValueError) as excinfo:
plugin(Cube(0))
assert str(excinfo.value) == "Constraint(s) could not be matched in input cube"


def test_no_matching_constraint_ignore():
"""
Test that the original cube is returned when no constraints match and ignore specified.
"""
plugin = ExtractSubCube(["dummy_name=dummy_value"], ignore_failure=True)
src_cube = Cube(0)
res = plugin(src_cube)
assert res is src_cube
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,14 @@
"""Unit tests for the wind_direction.WindDirection plugin."""

import unittest
import unittest.mock as mock

import numpy as np
from iris.coords import DimCoord
from iris.cube import Cube
from iris.tests import IrisTest

from improver.synthetic_data.set_up_test_cubes import set_up_variable_cube
from improver.wind_calculations.wind_direction import WindDirection
from improver.wind_calculations.wind_direction import WindDirection, deg_to_complex

# Data to test complex/degree handling functions.
# Complex angles equivalent to np.arange(0., 360, 10) degrees.
Expand Down Expand Up @@ -180,45 +179,6 @@ def test_basic(self):
self.assertEqual(result, msg)


@mock.patch("improver.wind_calculations.wind_direction.deg_to_complex")
def test_deg_to_complex(mock_deg_to_complex):
"""Tests that the underlying deg_to_complex function is
called via the deg_to_complex method."""
WindDirection().deg_to_complex(mock.sentinel.angle_deg, mock.sentinel.radius)
mock_deg_to_complex.assert_called_once_with(
mock.sentinel.angle_deg, mock.sentinel.radius
)


@mock.patch("improver.wind_calculations.wind_direction.complex_to_deg")
def test_complex_to_deg(mock_complex_to_deg):
"""Tests that the underlying complex_to_deg function is
called via the complex_to_deg method."""
WindDirection().complex_to_deg(mock.sentinel.complex_in)
mock_complex_to_deg.assert_called_once_with(mock.sentinel.complex_in)


class Test_complex_to_deg_roundtrip(IrisTest):
"""Test the complex_to_deg and deg_to_complex functions together."""

def setUp(self):
"""Initialise plugin and supply data for tests"""
self.plugin = WindDirection()
self.cube = make_wdir_cube_534()

def test_from_deg(self):
"""Tests that array of values are converted to complex and back."""
tmp_complex = self.plugin.deg_to_complex(self.cube.data)
result = self.plugin.complex_to_deg(tmp_complex)
self.assertArrayAlmostEqual(result, self.cube.data, decimal=4)

def test_from_complex(self):
"""Tests that array of values are converted to degrees and back."""
tmp_degrees = self.plugin.complex_to_deg(COMPLEX_ANGLES)
result = self.plugin.deg_to_complex(tmp_degrees)
self.assertArrayAlmostEqual(result, COMPLEX_ANGLES)


class Test_calc_wind_dir_mean(IrisTest):
"""Test the calc_wind_dir_mean function."""

Expand All @@ -227,7 +187,7 @@ def setUp(self):
self.plugin = WindDirection()
# 5x3x4 3D Array containing wind direction in angles.
cube = make_wdir_cube_534()
self.plugin.wdir_complex = self.plugin.deg_to_complex(cube.data)
self.plugin.wdir_complex = deg_to_complex(cube.data)
self.plugin.wdir_slice_mean = next(cube.slices_over("realization"))
self.plugin.realization_axis = 0

Expand All @@ -244,7 +204,7 @@ def test_complex(self):
"""Test that the function defines correct complex mean."""
self.plugin.calc_wind_dir_mean()
result = self.plugin.wdir_mean_complex
expected_complex = self.plugin.deg_to_complex(
expected_complex = deg_to_complex(
self.expected_wind_mean, radius=np.absolute(result)
)
self.assertArrayAlmostEqual(result, expected_complex)
Expand Down Expand Up @@ -312,9 +272,7 @@ def test_runs_function_1st_member(self):
self.plugin.wdir_slice_mean = cube[0].copy(
data=np.array([[180.0, 55.0], [280.0, 0.0]])
)
self.plugin.wdir_mean_complex = self.plugin.deg_to_complex(
self.plugin.wdir_slice_mean.data
)
self.plugin.wdir_mean_complex = deg_to_complex(self.plugin.wdir_slice_mean.data)
expected_out = np.array([[90.0, 55.0], [280.0, 0.0]])
where_low_r = np.array([[True, False], [False, False]])
self.plugin.wind_dir_decider(where_low_r, cube)
Expand Down Expand Up @@ -344,7 +302,7 @@ def test_runs_function_nbhood(self):
self.plugin.realization_axis = 0
self.plugin.n_realizations = 1
self.plugin.wdir_mean_complex = np.pad(
self.plugin.deg_to_complex(wind_dir_deg_mean),
deg_to_complex(wind_dir_deg_mean),
((4, 4), (4, 4)),
"constant",
constant_values=(0.0, 0.0),
Expand Down

0 comments on commit 5c9c797

Please sign in to comment.