Skip to content

Commit

Permalink
Style fixes.
Browse files Browse the repository at this point in the history
  • Loading branch information
bayliffe committed Dec 16, 2024
1 parent a138358 commit d1b446a
Show file tree
Hide file tree
Showing 3 changed files with 74 additions and 23 deletions.
2 changes: 1 addition & 1 deletion improver/cli/duration_subdivision.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ def process(
divided and to which the night mask is applied. The
target periods are reconstructed from these shorter periods.
Shorter fidelity periods better capture where the day / night
dicriminator falls.
discriminator falls.
night_mask (bool):
If true, points that fall at night are zeroed and duration
reallocated to day time periods as much as possible.
Expand Down
37 changes: 22 additions & 15 deletions improver/utilities/temporal_interpolation.py
Original file line number Diff line number Diff line change
Expand Up @@ -716,7 +716,13 @@ class DurationSubdivision:
when thick cloud might occur.
"""

def __init__(self, target_period, fidelity, night_mask=True, day_mask=False):
def __init__(
self,
target_period: int,
fidelity: int,
night_mask: bool = True,
day_mask: bool = False,
):
"""Define the length of the target periods to be constructed and the
intermediate fidelity. This fidelity is the length of the shorter
periods into which the data is split and from which the target periods
Expand All @@ -733,15 +739,16 @@ def __init__(self, target_period, fidelity, night_mask=True, day_mask=False):
divided and to which the night mask is applied. The
target periods are reconstructed from these shorter periods.
Shorter fidelity periods better capture where the day / night
dicriminator falls.
discriminator falls.
night_mask:
If true, points that fall at night are zeroed and duration
reallocated to day time periods as much as possible.
day_mask:
If true, points that fall in the day time are zeroed and
duration reallocated to night time periods as much as possible.
Raises:
ValurError: If day and night mask options are both set True.
ValueError: If target_period and / or fidelity are not positive integers.
ValueError: If day and night mask options are both set True.
"""
for item in [target_period, fidelity]:
if item <= 0:
Expand All @@ -763,7 +770,7 @@ def __init__(self, target_period, fidelity, night_mask=True, day_mask=False):
self.mask_value = 0 if night_mask else 1

@staticmethod
def cube_period(cube):
def cube_period(cube: Cube) -> int:
"""Return the time period of the cube in seconds.
Args:
Expand All @@ -776,7 +783,7 @@ def cube_period(cube):
(period,) = np.diff(cube.coord("time").bounds[0])
return period

def allocate_data(self, cube, period):
def allocate_data(self, cube: Cube, period: int) -> Cube:
"""Allocate fractions of the total duration to the shorter periods
and modify the metadata of the shorter period cubes appropriately.
Expand All @@ -785,7 +792,7 @@ def allocate_data(self, cube, period):
The original period cube from which duration data will be
taken and divided up.
period:
The target shorter period
The target shorter period in seconds.
Returns:
A cube, with a time dimension, that contains the subdivided data.
"""
Expand All @@ -810,17 +817,17 @@ def allocate_data(self, cube, period):
interval_cube.coord("time").bounds = np.array(
[[interval_start, interval_end]], dtype=np.int64
)
daynight_mask = daynightplugin(interval_cube).data
daynight_mask = np.broadcast_to(daynight_mask, interval_cube.shape)

if self.mask_value is not None:
daynight_mask = daynightplugin(interval_cube).data
daynight_mask = np.broadcast_to(daynight_mask, interval_cube.shape)
interval_cube.data[daynight_mask == self.mask_value] = 0.0
interpolated_cubes.append(interval_cube)

return interpolated_cubes.merge_cube()

@staticmethod
def renormalisation_factor(cube, fidelity_period_cube):
def renormalisation_factor(cube: Cube, fidelity_period_cube: Cube) -> np.ndarray:
"""Sum up the total of the durations distributed amongst the fidelity
period cubes following the application of any masking. These are
then used with the durations in the unsubdivided original data to
Expand All @@ -835,9 +842,9 @@ def renormalisation_factor(cube, fidelity_period_cube):
divided up into shorter fidelity periods).
Returns:
factor:
A factor that can be used to multiply up the fidelity period
durations such that when the are summed up they are equal
to the original durations.
An array of factors that can be used to multiply up the
fidelity period durations such that when the are summed up
they are equal to the original durations.
"""
retotal = fidelity_period_cube.collapsed("time", iris.analysis.SUM)
factor = cube.data / retotal.data
Expand All @@ -850,7 +857,7 @@ def renormalisation_factor(cube, fidelity_period_cube):

return factor

def construct_target_periods(self, fidelity_period_cube):
def construct_target_periods(self, fidelity_period_cube: Cube) -> Cube:
"""Combine the short fidelity period cubes into cubes that describe
the target period.
Expand Down Expand Up @@ -882,7 +889,7 @@ def construct_target_periods(self, fidelity_period_cube):

return new_period_cubes.merge_cube()

def process(self, cube):
def process(self, cube: Cube) -> Cube:
"""Create target period duration diagnostics from the original duration
diagnostic data.
Expand All @@ -900,7 +907,7 @@ def process(self, cube):
if period / self.target_period % 1 != 0:
raise ValueError(
"The target period must be a factor of the original period "
"of the input cube and the target period must >= the input "
"of the input cube and the target period must <= the input "
"period. "
f"Input period: {period}, target period: {self.target_period}"
)
Expand Down
58 changes: 51 additions & 7 deletions improver_tests/utilities/test_DurationSubdivision.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ def multi_time_cube(
return cubes.merge_cube()


def fidelity_cube(data: np.ndarray, period: int, fidelity_period: int):
def fidelity_cube(data: np.ndarray, period: int, fidelity_period: int) -> Cube:
"""Define a cube with an equally spaced leading time coordinate that
splits the period defined into shorter periods equal in length to the
fidelity period. Divide the data amongst these equally."""
Expand All @@ -155,15 +155,15 @@ def fidelity_cube(data: np.ndarray, period: int, fidelity_period: int):


@pytest.fixture
def basic_cube(period: int):
def basic_cube(period: int) -> Cube:
"""Define a cube with default values except for the period."""

data = np.ones((5, 5), dtype=np.float32)
return diagnostic_cube(data, period=period)


@pytest.fixture
def data_cube(data: np.ndarray, time: dt, period: int, realizations: List[int]):
def data_cube(data: np.ndarray, time: dt, period: int, realizations: List[int]) -> Cube:
"""Define a cube with specific period, data, and validity time."""

return diagnostic_cube(
Expand All @@ -172,7 +172,7 @@ def data_cube(data: np.ndarray, time: dt, period: int, realizations: List[int]):


@pytest.fixture
def renormalisation_cubes(times: List[dt], data: np.ndarray):
def renormalisation_cubes(times: List[dt], data: np.ndarray) -> Tuple[Cube, Cube]:
"""Define a cube with an equally spaced leading time coordinate and
specific data, which is a equal subdivision of the input data which
is also returned in a single period cube."""
Expand Down Expand Up @@ -384,6 +384,9 @@ def test_allocate_data(data_cube, kwargs, data, time, period, realizations):
assert (collapsed_rslice <= data).all()


@pytest.mark.parametrize(
"masked", [False, True]
) # Make the input cube data into a masked numpy array if True.
@pytest.mark.parametrize(
"times,data,masking,expected_factors",
[
Expand Down Expand Up @@ -431,15 +434,24 @@ def test_allocate_data(data_cube, kwargs, data, time, period, realizations):
0,
0,
], # Expected factors at points that are zeroed, 1 everywhere else by construction.
), # All fidelity periods are zeroed, so the factor returned is forced to zero.
), # All fidelity periods are zeroed, so the factor returned is forced to zero. When
# masked=True this is via the .filled method, and when masked=False the
# except statement it used.
],
)
def test_renormalisation_factor(renormalisation_cubes, masking, expected_factors):
def test_renormalisation_factor(
renormalisation_cubes, masking, expected_factors, masked
):
"""Test the renormalisation_factor method returns the array of renormalisation
factors."""
plugin = DurationSubdivision(target_period=10, fidelity=1) # Settings irrelevant
cube, fidelity_period_cube = renormalisation_cubes

# Make cube data type into masked array to check the function works using the
# .filled method.
if masked:
cube.data = np.ma.masked_array(cube.data)

# Zero some data points in the fidelity cubes to simulate masking impact.
# And construct expected factors array.
expected = np.ones((cube.shape))
Expand Down Expand Up @@ -538,7 +550,7 @@ def test_construct_target_periods(kwargs, data, input_period, expected):
None, # List of realization numbers if any
(
"The target period must be a factor of the original period "
"of the input cube and the target period must >= the input "
"of the input cube and the target period must <= the input "
"period. Input period: 3600, target period: 7200"
), # Expected exception
), # Raise a ValueError as the target period is longer than the input period.
Expand Down Expand Up @@ -570,6 +582,38 @@ def test_construct_target_periods(kwargs, data, input_period, expected):
None, # List of realization numbers if any
None, # Expected exception
), # Demonstate clipping of input data as input data exceeds the period.
(
{
"target_period": 3600,
"fidelity": 900,
"night_mask": True,
"day_mask": False,
},
np.full((2, 2), 1800), # Data in the input cube.
dt(2024, 6, 15, 21), # Validity time
7200, # Input period
np.array(
[
[[1028.5715, 0], [900, 900]],
[[771.42865, 0], [900, 900]],
],
dtype=np.float32,
), # Expected data in the output cube.
None, # List of realization numbers if any
None, # Expected exception
), # A 2-hour period containing just 30 minutes of sunshine duration.
# The night mask is applied, which affects the the northern most latitudes.
# The north-east most cell (top-right) is entirely zeroed in both target
# times, meaning all of the sunshine duration is lost. The north-west most
# cell (top-left) is masked in a single of the 900 second fidelity periods
# that is generated. This results in the original 1800 seconds of sunshine
# duration being renormalised in the day light period fidelity periods
# for this cell. Instead of 225 seconds (1800 / 8) each fidelity period
# contains (1800 / 7) seconds of sunshine duration. This is within the 900
# seconds possible, so no clipping is applied. The total across the two
# final 1-hour periods generated for this north-west cell is still 1800
# seconds of sunshine duration, but it is split unevenly to reflect that the
# later period is partially a night time period.
(
{
"target_period": 3600,
Expand Down

0 comments on commit d1b446a

Please sign in to comment.