Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add skeleton for generation of derived solar fields for RainForests #1705

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
6163941
Add modules to evaluate the derived solar fields.
benowen-bom Apr 12, 2022
a93be6b
Fill out Solar-time CLI, and add skeleton GenerateSolarTime Plugin.
benowen-bom Apr 12, 2022
f9ff51d
Fill out Clearsky solar-radiation CLI, and add skeleton GenerateClear…
benowen-bom Apr 12, 2022
d0f7362
Update docstrings.
benowen-bom Apr 12, 2022
83b1cab
Fix license.
benowen-bom Apr 12, 2022
fb0caff
Assume single valid time for linke turbidity rather than climatology.
benowen-bom Apr 12, 2022
af4159b
Add modules to evaluate the derived solar fields.
benowen-bom Apr 12, 2022
9de656c
Fix license.
benowen-bom Apr 12, 2022
f71a32b
Update typing in CLIs.
benowen-bom Apr 28, 2022
d2595f3
Linting.
benowen-bom Apr 28, 2022
b050644
Update temporal_spacing typing.
benowen-bom May 2, 2022
1bb3e36
Add datetime value converter.
benowen-bom May 2, 2022
d3e83c6
Update contributors list.
benowen-bom Apr 28, 2022
59a749a
Update interfaces to reflect logic around default values for alitude …
benowen-bom May 3, 2022
5638b18
Add function to create cube from target grid.
benowen-bom May 3, 2022
c508299
Change to using create_new_diagnostic_cube.
benowen-bom May 4, 2022
0e1926a
Formatting changes.
benowen-bom May 4, 2022
ac484db
Replace altitude by surface_altitude.
benowen-bom May 5, 2022
005a0b2
Update docstring for temporal_spacing.
benowen-bom May 5, 2022
2274143
Reorder args to ensure cubes are bucnhed together in the interface.
benowen-bom May 5, 2022
043db06
Change time to named arg to bring into line with other CLIs.
benowen-bom May 5, 2022
68dcb7c
Move initialisation of default cubes into plugin.
benowen-bom May 5, 2022
a52bf51
Add unit tests for _initialise_input_cubes.
benowen-bom May 5, 2022
3fa2414
Typo and linting fix.
benowen-bom May 5, 2022
25a1271
Update docstrings and fix typo, as per reviewer comments.
benowen-bom May 6, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ below:
* Daniel Mentiplay (Bureau of Meteorology, Australia)
* Stephen Moseley (Met Office, UK)
* Meabh NicGuidhir (Met Office, UK)
* Benjamin Owen (Bureau of Meteorology, Australia)
* Carwyn Pelley (Met Office, UK)
* Tim Pillinger (Met Office, UK)
* Fiona Rust (Met Office, UK)
Expand Down
17 changes: 17 additions & 0 deletions improver/cli/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,23 @@ def inputpath(to_convert):
return maybe_coerce_with(pathlib.Path, to_convert)


@value_converter
def inputdatetime(to_convert):
"""Converts string to datetime or returns passed object.

Args:
to_convert (string or datetime):
datetime represented as string of the format YYYYMMDDTHHMMZ

Returns:
(datetime): datetime object

"""
from improver.utilities.temporal import cycletime_to_datetime

return maybe_coerce_with(cycletime_to_datetime, to_convert)


def create_constrained_inputcubelist_converter(*constraints):
"""Makes function that the input constraints are used in a loop.

Expand Down
89 changes: 89 additions & 0 deletions improver/cli/generate_clearsky_solar_radiation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# -*- coding: utf-8 -*-
# -----------------------------------------------------------------------------
# (C) British Crown Copyright 2017-2021 Met Office.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# * Neither the name of the copyright holder nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
"""Script to run GenerateClearSkySolarRadiation ancillary generation."""

from improver import cli


@cli.clizefy
@cli.with_output
def process(
target_grid: cli.inputcube,
surface_altitude: cli.inputcube = None,
linke_turbidity: cli.inputcube = None,
*,
time: cli.inputdatetime,
accumulation_period: int,
temporal_spacing: int = 30,
):
"""Generate a cube containing clearsky solar radiation data, evaluated on the target grid
for the specified time and accumulation period. Accumulated clearsky solar radiation is used
as an input to the RainForests calibration for rainfall.

Args:
target_grid (iris.cube.Cube):
A cube containing the desired spatial grid.
surface_altitude (iris.cube.Cube):
Surface altitude data, specified in metres, used in the evaluation of the clearsky
solar irradiance values. If not provided, a cube with constant value 0.0 m is used,
created from target_grid.
linke_turbidity (iris.cube.Cube):
Linke turbidity data used in the evaluation of the clearsky solar irradiance
values. Linke turbidity is a dimensionless quantity that accounts for the
atmospheric scattering of radiation due to aerosols and water vapour, relative
to a dry atmosphere. If not provided, a cube with constant value 3.0 is used,
created from target_grid.
time (str):
A datetime specified in the format YYYYMMDDTHHMMZ at which to evaluate the
accumulated clearsky solar radiation. This time is taken to be the end of
the accumulation period.
accumulation_period (int):
The number of hours over which the solar radiation accumulation is defined.
temporal_spacing (int):
The time stepping, specified in mins, used in the integration of solar irradiance
to produce the accumulated solar radiation.

Returns:
iris.cube.Cube:
A cube containing accumulated clearsky solar radiation.
"""
from improver.generate_ancillaries.generate_derived_solar_fields import (
GenerateClearskySolarRadiation,
)

return GenerateClearskySolarRadiation()(
target_grid,
time,
accumulation_period,
surface_altitude=surface_altitude,
linke_turbidity=linke_turbidity,
temporal_spacing=temporal_spacing,
)
58 changes: 58 additions & 0 deletions improver/cli/generate_solar_time.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# -*- coding: utf-8 -*-
# -----------------------------------------------------------------------------
# (C) British Crown Copyright 2017-2021 Met Office.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# * Neither the name of the copyright holder nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
"""Script to run GenerateSolarTime ancillary generation."""

from improver import cli


@cli.clizefy
@cli.with_output
def process(target_grid: cli.inputcube, *, time: cli.inputdatetime):
"""Generate a cube containing local solar time, evaluated on the target grid for
specified time. Local solar time is used as an input to the RainForests calibration
for rainfall.

Args:
target_grid (iris.cube.Cube):
A cube with the desired grid.
time (str):
A datetime specified in the format YYYYMMDDTHHMMZ at which to calculate the
local solar time.

Returns:
iris.cube.Cube:
A cube containing local solar time.
"""
from improver.generate_ancillaries.generate_derived_solar_fields import (
GenerateSolarTime,
bayliffe marked this conversation as resolved.
Show resolved Hide resolved
)

return GenerateSolarTime()(target_grid, time)
170 changes: 170 additions & 0 deletions improver/generate_ancillaries/generate_derived_solar_fields.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
# -*- coding: utf-8 -*-
# -----------------------------------------------------------------------------
# (C) British Crown Copyright 2017-2021 Met Office.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# * Neither the name of the copyright holder nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
"""Module for generating derived solar fields."""
from datetime import datetime
from typing import Tuple

import numpy as np
from iris.cube import Cube

from improver import BasePlugin
from improver.metadata.utilities import (
create_new_diagnostic_cube,
generate_mandatory_attributes,
)
from improver.utilities.cube_checker import spatial_coords_match

DEFAULT_TEMPORAL_SPACING_IN_MINUTES = 30


class GenerateSolarTime(BasePlugin):
"""A plugin to evaluate local solar time."""

tjtg marked this conversation as resolved.
Show resolved Hide resolved
def process(self, target_grid: Cube, time: datetime) -> Cube:
"""Calculate the local solar time over the specified grid.

Args:
target_grid:
A cube containing the desired spatial grid.
time:
The valid time at which to evaluate the local solar time.

Returns:
A cube containing local solar time, on the same spatial grid as target_grid.
"""
pass


class GenerateClearskySolarRadiation(BasePlugin):
tjtg marked this conversation as resolved.
Show resolved Hide resolved
"""A plugin to evaluate clearsky solar radiation."""

def _initialise_input_cubes(
self, target_grid: Cube, surface_altitude: Cube, linke_turbidity: Cube
) -> Tuple[Cube, Cube]:
"""Assign default values to input cubes where none have been passed, and ensure
that all cubes are defined over consistent spatial grid.

Args:
target_grid:
A cube containing the desired spatial grid.
surface_altitude:
Input surface altitude value.
linke_turbidity:
Input linke-turbidity value.

Returns:
- Cube containing surface altitude, defined on the same grid as target_grid.
- Cube containing linke-turbidity, defined on the same grid as target_grid.

Raises:
ValueError:
If surface_altitude or linke_turbidity have inconsistent spatial coords
relative to target_grid.
"""
if surface_altitude is None:
# Create surface_altitude cube using target_grid as template.
surface_altitude_data = np.zeros(shape=target_grid.shape, dtype=np.float32)
surface_altitude = create_new_diagnostic_cube(
name="surface_altitude",
units="m",
template_cube=target_grid,
mandatory_attributes=generate_mandatory_attributes([target_grid]),
optional_attributes=target_grid.attributes,
data=surface_altitude_data,
)
else:
if not spatial_coords_match([target_grid, surface_altitude]):
raise ValueError(
"surface altitude spatial coordinates do not match target_grid"
)

if linke_turbidity is None:
# Create linke_turbidity cube using target_grid as template.
linke_turbidity_data = 3.0 * np.ones(
shape=target_grid.shape, dtype=np.float32
)
linke_turbidity = create_new_diagnostic_cube(
name="linke_turbidity",
units="1",
template_cube=target_grid,
mandatory_attributes=generate_mandatory_attributes([target_grid]),
optional_attributes=target_grid.attributes,
data=linke_turbidity_data,
)
else:
if not spatial_coords_match([target_grid, linke_turbidity]):
raise ValueError(
"linke-turbidity spatial coordinates do not match target_grid"
)

return surface_altitude, linke_turbidity

def process(
self,
target_grid: Cube,
time: datetime,
accumulation_period: int,
surface_altitude: Cube = None,
linke_turbidity: Cube = None,
temporal_spacing: int = DEFAULT_TEMPORAL_SPACING_IN_MINUTES,
) -> Cube:
"""Calculate the gridded clearsky solar radiation by integrating clearsky solar irradiance
values over the specified time-period, and on the specified grid.

Args:
target_grid:
A cube containing the desired spatial grid.
time:
The valid time at which to evaluate the accumulated clearsky solar
radiation. This time is taken to be the end of the accumulation period.
accumulation_period:
The number of hours over which the solar radiation accumulation is defined.
surface_altitude:
Surface altitude data, specified in metres, used in the evaluation of the clearsky
solar irradiance values.
linke_turbidity:
Linke turbidity data used in the evaluation of the clearsky solar irradiance
values. Linke turbidity is a dimensionless quantity that accounts for the
atmospheric scattering of radiation due to aerosols and water vapour, relative
to a dry atmosphere.
temporal_spacing:
The time stepping, specified in mins, used in the integration of solar irradiance
to produce the accumulated solar radiation.

Returns:
A cube containing the clearsky solar radiation accumulated over the specified
period, on the same spatial grid as target_grid.
"""
surface_altitude, linke_turbidity = self._initialise_input_cubes(
target_grid, surface_altitude, linke_turbidity
)

pass
12 changes: 12 additions & 0 deletions improver_tests/cli/test_init.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
inputcube,
inputcube_nolazy,
inputcubelist,
inputdatetime,
inputjson,
maybe_coerce_with,
run_main,
Expand Down Expand Up @@ -191,6 +192,17 @@ def test_basic(self, m):
self.assertEqual(result, {"mocked": 1})


class Test_inputdatetime(unittest.TestCase):
"""Tests the input datetime function"""

@patch("improver.cli.maybe_coerce_with", return_value="return")
def test_basic(self, m):
"""Tests that input cube calls load_cube with the string"""
result = inputdatetime("foo")
m.assert_called_with(improver.utilities.temporal.cycletime_to_datetime, "foo")
self.assertEqual(result, "return")


class Test_with_output(unittest.TestCase):
"""Tests the with_output wrapper"""

Expand Down
Loading