From 2d4eda00f93e40d86c876b5381abafd251f4acc3 Mon Sep 17 00:00:00 2001 From: Patrick Peglar Date: Mon, 22 Jul 2024 11:09:51 +0100 Subject: [PATCH 01/26] Enable UGRID loading always; deprecate PARSE_UGRID_ON_LOAD. (#6054) * Enable UGRID loading always; deprecate PARSE_UGRID_ON_LOAD. * Fix tests that need unstructured points data, assuming test-data v2.25. * Fix more tests for always-on ugrid loading. --------- Co-authored-by: stephenworsley <49274989+stephenworsley@users.noreply.github.com> --- .github/workflows/ci-tests.yml | 2 +- lib/iris/experimental/geovista.py | 8 +-- lib/iris/experimental/ugrid/__init__.py | 6 ++ lib/iris/experimental/ugrid/load.py | 62 ++++++++++++------- lib/iris/experimental/ugrid/mesh.py | 4 +- lib/iris/fileformats/netcdf/loader.py | 15 +---- .../geovista/test_cube_to_poly.py | 4 +- .../test_extract_unstructured_region.py | 4 +- .../test_regrid_ProjectedUnstructured.py | 2 +- .../experimental/test_ugrid_load.py | 30 ++++----- .../experimental/test_ugrid_save.py | 10 +-- lib/iris/tests/integration/test_regridding.py | 2 +- .../ugrid/load/test_ParseUgridOnLoad.py | 41 ++++-------- .../experimental/ugrid/load/test_load_mesh.py | 11 ++-- .../ugrid/load/test_load_meshes.py | 41 ++++-------- .../ugrid/load/test_meshload_checks.py | 9 +-- .../netcdf/loader/test_load_cubes.py | 16 ++--- .../fileformats/netcdf/saver/test_save.py | 4 +- .../tests/unit/tests/stock/test_netcdf.py | 4 +- 19 files changed, 111 insertions(+), 164 deletions(-) diff --git a/.github/workflows/ci-tests.yml b/.github/workflows/ci-tests.yml index 0970718b1a..2d59294cbb 100644 --- a/.github/workflows/ci-tests.yml +++ b/.github/workflows/ci-tests.yml @@ -50,7 +50,7 @@ jobs: session: "tests" env: - IRIS_TEST_DATA_VERSION: "2.22" + IRIS_TEST_DATA_VERSION: "2.25" ENV_NAME: "ci-tests" steps: diff --git a/lib/iris/experimental/geovista.py b/lib/iris/experimental/geovista.py index 206f2a8c97..3f42f09e03 100644 --- a/lib/iris/experimental/geovista.py +++ b/lib/iris/experimental/geovista.py @@ -59,12 +59,10 @@ def cube_to_polydata(cube, **kwargs): .. testsetup:: from iris import load_cube, sample_data_path - from iris.experimental.ugrid import PARSE_UGRID_ON_LOAD cube = load_cube(sample_data_path("air_temp.pp")) cube_w_time = load_cube(sample_data_path("A1B_north_america.nc")) - with PARSE_UGRID_ON_LOAD.context(): - cube_mesh = load_cube(sample_data_path("mesh_C4_synthetic_float.nc")) + cube_mesh = load_cube(sample_data_path("mesh_C4_synthetic_float.nc")) >>> from iris.experimental.geovista import cube_to_polydata @@ -212,11 +210,9 @@ def extract_unstructured_region(cube, polydata, region, **kwargs): from iris import load_cube, sample_data_path from iris.coords import AuxCoord from iris.cube import CubeList - from iris.experimental.ugrid import PARSE_UGRID_ON_LOAD file_path = sample_data_path("mesh_C4_synthetic_float.nc") - with PARSE_UGRID_ON_LOAD.context(): - cube_w_mesh = load_cube(file_path) + cube_w_mesh = load_cube(file_path) level_cubes = CubeList() for height_level in range(72): diff --git a/lib/iris/experimental/ugrid/__init__.py b/lib/iris/experimental/ugrid/__init__.py index 05f631d1cc..56c1a73411 100644 --- a/lib/iris/experimental/ugrid/__init__.py +++ b/lib/iris/experimental/ugrid/__init__.py @@ -5,6 +5,12 @@ """Infra-structure for unstructured mesh support. +.. deprecated:: 1.10 + + :data:`PARSE_UGRID_ON_LOAD` is due to be removed at next major release. + Please remove all uses of this, which are no longer needed : + UGRID loading is now **always** active for files containing a UGRID mesh. + Based on CF UGRID Conventions (v1.0), https://ugrid-conventions.github.io/ugrid-conventions/. .. note:: diff --git a/lib/iris/experimental/ugrid/load.py b/lib/iris/experimental/ugrid/load.py index d4e6d8afc3..e6b3436185 100644 --- a/lib/iris/experimental/ugrid/load.py +++ b/lib/iris/experimental/ugrid/load.py @@ -10,6 +10,11 @@ Eventual destination: :mod:`iris.fileformats.netcdf`. +.. seealso:: + + The UGRID Conventions, + https://ugrid-conventions.github.io/ugrid-conventions/ + """ from contextlib import contextmanager @@ -18,6 +23,7 @@ import threading import warnings +from ..._deprecation import warn_deprecated from ...config import get_logger from ...coords import AuxCoord from ...fileformats._nc_load_rules.helpers import get_attr_units, get_names @@ -60,20 +66,20 @@ def __init__(self): :const:`~iris.experimental.ugrid.load.PARSE_UGRID_ON_LOAD`. Use :meth:`context` to temporarily activate. - .. seealso:: - - The UGRID Conventions, - https://ugrid-conventions.github.io/ugrid-conventions/ + Notes + ----- + .. deprecated:: 1.10 + Do not use -- due to be removed at next major release : + UGRID loading is now **always** active for files containing a UGRID mesh. """ - self._state = False def __bool__(self): - return self._state + return True @contextmanager def context(self): - """Temporarily activate experimental UGRID-aware NetCDF loading. + """Activate UGRID-aware NetCDF loading. Use the standard Iris loading API while within the context manager. If the loaded file(s) include any UGRID content, this will be parsed and @@ -89,12 +95,35 @@ def context(self): constraint=my_constraint, callback=my_callback) + Notes + ----- + .. deprecated:: 1.10 + Do not use -- due to be removed at next major release : + UGRID loading is now **always** active for files containing a UGRID mesh. + + Examples + -------- + Replace usage, for example: + + .. code-block:: python + + with iris.experimental.ugrid.PARSE_UGRID_ON_LOAD.context(): + mesh_cubes = iris.load(path) + + with: + + .. code-block:: python + + mesh_cubes = iris.load(path) + """ - try: - self._state = True - yield - finally: - self._state = False + wmsg = ( + "iris.experimental.ugrid.load.PARSE_UGRID_ON_LOAD has been deprecated " + "and will be removed. Please remove all uses : these are no longer needed, " + "as UGRID loading is now applied to any file containing a mesh." + ) + warn_deprecated(wmsg) + yield #: Run-time switch for experimental UGRID-aware NetCDF loading. See :class:`~iris.experimental.ugrid.load.ParseUGridOnLoad`. @@ -174,15 +203,6 @@ def load_meshes(uris, var_name=None): from ...fileformats import FORMAT_AGENT - if not PARSE_UGRID_ON_LOAD: - # Explicit behaviour, consistent with netcdf.load_cubes(), rather than - # an invisible assumption. - message = ( - f"PARSE_UGRID_ON_LOAD is {bool(PARSE_UGRID_ON_LOAD)}. Must be " - f"True to enable mesh loading." - ) - raise ValueError(message) - if isinstance(uris, str): uris = [uris] diff --git a/lib/iris/experimental/ugrid/mesh.py b/lib/iris/experimental/ugrid/mesh.py index c90b67e1d6..398c240337 100644 --- a/lib/iris/experimental/ugrid/mesh.py +++ b/lib/iris/experimental/ugrid/mesh.py @@ -786,14 +786,12 @@ def from_coords(cls, *coords): from iris import load_cube, sample_data_path from iris.experimental.ugrid import ( - PARSE_UGRID_ON_LOAD, MeshXY, MeshCoord, ) file_path = sample_data_path("mesh_C4_synthetic_float.nc") - with PARSE_UGRID_ON_LOAD.context(): - cube_w_mesh = load_cube(file_path) + cube_w_mesh = load_cube(file_path) Examples -------- diff --git a/lib/iris/fileformats/netcdf/loader.py b/lib/iris/fileformats/netcdf/loader.py index 9378d7ae1f..5276657195 100644 --- a/lib/iris/fileformats/netcdf/loader.py +++ b/lib/iris/fileformats/netcdf/loader.py @@ -583,7 +583,6 @@ def load_cubes(file_sources, callback=None, constraints=None): # Deferred import to avoid circular imports. from iris.experimental.ugrid.cf import CFUGridReader from iris.experimental.ugrid.load import ( - PARSE_UGRID_ON_LOAD, _build_mesh_coords, _meshes_from_cf, ) @@ -600,15 +599,8 @@ def load_cubes(file_sources, callback=None, constraints=None): for file_source in file_sources: # Ingest the file. At present may be a filepath or an open netCDF4.Dataset. - meshes = {} - if PARSE_UGRID_ON_LOAD: - cf_reader_class = CFUGridReader - else: - cf_reader_class = iris.fileformats.cf.CFReader - - with cf_reader_class(file_source) as cf: - if PARSE_UGRID_ON_LOAD: - meshes = _meshes_from_cf(cf) + with CFUGridReader(file_source) as cf: + meshes = _meshes_from_cf(cf) # Process each CF data variable. data_variables = list(cf.cf_group.data_variables.values()) + list( @@ -626,8 +618,7 @@ def load_cubes(file_sources, callback=None, constraints=None): mesh_name = None mesh = None mesh_coords, mesh_dim = [], None - if PARSE_UGRID_ON_LOAD: - mesh_name = getattr(cf_var, "mesh", None) + mesh_name = getattr(cf_var, "mesh", None) if mesh_name is not None: try: mesh = meshes[mesh_name] diff --git a/lib/iris/tests/integration/experimental/geovista/test_cube_to_poly.py b/lib/iris/tests/integration/experimental/geovista/test_cube_to_poly.py index 582c216a44..26ea8c48ea 100644 --- a/lib/iris/tests/integration/experimental/geovista/test_cube_to_poly.py +++ b/lib/iris/tests/integration/experimental/geovista/test_cube_to_poly.py @@ -8,7 +8,6 @@ from iris import load_cube from iris.experimental.geovista import cube_to_polydata -from iris.experimental.ugrid import PARSE_UGRID_ON_LOAD from iris.tests import get_data_path @@ -57,8 +56,7 @@ def test_integration_mesh(): ] ) - with PARSE_UGRID_ON_LOAD.context(): - cube = load_cube(file_path, "conv_rain") + cube = load_cube(file_path, "conv_rain") polydata = cube_to_polydata(cube[0, :]) # This is a known good output, we have plotted the result and checked it. diff --git a/lib/iris/tests/integration/experimental/geovista/test_extract_unstructured_region.py b/lib/iris/tests/integration/experimental/geovista/test_extract_unstructured_region.py index 47024dc1cd..7343809cdc 100644 --- a/lib/iris/tests/integration/experimental/geovista/test_extract_unstructured_region.py +++ b/lib/iris/tests/integration/experimental/geovista/test_extract_unstructured_region.py @@ -8,7 +8,6 @@ from iris import load_cube from iris.experimental.geovista import cube_to_polydata, extract_unstructured_region -from iris.experimental.ugrid import PARSE_UGRID_ON_LOAD from iris.tests import get_data_path @@ -21,8 +20,7 @@ def test_face_region_extraction(): ] ) - with PARSE_UGRID_ON_LOAD.context(): - global_cube = load_cube(file_path, "conv_rain") + global_cube = load_cube(file_path, "conv_rain") polydata = cube_to_polydata(global_cube[0, :]) region = BBox(lons=[0, 70, 70, 0], lats=[-25, -25, 45, 45]) diff --git a/lib/iris/tests/integration/experimental/test_regrid_ProjectedUnstructured.py b/lib/iris/tests/integration/experimental/test_regrid_ProjectedUnstructured.py index 2a70ac2d32..dd62a7f376 100644 --- a/lib/iris/tests/integration/experimental/test_regrid_ProjectedUnstructured.py +++ b/lib/iris/tests/integration/experimental/test_regrid_ProjectedUnstructured.py @@ -28,7 +28,7 @@ class TestProjectedUnstructured(tests.IrisTest): def setUp(self): path = tests.get_data_path( - ("NetCDF", "unstructured_grid", "theta_nodal_xios.nc") + ("NetCDF", "unstructured_grid", "theta_nodal_not_ugrid.nc") ) self.src = iris.load_cube(path, "Potential Temperature") diff --git a/lib/iris/tests/integration/experimental/test_ugrid_load.py b/lib/iris/tests/integration/experimental/test_ugrid_load.py index 613e5f4e37..4325532fc6 100644 --- a/lib/iris/tests/integration/experimental/test_ugrid_load.py +++ b/lib/iris/tests/integration/experimental/test_ugrid_load.py @@ -18,7 +18,7 @@ import pytest from iris import Constraint, load -from iris.experimental.ugrid.load import PARSE_UGRID_ON_LOAD, load_mesh, load_meshes +from iris.experimental.ugrid.load import load_mesh, load_meshes from iris.experimental.ugrid.mesh import MeshXY from iris.tests.stock.netcdf import ( _file_from_cdl_template as create_file_from_cdl_template, @@ -47,8 +47,7 @@ def ugrid_load(uris, constraints=None, callback=None): constraints = [constraints] constraints.append(filter_orphan_connectivities) - with PARSE_UGRID_ON_LOAD.context(): - return load(uris, constraints, callback) + return load(uris, constraints, callback) @tests.skip_data @@ -110,12 +109,11 @@ def test_3D_veg_pseudo_levels(self): ) def test_no_mesh(self): - with PARSE_UGRID_ON_LOAD.context(): - cube_list = load( - tests.get_data_path( - ["NetCDF", "unstructured_grid", "theta_nodal_not_ugrid.nc"] - ) + cube_list = load( + tests.get_data_path( + ["NetCDF", "unstructured_grid", "theta_nodal_not_ugrid.nc"] ) + ) self.assertTrue(all([cube.mesh is None for cube in cube_list])) @@ -207,10 +205,9 @@ def test_mesh_no_cf_role(self): @tests.skip_data class Test_load_mesh(tests.IrisTest): def common_test(self, file_name, mesh_var_name): - with PARSE_UGRID_ON_LOAD.context(): - mesh = load_mesh( - tests.get_data_path(["NetCDF", "unstructured_grid", file_name]) - ) + mesh = load_mesh( + tests.get_data_path(["NetCDF", "unstructured_grid", file_name]) + ) # NOTE: cannot use CML tests as this isn't supported for non-Cubes. self.assertIsInstance(mesh, MeshXY) self.assertEqual(mesh.var_name, mesh_var_name) @@ -225,12 +222,11 @@ def test_mesh_file(self): self.common_test("mesh_C12.nc", "dynamics") def test_no_mesh(self): - with PARSE_UGRID_ON_LOAD.context(): - meshes = load_meshes( - tests.get_data_path( - ["NetCDF", "unstructured_grid", "theta_nodal_not_ugrid.nc"] - ) + meshes = load_meshes( + tests.get_data_path( + ["NetCDF", "unstructured_grid", "theta_nodal_not_ugrid.nc"] ) + ) self.assertDictEqual({}, meshes) diff --git a/lib/iris/tests/integration/experimental/test_ugrid_save.py b/lib/iris/tests/integration/experimental/test_ugrid_save.py index 85f6024b93..df3c9cc553 100644 --- a/lib/iris/tests/integration/experimental/test_ugrid_save.py +++ b/lib/iris/tests/integration/experimental/test_ugrid_save.py @@ -14,7 +14,6 @@ import tempfile import iris -from iris.experimental.ugrid.load import PARSE_UGRID_ON_LOAD import iris.fileformats.netcdf from iris.tests.stock.netcdf import _add_standard_data, ncgen_from_cdl @@ -48,8 +47,7 @@ def test_example_result_cdls(self): # Fill in blank data-variables. _add_standard_data(target_ncfile_path) # Load as Iris data - with PARSE_UGRID_ON_LOAD.context(): - cubes = iris.load(target_ncfile_path) + cubes = iris.load(target_ncfile_path) # Re-save, to check the save behaviour. resave_ncfile_path = str(self.temp_dir / f"{ex_name}_resaved.nc") iris.save(cubes, resave_ncfile_path) @@ -69,8 +67,7 @@ def test_example_roundtrips(self): # Fill in blank data-variables. _add_standard_data(target_ncfile_path) # Load the original as Iris data - with PARSE_UGRID_ON_LOAD.context(): - orig_cubes = iris.load(target_ncfile_path) + orig_cubes = iris.load(target_ncfile_path) if "ex4" in ex_name: # Discard the extra formula terms component cubes @@ -80,8 +77,7 @@ def test_example_roundtrips(self): # Save-and-load-back to compare the Iris saved result. resave_ncfile_path = str(self.temp_dir / f"{ex_name}_resaved.nc") iris.save(orig_cubes, resave_ncfile_path) - with PARSE_UGRID_ON_LOAD.context(): - savedloaded_cubes = iris.load(resave_ncfile_path) + savedloaded_cubes = iris.load(resave_ncfile_path) # This should match the original exactly # ..EXCEPT for our inability to compare meshes. diff --git a/lib/iris/tests/integration/test_regridding.py b/lib/iris/tests/integration/test_regridding.py index c8197a9d94..7d65577545 100644 --- a/lib/iris/tests/integration/test_regridding.py +++ b/lib/iris/tests/integration/test_regridding.py @@ -98,7 +98,7 @@ def test_nearest(self): class TestUnstructured(tests.IrisTest): def setUp(self): path = tests.get_data_path( - ("NetCDF", "unstructured_grid", "theta_nodal_xios.nc") + ("NetCDF", "unstructured_grid", "theta_nodal_not_ugrid.nc") ) self.src = iris.load_cube(path, "Potential Temperature") self.grid = simple_3d()[0, :, :] diff --git a/lib/iris/tests/unit/experimental/ugrid/load/test_ParseUgridOnLoad.py b/lib/iris/tests/unit/experimental/ugrid/load/test_ParseUgridOnLoad.py index 8f85699037..0c78fa5880 100644 --- a/lib/iris/tests/unit/experimental/ugrid/load/test_ParseUgridOnLoad.py +++ b/lib/iris/tests/unit/experimental/ugrid/load/test_ParseUgridOnLoad.py @@ -4,42 +4,27 @@ # See LICENSE in the root of the repository for full licensing details. """Unit tests for the :class:`iris.experimental.ugrid.load.ParseUgridOnLoad` class. -todo: remove this module when experimental.ugrid is folded into standard behaviour. +TODO: remove this module when ParseUGridOnLoad itself is removed. """ -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip +import pytest +from iris._deprecation import IrisDeprecation from iris.experimental.ugrid.load import PARSE_UGRID_ON_LOAD, ParseUGridOnLoad -class TestClass(tests.IrisTest): - @classmethod - def setUpClass(cls): - cls.cls = ParseUGridOnLoad() +def test_creation(): + # I.E. "does not fail". + _ = ParseUGridOnLoad() - def test_default(self): - self.assertFalse(self.cls) - def test_context(self): - self.assertFalse(self.cls) - with self.cls.context(): - self.assertTrue(self.cls) - self.assertFalse(self.cls) +def test_context(): + ugridswitch = ParseUGridOnLoad() + with pytest.warns(IrisDeprecation, match="PARSE_UGRID_ON_LOAD has been deprecated"): + with ugridswitch.context(): + pass -class TestConstant(tests.IrisTest): - @classmethod - def setUpClass(cls): - cls.constant = PARSE_UGRID_ON_LOAD - - def test_default(self): - self.assertFalse(self.constant) - - def test_context(self): - self.assertFalse(self.constant) - with self.constant.context(): - self.assertTrue(self.constant) - self.assertFalse(self.constant) +def test_constant(): + assert isinstance(PARSE_UGRID_ON_LOAD, ParseUGridOnLoad) diff --git a/lib/iris/tests/unit/experimental/ugrid/load/test_load_mesh.py b/lib/iris/tests/unit/experimental/ugrid/load/test_load_mesh.py index 54ca5b8fcf..6d1bbe995c 100644 --- a/lib/iris/tests/unit/experimental/ugrid/load/test_load_mesh.py +++ b/lib/iris/tests/unit/experimental/ugrid/load/test_load_mesh.py @@ -8,7 +8,7 @@ # importing anything else. import iris.tests as tests # isort:skip -from iris.experimental.ugrid.load import PARSE_UGRID_ON_LOAD, load_mesh +from iris.experimental.ugrid.load import load_mesh class Tests(tests.IrisTest): @@ -22,14 +22,12 @@ def setUp(self): def test_calls_load_meshes(self): args = [("file_1", "file_2"), "my_var_name"] - with PARSE_UGRID_ON_LOAD.context(): - _ = load_mesh(args) + _ = load_mesh(args) assert self.load_meshes_mock.call_count == 1 assert self.load_meshes_mock.call_args == ((args, None),) def test_returns_mesh(self): - with PARSE_UGRID_ON_LOAD.context(): - mesh = load_mesh([]) + mesh = load_mesh([]) self.assertEqual(mesh, "mesh") def test_single_mesh(self): @@ -37,8 +35,7 @@ def test_single_mesh(self): def common(ret_val): self.load_meshes_mock.return_value = ret_val with self.assertRaisesRegex(ValueError, "Expecting 1 mesh.*"): - with PARSE_UGRID_ON_LOAD.context(): - _ = load_mesh([]) + _ = load_mesh([]) # Too many. common({"file": ["mesh1", "mesh2"]}) diff --git a/lib/iris/tests/unit/experimental/ugrid/load/test_load_meshes.py b/lib/iris/tests/unit/experimental/ugrid/load/test_load_meshes.py index 0ae09696bb..da7cf9b649 100644 --- a/lib/iris/tests/unit/experimental/ugrid/load/test_load_meshes.py +++ b/lib/iris/tests/unit/experimental/ugrid/load/test_load_meshes.py @@ -13,7 +13,7 @@ import tempfile from uuid import uuid4 -from iris.experimental.ugrid.load import PARSE_UGRID_ON_LOAD, load_meshes, logger +from iris.experimental.ugrid.load import load_meshes, logger from iris.tests.stock.netcdf import ncgen_from_cdl @@ -98,8 +98,7 @@ def add_second_mesh(self): def test_with_data(self): nc_path = cdl_to_nc(self.ref_cdl) - with PARSE_UGRID_ON_LOAD.context(): - meshes = load_meshes(nc_path) + meshes = load_meshes(nc_path) files = list(meshes.keys()) self.assertEqual(1, len(files)) @@ -114,8 +113,7 @@ def test_no_data(self): ref_cdl = "\n".join(cdl_lines) nc_path = cdl_to_nc(ref_cdl) - with PARSE_UGRID_ON_LOAD.context(): - meshes = load_meshes(nc_path) + meshes = load_meshes(nc_path) files = list(meshes.keys()) self.assertEqual(1, len(files)) @@ -135,23 +133,20 @@ def test_no_mesh(self): ref_cdl = "\n".join(cdl_lines) nc_path = cdl_to_nc(ref_cdl) - with PARSE_UGRID_ON_LOAD.context(): - meshes = load_meshes(nc_path) + meshes = load_meshes(nc_path) self.assertDictEqual({}, meshes) def test_multi_files(self): files_count = 3 nc_paths = [cdl_to_nc(self.ref_cdl) for _ in range(files_count)] - with PARSE_UGRID_ON_LOAD.context(): - meshes = load_meshes(nc_paths) + meshes = load_meshes(nc_paths) self.assertEqual(files_count, len(meshes)) def test_multi_meshes(self): ref_cdl, second_name = self.add_second_mesh() nc_path = cdl_to_nc(ref_cdl) - with PARSE_UGRID_ON_LOAD.context(): - meshes = load_meshes(nc_path) + meshes = load_meshes(nc_path) files = list(meshes.keys()) self.assertEqual(1, len(files)) @@ -165,8 +160,7 @@ def test_var_name(self): second_cdl, second_name = self.add_second_mesh() cdls = [self.ref_cdl, second_cdl] nc_paths = [cdl_to_nc(cdl) for cdl in cdls] - with PARSE_UGRID_ON_LOAD.context(): - meshes = load_meshes(nc_paths, second_name) + meshes = load_meshes(nc_paths, second_name) files = list(meshes.keys()) self.assertEqual(1, len(files)) @@ -174,26 +168,15 @@ def test_var_name(self): self.assertEqual(1, len(file_meshes)) self.assertEqual(second_name, file_meshes[0].var_name) - def test_no_parsing(self): - nc_path = cdl_to_nc(self.ref_cdl) - with self.assertRaisesRegex( - ValueError, ".*Must be True to enable mesh loading." - ): - _ = load_meshes(nc_path) - def test_invalid_scheme(self): with self.assertRaisesRegex(ValueError, "Iris cannot handle the URI scheme:.*"): - with PARSE_UGRID_ON_LOAD.context(): - _ = load_meshes("foo://bar") + _ = load_meshes("foo://bar") @tests.skip_data def test_non_nc(self): log_regex = r"Ignoring non-NetCDF file:.*" with self.assertLogs(logger, level="INFO", msg_regex=log_regex): - with PARSE_UGRID_ON_LOAD.context(): - meshes = load_meshes( - tests.get_data_path(["PP", "simple_pp", "global.pp"]) - ) + meshes = load_meshes(tests.get_data_path(["PP", "simple_pp", "global.pp"])) self.assertDictEqual({}, meshes) @@ -205,8 +188,7 @@ def setUp(self): def test_http(self): url = "https://foo" - with PARSE_UGRID_ON_LOAD.context(): - _ = load_meshes(url) + _ = load_meshes(url) self.format_agent_mock.assert_called_with(url, None) def test_mixed_sources(self): @@ -215,8 +197,7 @@ def test_mixed_sources(self): file.touch() glob = f"{TMP_DIR}/*.nc" - with PARSE_UGRID_ON_LOAD.context(): - _ = load_meshes([url, glob]) + _ = load_meshes([url, glob]) file_uris = [call[0][0] for call in self.format_agent_mock.call_args_list] for source in (url, Path(file).name): self.assertIn(source, file_uris) diff --git a/lib/iris/tests/unit/experimental/ugrid/load/test_meshload_checks.py b/lib/iris/tests/unit/experimental/ugrid/load/test_meshload_checks.py index 493ad047a4..ed98c8c3d4 100644 --- a/lib/iris/tests/unit/experimental/ugrid/load/test_meshload_checks.py +++ b/lib/iris/tests/unit/experimental/ugrid/load/test_meshload_checks.py @@ -7,7 +7,6 @@ import pytest import iris -from iris.experimental.ugrid.load import PARSE_UGRID_ON_LOAD from .test_load_meshes import ( _TEST_CDL_HEAD, @@ -55,8 +54,7 @@ def test_extrameshvar__ok(self, tmp_path_factory): # Check that the default cdl construction loads OK self.tmpdir = tmp_path_factory.mktemp("meshload") testnc = self._create_testnc() - with PARSE_UGRID_ON_LOAD.context(): - iris.load(testnc) + iris.load(testnc) def test_extrameshvar__fail(self, failnc): # Check that the expected errors are raised in various cases. @@ -78,6 +76,5 @@ def test_extrameshvar__fail(self, failnc): else: raise ValueError(f"unexpected param: {param}") - with PARSE_UGRID_ON_LOAD.context(): - with pytest.raises(ValueError, match=match_msg): - iris.load(failnc) + with pytest.raises(ValueError, match=match_msg): + iris.load(failnc) diff --git a/lib/iris/tests/unit/fileformats/netcdf/loader/test_load_cubes.py b/lib/iris/tests/unit/fileformats/netcdf/loader/test_load_cubes.py index dfb1379d5a..571a749bf0 100644 --- a/lib/iris/tests/unit/fileformats/netcdf/loader/test_load_cubes.py +++ b/lib/iris/tests/unit/fileformats/netcdf/loader/test_load_cubes.py @@ -21,7 +21,6 @@ import numpy as np from iris.coords import AncillaryVariable, CellMeasure -from iris.experimental.ugrid.load import PARSE_UGRID_ON_LOAD from iris.experimental.ugrid.mesh import MeshCoord from iris.fileformats.netcdf import logger from iris.fileformats.netcdf.loader import load_cubes @@ -262,13 +261,7 @@ def setUpClass(cls): } """ cls.nc_path = cdl_to_nc(cls.ref_cdl) - with PARSE_UGRID_ON_LOAD.context(): - cls.mesh_cubes = list(load_cubes(cls.nc_path)) - - def test_mesh_handled(self): - cubes_no_ugrid = list(load_cubes(self.nc_path)) - self.assertEqual(4, len(cubes_no_ugrid)) - self.assertEqual(2, len(self.mesh_cubes)) + cls.mesh_cubes = list(load_cubes(cls.nc_path)) def test_standard_dims(self): for cube in self.mesh_cubes: @@ -303,7 +296,6 @@ def test_missing_mesh(self): # No error when mesh handling not activated. _ = list(load_cubes(nc_path)) - with PARSE_UGRID_ON_LOAD.context(): - log_regex = r"File does not contain mesh.*" - with self.assertLogs(logger, level="DEBUG", msg_regex=log_regex): - _ = list(load_cubes(nc_path)) + log_regex = r"File does not contain mesh.*" + with self.assertLogs(logger, level="DEBUG", msg_regex=log_regex): + _ = list(load_cubes(nc_path)) diff --git a/lib/iris/tests/unit/fileformats/netcdf/saver/test_save.py b/lib/iris/tests/unit/fileformats/netcdf/saver/test_save.py index ae85dc1aab..860da84e6b 100644 --- a/lib/iris/tests/unit/fileformats/netcdf/saver/test_save.py +++ b/lib/iris/tests/unit/fileformats/netcdf/saver/test_save.py @@ -19,7 +19,6 @@ import iris from iris.coords import AuxCoord, DimCoord from iris.cube import Cube, CubeList -from iris.experimental.ugrid import PARSE_UGRID_ON_LOAD from iris.fileformats.netcdf import CF_CONVENTIONS_VERSION, Saver, _thread_safe_nc, save from iris.tests.stock import lat_lon_cube from iris.tests.stock.mesh import sample_mesh_cube @@ -246,8 +245,7 @@ def _check_save_and_reload(self, cubes): save(cubes, filepath) # Load them back for roundtrip testing. - with PARSE_UGRID_ON_LOAD.context(): - new_cubes = iris.load(str(filepath)) + new_cubes = iris.load(str(filepath)) # There should definitely still be the same number of cubes. self.assertEqual(len(new_cubes), len(cubes)) diff --git a/lib/iris/tests/unit/tests/stock/test_netcdf.py b/lib/iris/tests/unit/tests/stock/test_netcdf.py index e88d3f6abb..c218385425 100644 --- a/lib/iris/tests/unit/tests/stock/test_netcdf.py +++ b/lib/iris/tests/unit/tests/stock/test_netcdf.py @@ -8,7 +8,6 @@ import tempfile from iris import load_cube -from iris.experimental.ugrid.load import PARSE_UGRID_ON_LOAD from iris.experimental.ugrid.mesh import MeshCoord, MeshXY # Import iris.tests first so that some things can be initialised before @@ -38,8 +37,7 @@ def create_synthetic_file(self, **create_kwargs): def create_synthetic_test_cube(self, **create_kwargs): file_path = self.create_synthetic_file(**create_kwargs) - with PARSE_UGRID_ON_LOAD.context(): - cube = load_cube(file_path) + cube = load_cube(file_path) return cube def check_cube(self, cube, shape, location, level): From 304c5e01a0404ca2f7d32758bc5fc0e92346427b Mon Sep 17 00:00:00 2001 From: "scitools-ci[bot]" <107775138+scitools-ci[bot]@users.noreply.github.com> Date: Mon, 22 Jul 2024 11:50:40 +0000 Subject: [PATCH 02/26] Updated environment lockfiles (#6068) Co-authored-by: Lockfile bot Co-authored-by: Patrick Peglar --- requirements/locks/py310-linux-64.lock | 2 +- requirements/locks/py311-linux-64.lock | 2 +- requirements/locks/py312-linux-64.lock | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/requirements/locks/py310-linux-64.lock b/requirements/locks/py310-linux-64.lock index 44fb57644b..37fd003043 100644 --- a/requirements/locks/py310-linux-64.lock +++ b/requirements/locks/py310-linux-64.lock @@ -192,7 +192,7 @@ https://conda.anaconda.org/conda-forge/linux-64/python-xxhash-3.4.1-py310h2372a7 https://conda.anaconda.org/conda-forge/noarch/pytz-2024.1-pyhd8ed1ab_0.conda#3eeeeb9e4827ace8c0c1419c85d590ad https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0.1-py310h2372a71_1.conda#bb010e368de4940771368bc3dc4c63e7 https://conda.anaconda.org/conda-forge/noarch/scooby-0.10.0-pyhd8ed1ab_0.conda#9e57330f431abbb4c88a5f898a4ba223 -https://conda.anaconda.org/conda-forge/noarch/setuptools-71.0.1-pyhd8ed1ab_0.conda#aede3d5c0882ebed2f07024400a111ed +https://conda.anaconda.org/conda-forge/noarch/setuptools-71.0.3-pyhd8ed1ab_0.conda#d4b6e6ce2f7bcaec484b81c447df1028 https://conda.anaconda.org/conda-forge/noarch/six-1.16.0-pyh6c4a22f_0.tar.bz2#e5f25f8dbc060e9a8d912e432202afc2 https://conda.anaconda.org/conda-forge/noarch/snowballstemmer-2.2.0-pyhd8ed1ab_0.tar.bz2#4d22a9315e78c6827f806065957d566e https://conda.anaconda.org/conda-forge/noarch/sortedcontainers-2.4.0-pyhd8ed1ab_0.tar.bz2#6d6552722448103793743dabfbda532d diff --git a/requirements/locks/py311-linux-64.lock b/requirements/locks/py311-linux-64.lock index eb273cae49..9ef2aa51f7 100644 --- a/requirements/locks/py311-linux-64.lock +++ b/requirements/locks/py311-linux-64.lock @@ -192,7 +192,7 @@ https://conda.anaconda.org/conda-forge/linux-64/python-xxhash-3.4.1-py311h459d7e https://conda.anaconda.org/conda-forge/noarch/pytz-2024.1-pyhd8ed1ab_0.conda#3eeeeb9e4827ace8c0c1419c85d590ad https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0.1-py311h459d7ec_1.conda#52719a74ad130de8fb5d047dc91f247a https://conda.anaconda.org/conda-forge/noarch/scooby-0.10.0-pyhd8ed1ab_0.conda#9e57330f431abbb4c88a5f898a4ba223 -https://conda.anaconda.org/conda-forge/noarch/setuptools-71.0.1-pyhd8ed1ab_0.conda#aede3d5c0882ebed2f07024400a111ed +https://conda.anaconda.org/conda-forge/noarch/setuptools-71.0.3-pyhd8ed1ab_0.conda#d4b6e6ce2f7bcaec484b81c447df1028 https://conda.anaconda.org/conda-forge/noarch/six-1.16.0-pyh6c4a22f_0.tar.bz2#e5f25f8dbc060e9a8d912e432202afc2 https://conda.anaconda.org/conda-forge/noarch/snowballstemmer-2.2.0-pyhd8ed1ab_0.tar.bz2#4d22a9315e78c6827f806065957d566e https://conda.anaconda.org/conda-forge/noarch/sortedcontainers-2.4.0-pyhd8ed1ab_0.tar.bz2#6d6552722448103793743dabfbda532d diff --git a/requirements/locks/py312-linux-64.lock b/requirements/locks/py312-linux-64.lock index 7fa3ff1a58..ccf1c78c28 100644 --- a/requirements/locks/py312-linux-64.lock +++ b/requirements/locks/py312-linux-64.lock @@ -192,7 +192,7 @@ https://conda.anaconda.org/conda-forge/linux-64/python-xxhash-3.4.1-py312h98912e https://conda.anaconda.org/conda-forge/noarch/pytz-2024.1-pyhd8ed1ab_0.conda#3eeeeb9e4827ace8c0c1419c85d590ad https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0.1-py312h98912ed_1.conda#e3fd78d8d490af1d84763b9fe3f2e552 https://conda.anaconda.org/conda-forge/noarch/scooby-0.10.0-pyhd8ed1ab_0.conda#9e57330f431abbb4c88a5f898a4ba223 -https://conda.anaconda.org/conda-forge/noarch/setuptools-71.0.1-pyhd8ed1ab_0.conda#aede3d5c0882ebed2f07024400a111ed +https://conda.anaconda.org/conda-forge/noarch/setuptools-71.0.3-pyhd8ed1ab_0.conda#d4b6e6ce2f7bcaec484b81c447df1028 https://conda.anaconda.org/conda-forge/noarch/six-1.16.0-pyh6c4a22f_0.tar.bz2#e5f25f8dbc060e9a8d912e432202afc2 https://conda.anaconda.org/conda-forge/noarch/snowballstemmer-2.2.0-pyhd8ed1ab_0.tar.bz2#4d22a9315e78c6827f806065957d566e https://conda.anaconda.org/conda-forge/noarch/sortedcontainers-2.4.0-pyhd8ed1ab_0.tar.bz2#6d6552722448103793743dabfbda532d From c333b7f7a15ef4598530015e8586425f7c98e791 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 23 Jul 2024 18:20:24 +0100 Subject: [PATCH 03/26] Bump scitools/workflows from 2024.07.4 to 2024.07.5 (#6076) Bumps [scitools/workflows](https://github.com/scitools/workflows) from 2024.07.4 to 2024.07.5. - [Release notes](https://github.com/scitools/workflows/releases) - [Commits](https://github.com/scitools/workflows/compare/2024.07.4...2024.07.5) --- updated-dependencies: - dependency-name: scitools/workflows dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci-manifest.yml | 2 +- .github/workflows/refresh-lockfiles.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci-manifest.yml b/.github/workflows/ci-manifest.yml index 50c1cca34b..12691f8536 100644 --- a/.github/workflows/ci-manifest.yml +++ b/.github/workflows/ci-manifest.yml @@ -23,4 +23,4 @@ concurrency: jobs: manifest: name: "check-manifest" - uses: scitools/workflows/.github/workflows/ci-manifest.yml@2024.07.4 + uses: scitools/workflows/.github/workflows/ci-manifest.yml@2024.07.5 diff --git a/.github/workflows/refresh-lockfiles.yml b/.github/workflows/refresh-lockfiles.yml index 8a24cdbe7c..b0cbe14054 100644 --- a/.github/workflows/refresh-lockfiles.yml +++ b/.github/workflows/refresh-lockfiles.yml @@ -14,5 +14,5 @@ on: jobs: refresh_lockfiles: - uses: scitools/workflows/.github/workflows/refresh-lockfiles.yml@2024.07.4 + uses: scitools/workflows/.github/workflows/refresh-lockfiles.yml@2024.07.5 secrets: inherit From 7c87fb2a440d4ef0b63778aa45b7a8e6f35438e8 Mon Sep 17 00:00:00 2001 From: Patrick Peglar Date: Thu, 25 Jul 2024 12:28:18 +0100 Subject: [PATCH 04/26] Mesh nonexperimental (#6061) * Move all iris.experimental.ugrid to iris.ugrid. Replace experiment.ugrid, including docstrings and imports. Fix test_ParseUgridOnLoad Fix ugrid.load. Remove PARSE_UGRID from t/i/ugrid/test_ugrid_save Remove PARSE_UGRID from t/u/ff/nc/saver/test_save Remove PARSE_UGRID from t/i/exp/geovista/(both) Remove PARSE_UGRID from t/u/tests/stock/test_netcdf * Adjust references in docs. * Fix type. * Workaround docs build problem. Include alternative index workaround. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Fix Mesh -> MeshXY in experimental.ugrid * Import annotations in new files. --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- docs/src/further_topics/ugrid/data_model.rst | 78 ++++----- docs/src/further_topics/ugrid/index.rst | 2 +- docs/src/further_topics/ugrid/operations.rst | 70 ++++---- .../src/further_topics/ugrid/other_meshes.rst | 10 +- .../further_topics/ugrid/partner_packages.rst | 2 +- lib/iris/analysis/_regrid.py | 2 +- lib/iris/common/metadata.py | 3 +- lib/iris/common/resolve.py | 4 +- lib/iris/coords.py | 3 +- lib/iris/cube.py | 24 +-- lib/iris/experimental/geovista.py | 4 +- lib/iris/experimental/ugrid.py | 77 +++++++++ lib/iris/fileformats/cf.py | 6 +- lib/iris/fileformats/netcdf/loader.py | 13 +- lib/iris/fileformats/netcdf/saver.py | 18 +- .../experimental/test_meshcoord_coordsys.py | 2 +- .../test_ugrid_load.py | 11 +- .../test_ugrid_save.py | 0 .../ugrid_conventions_examples/README.txt | 0 .../ugrid_ex1_1d_mesh.cdl | 0 .../ugrid_ex2_2d_triangular.cdl | 0 .../ugrid_ex3_2d_flexible.cdl | 0 .../ugrid_ex4_3d_layered.cdl | 0 .../ugrid/2D_1t_face_half_levels.cml | 0 .../ugrid/2D_72t_face_half_levels.cml | 0 .../ugrid/3D_1t_face_full_levels.cml | 0 .../ugrid/3D_1t_face_half_levels.cml | 0 .../ugrid/3D_snow_pseudo_levels.cml | 0 .../ugrid/3D_soil_pseudo_levels.cml | 0 .../ugrid/3D_tile_pseudo_levels.cml | 0 .../ugrid/3D_veg_pseudo_levels.cml | 0 .../{experimental => }/ugrid/surface_mean.cml | 0 lib/iris/tests/stock/__init__.py | 2 +- lib/iris/tests/stock/mesh.py | 2 +- .../metadata/test_metadata_manager_factory.py | 2 +- .../unit/common/mixin/test_CFVariableMixin.py | 2 +- .../unit/coords/test__DimensionalMetadata.py | 2 +- .../experimental/ugrid/metadata/__init__.py | 5 - .../unit/experimental/ugrid/utils/__init__.py | 5 - .../netcdf/loader/test_load_cubes.py | 2 +- .../netcdf/saver/test_Saver__ugrid.py | 6 +- .../tests/unit/tests/stock/test_netcdf.py | 4 +- .../unit/{experimental => }/ugrid/__init__.py | 2 +- .../{experimental => }/ugrid/cf/__init__.py | 2 +- ...test_CFUGridAuxiliaryCoordinateVariable.py | 9 +- .../cf/test_CFUGridConnectivityVariable.py | 11 +- .../ugrid/cf/test_CFUGridGroup.py | 9 +- .../ugrid/cf/test_CFUGridMeshVariable.py | 9 +- .../ugrid/cf/test_CFUGridReader.py | 13 +- .../ugrid/mesh => ugrid/load}/__init__.py | 2 +- .../ugrid/load/test_ParseUgridOnLoad.py | 4 +- .../ugrid/load/test_load_mesh.py | 6 +- .../ugrid/load/test_load_meshes.py | 4 +- .../ugrid/load/test_meshload_checks.py | 0 .../ugrid/load => ugrid/mesh}/__init__.py | 2 +- .../ugrid/mesh/test_Connectivity.py | 4 +- .../ugrid/mesh/test_Mesh.py | 4 +- .../ugrid/mesh/test_MeshCoord.py | 4 +- .../ugrid/mesh/test_Mesh__from_coords.py | 6 +- .../tests/unit/ugrid/metadata/__init__.py | 7 + .../metadata/test_ConnectivityMetadata.py | 4 +- .../ugrid/metadata/test_MeshCoordMetadata.py | 4 +- .../ugrid/metadata/test_MeshMetadata.py | 4 +- lib/iris/tests/unit/ugrid/utils/__init__.py | 7 + .../ugrid/utils/test_recombine_submeshes.py | 4 +- lib/iris/{experimental => }/ugrid/__init__.py | 4 +- lib/iris/{experimental => }/ugrid/cf.py | 6 +- lib/iris/{experimental => }/ugrid/load.py | 65 +++---- lib/iris/{experimental => }/ugrid/mesh.py | 162 +++++++++--------- lib/iris/{experimental => }/ugrid/metadata.py | 12 +- lib/iris/{experimental => }/ugrid/save.py | 6 +- lib/iris/{experimental => }/ugrid/utils.py | 2 +- 72 files changed, 416 insertions(+), 323 deletions(-) create mode 100644 lib/iris/experimental/ugrid.py rename lib/iris/tests/integration/{experimental => ugrid}/test_ugrid_load.py (95%) rename lib/iris/tests/integration/{experimental => ugrid}/test_ugrid_save.py (100%) rename lib/iris/tests/integration/{experimental => ugrid}/ugrid_conventions_examples/README.txt (100%) rename lib/iris/tests/integration/{experimental => ugrid}/ugrid_conventions_examples/ugrid_ex1_1d_mesh.cdl (100%) rename lib/iris/tests/integration/{experimental => ugrid}/ugrid_conventions_examples/ugrid_ex2_2d_triangular.cdl (100%) rename lib/iris/tests/integration/{experimental => ugrid}/ugrid_conventions_examples/ugrid_ex3_2d_flexible.cdl (100%) rename lib/iris/tests/integration/{experimental => ugrid}/ugrid_conventions_examples/ugrid_ex4_3d_layered.cdl (100%) rename lib/iris/tests/results/{experimental => }/ugrid/2D_1t_face_half_levels.cml (100%) rename lib/iris/tests/results/{experimental => }/ugrid/2D_72t_face_half_levels.cml (100%) rename lib/iris/tests/results/{experimental => }/ugrid/3D_1t_face_full_levels.cml (100%) rename lib/iris/tests/results/{experimental => }/ugrid/3D_1t_face_half_levels.cml (100%) rename lib/iris/tests/results/{experimental => }/ugrid/3D_snow_pseudo_levels.cml (100%) rename lib/iris/tests/results/{experimental => }/ugrid/3D_soil_pseudo_levels.cml (100%) rename lib/iris/tests/results/{experimental => }/ugrid/3D_tile_pseudo_levels.cml (100%) rename lib/iris/tests/results/{experimental => }/ugrid/3D_veg_pseudo_levels.cml (100%) rename lib/iris/tests/results/{experimental => }/ugrid/surface_mean.cml (100%) delete mode 100644 lib/iris/tests/unit/experimental/ugrid/metadata/__init__.py delete mode 100644 lib/iris/tests/unit/experimental/ugrid/utils/__init__.py rename lib/iris/tests/unit/{experimental => }/ugrid/__init__.py (72%) rename lib/iris/tests/unit/{experimental => }/ugrid/cf/__init__.py (71%) rename lib/iris/tests/unit/{experimental => }/ugrid/cf/test_CFUGridAuxiliaryCoordinateVariable.py (96%) rename lib/iris/tests/unit/{experimental => }/ugrid/cf/test_CFUGridConnectivityVariable.py (95%) rename lib/iris/tests/unit/{experimental => }/ugrid/cf/test_CFUGridGroup.py (93%) rename lib/iris/tests/unit/{experimental => }/ugrid/cf/test_CFUGridMeshVariable.py (97%) rename lib/iris/tests/unit/{experimental => }/ugrid/cf/test_CFUGridReader.py (94%) rename lib/iris/tests/unit/{experimental/ugrid/mesh => ugrid/load}/__init__.py (70%) rename lib/iris/tests/unit/{experimental => }/ugrid/load/test_ParseUgridOnLoad.py (80%) rename lib/iris/tests/unit/{experimental => }/ugrid/load/test_load_mesh.py (86%) rename lib/iris/tests/unit/{experimental => }/ugrid/load/test_load_meshes.py (97%) rename lib/iris/tests/unit/{experimental => }/ugrid/load/test_meshload_checks.py (100%) rename lib/iris/tests/unit/{experimental/ugrid/load => ugrid/mesh}/__init__.py (70%) rename lib/iris/tests/unit/{experimental => }/ugrid/mesh/test_Connectivity.py (98%) rename lib/iris/tests/unit/{experimental => }/ugrid/mesh/test_Mesh.py (99%) rename lib/iris/tests/unit/{experimental => }/ugrid/mesh/test_MeshCoord.py (99%) rename lib/iris/tests/unit/{experimental => }/ugrid/mesh/test_Mesh__from_coords.py (97%) create mode 100644 lib/iris/tests/unit/ugrid/metadata/__init__.py rename lib/iris/tests/unit/{experimental => }/ugrid/metadata/test_ConnectivityMetadata.py (99%) rename lib/iris/tests/unit/{experimental => }/ugrid/metadata/test_MeshCoordMetadata.py (99%) rename lib/iris/tests/unit/{experimental => }/ugrid/metadata/test_MeshMetadata.py (99%) create mode 100644 lib/iris/tests/unit/ugrid/utils/__init__.py rename lib/iris/tests/unit/{experimental => }/ugrid/utils/test_recombine_submeshes.py (99%) rename lib/iris/{experimental => }/ugrid/__init__.py (91%) rename lib/iris/{experimental => }/ugrid/cf.py (98%) rename lib/iris/{experimental => }/ugrid/load.py (88%) rename lib/iris/{experimental => }/ugrid/mesh.py (94%) rename lib/iris/{experimental => }/ugrid/metadata.py (97%) rename lib/iris/{experimental => }/ugrid/save.py (87%) rename lib/iris/{experimental => }/ugrid/utils.py (99%) diff --git a/docs/src/further_topics/ugrid/data_model.rst b/docs/src/further_topics/ugrid/data_model.rst index d7282c71d8..9e74647e96 100644 --- a/docs/src/further_topics/ugrid/data_model.rst +++ b/docs/src/further_topics/ugrid/data_model.rst @@ -298,7 +298,7 @@ How Iris Represents This .. seealso:: Remember this is a prose summary. Precise documentation is at: - :mod:`iris.experimental.ugrid`. + :mod:`iris.ugrid`. .. note:: @@ -310,7 +310,7 @@ The Basics The Iris :class:`~iris.cube.Cube` has several new members: * | :attr:`~iris.cube.Cube.mesh` - | The :class:`iris.experimental.ugrid.MeshXY` that describes the + | The :class:`iris.ugrid.MeshXY` that describes the :class:`~iris.cube.Cube`\'s horizontal geography. * | :attr:`~iris.cube.Cube.location` | ``node``/``edge``/``face`` - the mesh element type with which this @@ -320,10 +320,10 @@ The Iris :class:`~iris.cube.Cube` has several new members: indexes over the horizontal :attr:`~iris.cube.Cube.data` positions. These members will all be ``None`` for a :class:`~iris.cube.Cube` with no -associated :class:`~iris.experimental.ugrid.MeshXY`. +associated :class:`~iris.ugrid.MeshXY`. This :class:`~iris.cube.Cube`\'s unstructured dimension has multiple attached -:class:`iris.experimental.ugrid.MeshCoord`\s (one for each axis e.g. +:class:`iris.ugrid.MeshCoord`\s (one for each axis e.g. ``x``/``y``), which can be used to infer the points and bounds of any index on the :class:`~iris.cube.Cube`\'s unstructured dimension. @@ -333,7 +333,7 @@ the :class:`~iris.cube.Cube`\'s unstructured dimension. from iris.coords import AuxCoord, DimCoord from iris.cube import Cube - from iris.experimental.ugrid import Connectivity, MeshXY + from iris.ugrid import Connectivity, MeshXY node_x = AuxCoord( points=[0.0, 5.0, 0.0, 5.0, 8.0], @@ -422,38 +422,38 @@ The Detail ---------- How UGRID information is stored ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -* | :class:`iris.experimental.ugrid.MeshXY` +* | :class:`iris.ugrid.MeshXY` | Contains all information about the mesh. | Includes: - * | :attr:`~iris.experimental.ugrid.MeshXY.topology_dimension` + * | :attr:`~iris.ugrid.MeshXY.topology_dimension` | The maximum dimensionality of shape (1D=edge, 2D=face) supported - by this :class:`~iris.experimental.ugrid.MeshXY`. Determines which - :class:`~iris.experimental.ugrid.Connectivity`\s are required/optional + by this :class:`~iris.ugrid.MeshXY`. Determines which + :class:`~iris.ugrid.Connectivity`\s are required/optional (see below). * 1-3 collections of :class:`iris.coords.AuxCoord`\s: - * | **Required**: :attr:`~iris.experimental.ugrid.MeshXY.node_coords` + * | **Required**: :attr:`~iris.ugrid.MeshXY.node_coords` | The nodes that are the basis for the mesh. - * | Optional: :attr:`~iris.experimental.ugrid.MeshXY.edge_coords`, - :attr:`~iris.experimental.ugrid.MeshXY.face_coords` + * | Optional: :attr:`~iris.ugrid.Mesh.edge_coords`, + :attr:`~iris.ugrid.MeshXY.face_coords` | For indicating the 'centres' of the edges/faces. - | **NOTE:** generating a :class:`~iris.experimental.ugrid.MeshCoord` from - a :class:`~iris.experimental.ugrid.MeshXY` currently (``Jan 2022``) + | **NOTE:** generating a :class:`~iris.ugrid.MeshCoord` from + a :class:`~iris.ugrid.MeshXY` currently (``Jan 2022``) requires centre coordinates for the given ``location``; to be rectified in future. - * 1 or more :class:`iris.experimental.ugrid.Connectivity`\s: + * 1 or more :class:`iris.ugrid.Connectivity`\s: * | **Required for 1D (edge) elements**: - :attr:`~iris.experimental.ugrid.MeshXY.edge_node_connectivity` + :attr:`~iris.ugrid.MeshXY.edge_node_connectivity` | Define the edges by connecting nodes. * | **Required for 2D (face) elements**: - :attr:`~iris.experimental.ugrid.MeshXY.face_node_connectivity` + :attr:`~iris.ugrid.MeshXY.face_node_connectivity` | Define the faces by connecting nodes. * Optional: any other connectivity type. See - :attr:`iris.experimental.ugrid.mesh.Connectivity.UGRID_CF_ROLES` for the + :attr:`iris.ugrid.mesh.Connectivity.UGRID_CF_ROLES` for the full list of types. .. doctest:: ugrid_summaries @@ -480,30 +480,30 @@ How UGRID information is stored long_name: 'my_mesh' -* | :class:`iris.experimental.ugrid.MeshCoord` +* | :class:`iris.ugrid.MeshCoord` | Described in detail in `MeshCoords`_. | Stores the following information: - * | :attr:`~iris.experimental.ugrid.MeshCoord.mesh` - | The :class:`~iris.experimental.ugrid.MeshXY` associated with this - :class:`~iris.experimental.ugrid.MeshCoord`. This determines the + * | :attr:`~iris.ugrid.MeshCoord.mesh` + | The :class:`~iris.ugrid.MeshXY` associated with this + :class:`~iris.ugrid.MeshCoord`. This determines the :attr:`~iris.cube.Cube.mesh` attribute of any :class:`~iris.cube.Cube` - this :class:`~iris.experimental.ugrid.MeshCoord` is attached to (see + this :class:`~iris.ugrid.MeshCoord` is attached to (see `The Basics`_) - * | :attr:`~iris.experimental.ugrid.MeshCoord.location` + * | :attr:`~iris.ugrid.MeshCoord.location` | ``node``/``edge``/``face`` - the element detailed by this - :class:`~iris.experimental.ugrid.MeshCoord`. This determines the + :class:`~iris.ugrid.MeshCoord`. This determines the :attr:`~iris.cube.Cube.location` attribute of any :class:`~iris.cube.Cube` this - :class:`~iris.experimental.ugrid.MeshCoord` is attached to (see + :class:`~iris.ugrid.MeshCoord` is attached to (see `The Basics`_). .. _ugrid MeshCoords: MeshCoords ~~~~~~~~~~ -Links a :class:`~iris.cube.Cube` to a :class:`~iris.experimental.ugrid.MeshXY` by +Links a :class:`~iris.cube.Cube` to a :class:`~iris.ugrid.MeshXY` by attaching to the :class:`~iris.cube.Cube`\'s unstructured dimension, in the same way that all :class:`~iris.coords.Coord`\s attach to :class:`~iris.cube.Cube` dimensions. This allows a single @@ -511,23 +511,23 @@ same way that all :class:`~iris.coords.Coord`\s attach to dimensions (e.g. horizontal mesh plus vertical levels and a time series), using the same logic for every dimension. -:class:`~iris.experimental.ugrid.MeshCoord`\s are instantiated using a given -:class:`~iris.experimental.ugrid.MeshXY`, ``location`` +:class:`~iris.ugrid.MeshCoord`\s are instantiated using a given +:class:`~iris.ugrid.MeshXY`, ``location`` ("node"/"edge"/"face") and ``axis``. The process interprets the -:class:`~iris.experimental.ugrid.MeshXY`\'s -:attr:`~iris.experimental.ugrid.MeshXY.node_coords` and if appropriate the -:attr:`~iris.experimental.ugrid.MeshXY.edge_node_connectivity`/ -:attr:`~iris.experimental.ugrid.MeshXY.face_node_connectivity` and -:attr:`~iris.experimental.ugrid.MeshXY.edge_coords`/ -:attr:`~iris.experimental.ugrid.MeshXY.face_coords` +:class:`~iris.ugrid.MeshXY`\'s +:attr:`~iris.ugrid.MeshXY.node_coords` and if appropriate the +:attr:`~iris.ugrid.MeshXY.edge_node_connectivity`/ +:attr:`~iris.ugrid.MeshXY.face_node_connectivity` and +:attr:`~iris.ugrid.MeshXY.edge_coords`/ +:attr:`~iris.ugrid.MeshXY.face_coords` to produce a :class:`~iris.coords.Coord` :attr:`~iris.coords.Coord.points` and :attr:`~iris.coords.Coord.bounds` -representation of all the :class:`~iris.experimental.ugrid.MeshXY`\'s +representation of all the :class:`~iris.ugrid.MeshXY`\'s nodes/edges/faces for the given axis. -The method :meth:`iris.experimental.ugrid.MeshXY.to_MeshCoords` is available to -create a :class:`~iris.experimental.ugrid.MeshCoord` for -every axis represented by that :class:`~iris.experimental.ugrid.MeshXY`, +The method :meth:`iris.ugrid.MeshXY.to_MeshCoords` is available to +create a :class:`~iris.ugrid.MeshCoord` for +every axis represented by that :class:`~iris.ugrid.MeshXY`, given only the ``location`` argument .. doctest:: ugrid_summaries diff --git a/docs/src/further_topics/ugrid/index.rst b/docs/src/further_topics/ugrid/index.rst index c45fd271a2..e21730bb6e 100644 --- a/docs/src/further_topics/ugrid/index.rst +++ b/docs/src/further_topics/ugrid/index.rst @@ -9,7 +9,7 @@ Iris includes specialised handling of mesh-located data (as opposed to grid-located data). Iris and its :ref:`partner packages ` are designed to make working with mesh-located data as simple as possible, with new capabilities being added all the time. More detail is in this section and in -the :mod:`iris.experimental.ugrid` API documentation. +the :mod:`iris.ugrid` API documentation. This mesh support is based on the `CF-UGRID Conventions`__; UGRID-conformant meshes + data can be loaded from a file into Iris' data model, and meshes + diff --git a/docs/src/further_topics/ugrid/operations.rst b/docs/src/further_topics/ugrid/operations.rst index 80ff284f66..f7b9eb2fca 100644 --- a/docs/src/further_topics/ugrid/operations.rst +++ b/docs/src/further_topics/ugrid/operations.rst @@ -61,7 +61,7 @@ subsequent example operations on this page. >>> import numpy as np >>> from iris.coords import AuxCoord - >>> from iris.experimental.ugrid import Connectivity, MeshXY + >>> from iris.ugrid import Connectivity, MeshXY # Going to create the following mesh # (node indices are shown to aid understanding): @@ -143,8 +143,8 @@ Making a Cube (with a Mesh) .. rubric:: |tagline: making a cube| Creating a :class:`~iris.cube.Cube` is unchanged; the -:class:`~iris.experimental.ugrid.MeshXY` is linked via a -:class:`~iris.experimental.ugrid.MeshCoord` (see :ref:`ugrid MeshCoords`): +:class:`~iris.ugrid.MeshXY` is linked via a +:class:`~iris.ugrid.MeshCoord` (see :ref:`ugrid MeshCoords`): .. dropdown:: Code :icon: code @@ -205,7 +205,7 @@ Save .. note:: UGRID saving support is limited to the NetCDF file format. The Iris saving process automatically detects if the :class:`~iris.cube.Cube` -has an associated :class:`~iris.experimental.ugrid.MeshXY` and automatically +has an associated :class:`~iris.ugrid.MeshXY` and automatically saves the file in a UGRID-conformant format: .. dropdown:: Code @@ -282,8 +282,8 @@ saves the file in a UGRID-conformant format: } -The :func:`iris.experimental.ugrid.save_mesh` function allows -:class:`~iris.experimental.ugrid.MeshXY`\es to be saved to file without +The :func:`iris.ugrid.save_mesh` function allows +:class:`~iris.ugrid.MeshXY`\es to be saved to file without associated :class:`~iris.cube.Cube`\s: .. dropdown:: Code @@ -293,7 +293,7 @@ associated :class:`~iris.cube.Cube`\s: >>> from subprocess import run - >>> from iris.experimental.ugrid import save_mesh + >>> from iris.ugrid import save_mesh >>> mesh_path = "my_mesh.nc" >>> save_mesh(my_mesh, mesh_path) @@ -356,7 +356,7 @@ Load While Iris' UGRID support remains :mod:`~iris.experimental`, parsing UGRID when loading a file remains **optional**. To load UGRID data from a file into the Iris mesh data model, use the -:const:`iris.experimental.ugrid.PARSE_UGRID_ON_LOAD` context manager: +:const:`iris.ugrid.PARSE_UGRID_ON_LOAD` context manager: .. dropdown:: Code :icon: code @@ -364,7 +364,7 @@ Iris mesh data model, use the .. doctest:: ugrid_operations >>> from iris import load - >>> from iris.experimental.ugrid import PARSE_UGRID_ON_LOAD + >>> from iris.ugrid import PARSE_UGRID_ON_LOAD >>> with PARSE_UGRID_ON_LOAD.context(): ... loaded_cubelist = load(cubelist_path) @@ -412,15 +412,15 @@ etcetera: .. note:: We recommend caution if constraining on coordinates associated with a - :class:`~iris.experimental.ugrid.MeshXY`. An individual coordinate value + :class:`~iris.ugrid.MeshXY`. An individual coordinate value might not be shared by any other data points, and using a coordinate range will demand notably higher performance given the size of the dimension versus structured grids (:ref:`see the data model detail `). -The :func:`iris.experimental.ugrid.load_mesh` and -:func:`~iris.experimental.ugrid.load_meshes` functions allow only -:class:`~iris.experimental.ugrid.MeshXY`\es to be loaded from a file without +The :func:`iris.ugrid.load_mesh` and +:func:`~iris.ugrid.load_meshes` functions allow only +:class:`~iris.ugrid.MeshXY`\es to be loaded from a file without creating any associated :class:`~iris.cube.Cube`\s: .. dropdown:: Code @@ -428,7 +428,7 @@ creating any associated :class:`~iris.cube.Cube`\s: .. doctest:: ugrid_operations - >>> from iris.experimental.ugrid import load_mesh + >>> from iris.ugrid import load_mesh >>> with PARSE_UGRID_ON_LOAD.context(): ... loaded_mesh = load_mesh(cubelist_path) @@ -493,7 +493,7 @@ GeoVista :external+geovista:doc:`generated/gallery/index`. >>> from iris import load_cube, sample_data_path >>> from iris.experimental.geovista import cube_to_polydata - >>> from iris.experimental.ugrid import PARSE_UGRID_ON_LOAD + >>> from iris.ugrid import PARSE_UGRID_ON_LOAD >>> with PARSE_UGRID_ON_LOAD.context(): ... sample_mesh_cube = load_cube(sample_data_path("mesh_C4_synthetic_float.nc")) @@ -541,11 +541,11 @@ As described in :doc:`data_model`, indexing for a range along a :class:`~iris.cube.Cube`\'s :meth:`~iris.cube.Cube.mesh_dim` will not provide a contiguous region, since **position on the unstructured dimension is unrelated to spatial position**. This means that subsetted -:class:`~iris.experimental.ugrid.MeshCoord`\s cannot be reliably interpreted -as intended, and subsetting a :class:`~iris.experimental.ugrid.MeshCoord` is +:class:`~iris.ugrid.MeshCoord`\s cannot be reliably interpreted +as intended, and subsetting a :class:`~iris.ugrid.MeshCoord` is therefore set to return an :class:`~iris.coords.AuxCoord` instead - breaking the link between :class:`~iris.cube.Cube` and -:class:`~iris.experimental.ugrid.MeshXY`: +:class:`~iris.ugrid.MeshXY`: .. dropdown:: Code :icon: code @@ -595,7 +595,7 @@ below: >>> from geovista.geodesic import BBox >>> from iris import load_cube, sample_data_path >>> from iris.experimental.geovista import cube_to_polydata, extract_unstructured_region - >>> from iris.experimental.ugrid import PARSE_UGRID_ON_LOAD + >>> from iris.ugrid import PARSE_UGRID_ON_LOAD >>> with PARSE_UGRID_ON_LOAD.context(): ... sample_mesh_cube = load_cube(sample_data_path("mesh_C4_synthetic_float.nc")) @@ -667,7 +667,7 @@ with the >>> from esmf_regrid.experimental.unstructured_scheme import MeshToGridESMFRegridder >>> from iris import load, load_cube - >>> from iris.experimental.ugrid import PARSE_UGRID_ON_LOAD + >>> from iris.ugrid import PARSE_UGRID_ON_LOAD # You could also download these files from github.com/SciTools/iris-test-data. >>> from iris.tests import get_data_path @@ -751,7 +751,7 @@ with the The initialisation process is computationally expensive so we use caching to improve performance. Once a regridder has been initialised, it can be used on any :class:`~iris.cube.Cube` which has been defined on the same -:class:`~iris.experimental.ugrid.MeshXY` (or on the same **grid** in the case of +:class:`~iris.ugrid.MeshXY` (or on the same **grid** in the case of :class:`~esmf_regrid.experimental.unstructured_scheme.GridToMeshESMFRegridder`). Since calling a regridder is usually a lot faster than initialising, reusing regridders can save a lot of time. We can demonstrate the reuse of the @@ -819,19 +819,19 @@ Equality .. rubric:: |tagline: equality| -:class:`~iris.experimental.ugrid.MeshXY` comparison is supported, and comparing -two ':class:`~iris.experimental.ugrid.MeshXY`-:class:`~iris.cube.Cube`\s' will +:class:`~iris.ugrid.MeshXY` comparison is supported, and comparing +two ':class:`~iris.ugrid.MeshXY`-:class:`~iris.cube.Cube`\s' will include a comparison of the respective -:class:`~iris.experimental.ugrid.MeshXY`\es, with no extra action needed by the +:class:`~iris.ugrid.MeshXY`\es, with no extra action needed by the user. .. note:: Keep an eye on memory demand when comparing large - :class:`~iris.experimental.ugrid.MeshXY`\es, but note that - :class:`~iris.experimental.ugrid.MeshXY`\ equality is enabled for lazy + :class:`~iris.ugrid.MeshXY`\es, but note that + :class:`~iris.ugrid.MeshXY`\ equality is enabled for lazy processing (:doc:`/userguide/real_and_lazy_data`), so if the - :class:`~iris.experimental.ugrid.MeshXY`\es being compared are lazy the + :class:`~iris.ugrid.MeshXY`\es being compared are lazy the process will use less memory than their total size. Combining Cubes @@ -842,23 +842,23 @@ Combining Cubes Merging or concatenating :class:`~iris.cube.Cube`\s (described in :doc:`/userguide/merge_and_concat`) with two different -:class:`~iris.experimental.ugrid.MeshXY`\es is not possible - a +:class:`~iris.ugrid.MeshXY`\es is not possible - a :class:`~iris.cube.Cube` must be associated with just a single -:class:`~iris.experimental.ugrid.MeshXY`, and merge/concatenate are not yet -capable of combining multiple :class:`~iris.experimental.ugrid.MeshXY`\es into +:class:`~iris.ugrid.MeshXY`, and merge/concatenate are not yet +capable of combining multiple :class:`~iris.ugrid.MeshXY`\es into one. :class:`~iris.cube.Cube`\s that include -:class:`~iris.experimental.ugrid.MeshCoord`\s can still be merged/concatenated +:class:`~iris.ugrid.MeshCoord`\s can still be merged/concatenated on dimensions other than the :meth:`~iris.cube.Cube.mesh_dim`, since such :class:`~iris.cube.Cube`\s will by definition share the same -:class:`~iris.experimental.ugrid.MeshXY`. +:class:`~iris.ugrid.MeshXY`. .. seealso:: You may wish to investigate - :func:`iris.experimental.ugrid.recombine_submeshes`, which can be used - for a very specific type of :class:`~iris.experimental.ugrid.MeshXY` + :func:`iris.ugrid.recombine_submeshes`, which can be used + for a very specific type of :class:`~iris.ugrid.MeshXY` combination not detailed here. Arithmetic @@ -869,7 +869,7 @@ Arithmetic Cube Arithmetic (described in :doc:`/userguide/cube_maths`) has been extended to handle :class:`~iris.cube.Cube`\s that include -:class:`~iris.experimental.ugrid.MeshCoord`\s, and hence have a ``cube.mesh``. +:class:`~iris.ugrid.MeshCoord`\s, and hence have a ``cube.mesh``. Cubes with meshes can be combined in arithmetic operations like "ordinary" cubes. They can combine with other cubes without that mesh diff --git a/docs/src/further_topics/ugrid/other_meshes.rst b/docs/src/further_topics/ugrid/other_meshes.rst index df83c8c4f6..19f220be82 100644 --- a/docs/src/further_topics/ugrid/other_meshes.rst +++ b/docs/src/further_topics/ugrid/other_meshes.rst @@ -25,7 +25,7 @@ A FESOM mesh encoded in a NetCDF file includes: To represent the Voronoi Polygons as faces, the corner coordinates will be used as the **nodes** when creating the Iris -:class:`~iris.experimental.ugrid.mesh.MeshXY`. +:class:`~iris.ugrid.mesh.MeshXY`. .. dropdown:: Code :icon: code @@ -33,7 +33,7 @@ as the **nodes** when creating the Iris .. code-block:: python >>> import iris - >>> from iris.experimental.ugrid import MeshXY + >>> from iris.ugrid import MeshXY >>> temperature_cube = iris.load_cube("my_file.nc", "sea_surface_temperature") @@ -113,7 +113,7 @@ An SMC grid encoded in a NetCDF file includes: From this information we can derive face corner coordinates, which will be used as the **nodes** when creating the Iris -:class:`~iris.experimental.ugrid.mesh.MeshXY`. +:class:`~iris.ugrid.mesh.MeshXY`. .. dropdown:: Code @@ -122,7 +122,7 @@ as the **nodes** when creating the Iris .. code-block:: python >>> import iris - >>> from iris.experimental.ugrid import MeshXY + >>> from iris.ugrid import MeshXY >>> import numpy as np @@ -265,7 +265,7 @@ dimensions into a single mesh dimension. Since Iris cubes don't support a "resh >>> import iris >>> from iris.coords import AuxCoord, CellMeasure >>> from iris.cube import Cube - >>> from iris.experimental.ugrid.mesh import MeshXY, Connectivity + >>> from iris.ugrid.mesh import MeshXY, Connectivity >>> filepath = iris.sample_data_path('orca2_votemper.nc') diff --git a/docs/src/further_topics/ugrid/partner_packages.rst b/docs/src/further_topics/ugrid/partner_packages.rst index 87a61ae0fe..5dea58b752 100644 --- a/docs/src/further_topics/ugrid/partner_packages.rst +++ b/docs/src/further_topics/ugrid/partner_packages.rst @@ -58,7 +58,7 @@ PyVista is described as "VTK for humans" - VTK is a very powerful toolkit for working with meshes, and PyVista brings that power into the Python ecosystem. GeoVista in turn makes it easy to use PyVista specifically for cartographic work, designed from the start with the Iris -:class:`~iris.experimental.ugrid.MeshXY` in mind. +:class:`~iris.ugrid.MeshXY` in mind. Applications ------------ diff --git a/lib/iris/analysis/_regrid.py b/lib/iris/analysis/_regrid.py index 6c10b8c404..31ceafb025 100644 --- a/lib/iris/analysis/_regrid.py +++ b/lib/iris/analysis/_regrid.py @@ -998,7 +998,7 @@ def _create_cube(data, src, src_dims, tgt_coords, num_tgt_dims, regrid_callback) The dimensions of the X and Y coordinate within the source Cube. tgt_coords : tuple of :class:`iris.coords.Coord Either two 1D :class:`iris.coords.DimCoord`, two 1D - :class:`iris.experimental.ugrid.DimCoord` or two n-D + :class:`iris.ugrid.DimCoord` or two n-D :class:`iris.coords.AuxCoord` representing the new grid's X and Y coordinates. num_tgt_dims : int diff --git a/lib/iris/common/metadata.py b/lib/iris/common/metadata.py index e11ea71462..c705054725 100644 --- a/lib/iris/common/metadata.py +++ b/lib/iris/common/metadata.py @@ -1602,7 +1602,8 @@ def metadata_manager_factory(cls, **kwargs): #: Convenience collection of lenient metadata combine services. # TODO: change lists back to tuples once CellMeasureMetadata is re-integrated -# here (currently in experimental.ugrid). +# here (currently in iris.ugrid). +# TODO: complete iris.ugrid replacement SERVICES_COMBINE = [ AncillaryVariableMetadata.combine, BaseMetadata.combine, diff --git a/lib/iris/common/resolve.py b/lib/iris/common/resolve.py index 8b5f0cdc7f..d678d13cf8 100644 --- a/lib/iris/common/resolve.py +++ b/lib/iris/common/resolve.py @@ -71,7 +71,7 @@ class _PreparedItem: axis: Any = None def create_coord(self, metadata): - from iris.experimental.ugrid.mesh import MeshCoord + from iris.ugrid.mesh import MeshCoord if issubclass(self.container, MeshCoord): # Make a MeshCoord, for which we have mesh/location/axis. @@ -741,7 +741,7 @@ def _create_prepared_item( if container is None: container = type(coord) - from iris.experimental.ugrid.mesh import MeshCoord + from iris.ugrid.mesh import MeshCoord if issubclass(container, MeshCoord): # Build a prepared-item to make a MeshCoord. diff --git a/lib/iris/coords.py b/lib/iris/coords.py index e563b56498..40b131e496 100644 --- a/lib/iris/coords.py +++ b/lib/iris/coords.py @@ -844,7 +844,8 @@ def xml_element(self, doc): if isinstance(self, Coord): values_term = "points" # TODO: replace with isinstance(self, Connectivity) once Connectivity - # is re-integrated here (currently in experimental.ugrid). + # is re-integrated here (currently in iris.ugrid). + # TODO: complete iris.ugrid replacement elif hasattr(self, "indices"): values_term = "indices" else: diff --git a/lib/iris/cube.py b/lib/iris/cube.py index 47b66b6ead..eb4b82fd73 100644 --- a/lib/iris/cube.py +++ b/lib/iris/cube.py @@ -2088,7 +2088,7 @@ def coords( If ``None``, returns all coordinates. mesh_coords : optional Set to ``True`` to return only coordinates which are - :class:`~iris.experimental.ugrid.MeshCoord`\'s. + :class:`~iris.ugrid.MeshCoord`\'s. Set to ``False`` to return only non-mesh coordinates. If ``None``, returns all coordinates. @@ -2115,7 +2115,7 @@ def coords( if mesh_coords is not None: # Select on mesh or non-mesh. mesh_coords = bool(mesh_coords) - # Use duck typing to avoid importing from iris.experimental.ugrid, + # Use duck typing to avoid importing from iris.ugrid, # which could be a circular import. if mesh_coords: # *only* MeshCoords @@ -2245,7 +2245,7 @@ def coord( If ``None``, returns all coordinates. mesh_coords : optional Set to ``True`` to return only coordinates which are - :class:`~iris.experimental.ugrid.MeshCoord`\'s. + :class:`~iris.ugrid.MeshCoord`\'s. Set to ``False`` to return only non-mesh coordinates. If ``None``, returns all coordinates. @@ -2365,18 +2365,18 @@ def _any_meshcoord(self): @property def mesh(self): - r"""Return the unstructured :class:`~iris.experimental.ugrid.MeshXY` associated with the cube. + r"""Return the unstructured :class:`~iris.ugrid.MeshXY` associated with the cube. - Return the unstructured :class:`~iris.experimental.ugrid.MeshXY` + Return the unstructured :class:`~iris.ugrid.MeshXY` associated with the cube, if the cube has any - :class:`~iris.experimental.ugrid.MeshCoord`, + :class:`~iris.ugrid.MeshCoord`, or ``None`` if it has none. Returns ------- - :class:`iris.experimental.ugrid.mesh.MeshXY` or None + :class:`iris.ugrid.mesh.MeshXY` or None The mesh of the cube - :class:`~iris.experimental.ugrid.MeshCoord`'s, + :class:`~iris.ugrid.MeshCoord`'s, or ``None``. """ @@ -2390,14 +2390,14 @@ def location(self): r"""Return the mesh "location" of the cube data. Return the mesh "location" of the cube data, if the cube has any - :class:`~iris.experimental.ugrid.MeshCoord`, + :class:`~iris.ugrid.MeshCoord`, or ``None`` if it has none. Returns ------- str or None The mesh location of the cube - :class:`~iris.experimental.ugrid.MeshCoords` + :class:`~iris.ugrid.MeshCoords` (i.e. one of 'face' / 'edge' / 'node'), or ``None``. """ @@ -2410,14 +2410,14 @@ def mesh_dim(self): r"""Return the cube dimension of the mesh. Return the cube dimension of the mesh, if the cube has any - :class:`~iris.experimental.ugrid.MeshCoord`, + :class:`~iris.ugrid.MeshCoord`, or ``None`` if it has none. Returns ------- int or None The cube dimension which the cube - :class:`~iris.experimental.ugrid.MeshCoord` map to, + :class:`~iris.ugrid.MeshCoord` map to, or ``None``. """ diff --git a/lib/iris/experimental/geovista.py b/lib/iris/experimental/geovista.py index 3f42f09e03..690e19d543 100644 --- a/lib/iris/experimental/geovista.py +++ b/lib/iris/experimental/geovista.py @@ -8,7 +8,7 @@ from geovista.common import VTK_CELL_IDS, VTK_POINT_IDS from iris.exceptions import CoordinateNotFoundError -from iris.experimental.ugrid import MeshXY +from iris.ugrid import MeshXY def _get_coord(cube, axis): @@ -52,7 +52,7 @@ def cube_to_polydata(cube, **kwargs): If a :class:`~iris.cube.Cube` with too many dimensions is passed. Only the horizontal data can be represented, meaning a 2D Cube, or 1D Cube if the horizontal space is described by - :class:`~iris.experimental.ugrid.MeshCoord`\ s. + :class:`~iris.ugrid.MeshCoord`\ s. Examples -------- diff --git a/lib/iris/experimental/ugrid.py b/lib/iris/experimental/ugrid.py new file mode 100644 index 0000000000..bbc8ef93b3 --- /dev/null +++ b/lib/iris/experimental/ugrid.py @@ -0,0 +1,77 @@ +# Copyright Iris contributors +# +# This file is part of Iris and is released under the BSD license. +# See LICENSE in the root of the repository for full licensing details. + +"""Legacy import location for mesh support. + +See :mod:`iris.ugrid` for the new, correct import location. + +Notes +----- +This import path alios is provided for backwards compatibility, but will be removed +in a future release : Please re-write code to import from the new module path. + +This legacy import module will be removed in a future release. +N.B. it does **not** need to wait for a major release, since the former API was +experimental. + +.. deprecated:: 3.10 + All the former :mod:`iris.experimental.ugrid` modules have been relocated to + :mod:`iris.ugrid` and its submodules. Please re-write code to import from the new + module path. + This import path alios is provided for backwards compatibility, but will be removed + in a future release : N.B. removing this does **not** need to wait for a major + release, since the former API was experimental. + +""" + +from __future__ import annotations + +from .._deprecation import warn_deprecated +from ..ugrid.load import PARSE_UGRID_ON_LOAD, load_mesh, load_meshes +from ..ugrid.mesh import Connectivity as _Connectivity +from ..ugrid.mesh import MeshCoord as _MeshCoord +from ..ugrid.mesh import MeshXY as _MeshXY +from ..ugrid.save import save_mesh +from ..ugrid.utils import recombine_submeshes + + +# NOTE: publishing the original Mesh, MeshCoord and Connectivity here causes a Sphinx +# Sphinx warning, E.G.: +# "WARNING: duplicate object description of iris.ugrid.mesh.Mesh, other instance +# in generated/api/iris.experimental.ugrid, use :no-index: for one of them" +# For some reason, this only happens for the classes, and not the functions. +# +# This is a fatal problem, i.e. breaks the build since we are building with -W. +# We couldn't fix this with "autodoc_suppress_warnings", so the solution for now is to +# wrap the classes. Which is really ugly. +# TODO: remove this when we remove iris.experimental.ugrid +class MeshXY(_MeshXY): + pass + + +class MeshCoord(_MeshCoord): + pass + + +class Connectivity(_Connectivity): + pass + + +__all__ = [ + "Connectivity", + "MeshCoord", + "MeshXY", + "PARSE_UGRID_ON_LOAD", + "load_mesh", + "load_meshes", + "recombine_submeshes", + "save_mesh", +] + +warn_deprecated( + "All the former :mod:`iris.experimental.ugrid` modules have been relocated to " + "module 'iris.ugrid' and its submodules. " + "Please re-write code to import from the new module path." +) diff --git a/lib/iris/fileformats/cf.py b/lib/iris/fileformats/cf.py index 3247aa1960..b4c754e4a6 100644 --- a/lib/iris/fileformats/cf.py +++ b/lib/iris/fileformats/cf.py @@ -1056,7 +1056,8 @@ class CFReader: CFMeasureVariable, ) - # TODO: remove once iris.experimental.ugrid.CFUGridReader is folded in. + # TODO: remove once iris.ugrid.CFUGridReader is folded in. + # TODO: complete iris.ugrid replacement CFGroup = CFGroup def __init__(self, file_source, warn=False, monotonic=False): @@ -1174,7 +1175,8 @@ def _build_cf_groups(self): def _build(cf_variable): # TODO: isinstance(cf_variable, UGridMeshVariable) - # UGridMeshVariable currently in experimental.ugrid - circular import. + # UGridMeshVariable currently in iris.ugrid - circular import. + # TODO: complete iris.ugrid replacement is_mesh_var = cf_variable.cf_identity == "mesh" ugrid_coord_names = [] ugrid_coords = getattr(self.cf_group, "ugrid_coords", None) diff --git a/lib/iris/fileformats/netcdf/loader.py b/lib/iris/fileformats/netcdf/loader.py index 5276657195..2bdfed9fff 100644 --- a/lib/iris/fileformats/netcdf/loader.py +++ b/lib/iris/fileformats/netcdf/loader.py @@ -578,15 +578,16 @@ def load_cubes(file_sources, callback=None, constraints=None): Generator of loaded NetCDF :class:`iris.cube.Cube`. """ - # TODO: rationalise UGRID/mesh handling once experimental.ugrid is folded + # TODO: rationalise UGRID/mesh handling once iris.ugrid is folded + # TODO: complete iris.ugrid replacement # into standard behaviour. # Deferred import to avoid circular imports. - from iris.experimental.ugrid.cf import CFUGridReader - from iris.experimental.ugrid.load import ( + from iris.io import run_callback + from iris.ugrid.cf import CFUGridReader + from iris.ugrid.load import ( _build_mesh_coords, _meshes_from_cf, ) - from iris.io import run_callback # Create a low-level data-var filter from the original load constraints, if they are suitable. var_callback = _translate_constraints_to_var_callback(constraints) @@ -683,8 +684,8 @@ def __init__(self, var_dim_chunksizes=None): :class:`~iris.coords.AncillaryVariable` etc. This can be overridden, if required, by variable-specific settings. - For this purpose, :class:`~iris.experimental.ugrid.mesh.MeshCoord` and - :class:`~iris.experimental.ugrid.mesh.Connectivity` are not + For this purpose, :class:`~iris.ugrid.mesh.MeshCoord` and + :class:`~iris.ugrid.mesh.Connectivity` are not :class:`~iris.cube.Cube` components, and chunk control on a :class:`~iris.cube.Cube` data-variable will not affect them. diff --git a/lib/iris/fileformats/netcdf/saver.py b/lib/iris/fileformats/netcdf/saver.py index 179adaf9cd..1981091717 100644 --- a/lib/iris/fileformats/netcdf/saver.py +++ b/lib/iris/fileformats/netcdf/saver.py @@ -271,7 +271,7 @@ def _setncattr(variable, name, attribute): return variable.setncattr(name, attribute) -# NOTE : this matches :class:`iris.experimental.ugrid.mesh.MeshXY.ELEMENTS`, +# NOTE : this matches :class:`iris.ugrid.mesh.MeshXY.ELEMENTS`, # but in the preferred order for coord/connectivity variables in the file. MESH_ELEMENTS = ("node", "edge", "face") @@ -766,7 +766,7 @@ def _add_mesh(self, cube_or_mesh): Parameters ---------- - cube_or_mesh : :class:`iris.cube.Cube` or :class:`iris.experimental.ugrid.MeshXY` + cube_or_mesh : :class:`iris.cube.Cube` or :class:`iris.ugrid.MeshXY` The Cube or Mesh being saved to the netCDF file. Returns @@ -941,7 +941,7 @@ def _add_aux_coords(self, cube, cf_var_cube, dimension_names): dimension_names : list Names associated with the dimensions of the cube. """ - from iris.experimental.ugrid.mesh import ( + from iris.ugrid.mesh import ( MeshEdgeCoords, MeshFaceCoords, MeshNodeCoords, @@ -1120,7 +1120,7 @@ def _get_dim_names(self, cube_or_mesh): Parameters ---------- - cube_or_mesh : :class:`iris.cube.Cube` or :class:`iris.experimental.ugrid.MeshXY` + cube_or_mesh : :class:`iris.cube.Cube` or :class:`iris.ugrid.MeshXY` The Cube or Mesh being saved to the netCDF file. Returns @@ -1482,7 +1482,7 @@ def _get_coord_variable_name(self, cube_or_mesh, coord): Parameters ---------- - cube_or_mesh : :class:`iris.cube.Cube` or :class:`iris.experimental.ugrid.MeshXY` + cube_or_mesh : :class:`iris.cube.Cube` or :class:`iris.ugrid.MeshXY` The Cube or Mesh being saved to the netCDF file. coord : :class:`iris.coords._DimensionalMetadata` An instance of a coordinate (or similar), for which a CF-netCDF @@ -1524,7 +1524,7 @@ def _get_coord_variable_name(self, cube_or_mesh, coord): # element-coordinate of the mesh. # Name it for it's first dim, i.e. mesh-dim of its location. - from iris.experimental.ugrid.mesh import Connectivity + from iris.ugrid.mesh import Connectivity # At present, a location-coord cannot be nameless, as the # MeshXY code relies on guess_coord_axis. @@ -1544,7 +1544,7 @@ def _get_mesh_variable_name(self, mesh): Parameters ---------- - mesh : :class:`iris.experimental.ugrid.mesh.MeshXY` + mesh : :class:`iris.ugrid.mesh.MeshXY` An instance of a Mesh for which a CF-netCDF variable name is required. @@ -1570,7 +1570,7 @@ def _create_mesh(self, mesh): Parameters ---------- - mesh : :class:`iris.experimental.ugrid.mesh.MeshXY` + mesh : :class:`iris.ugrid.mesh.MeshXY` The Mesh to be saved to CF-netCDF file. Returns @@ -1660,7 +1660,7 @@ def _create_generic_cf_array_var( Parameters ---------- - cube_or_mesh : :class:`iris.cube.Cube` or :class:`iris.experimental.ugrid.MeshXY` + cube_or_mesh : :class:`iris.cube.Cube` or :class:`iris.ugrid.MeshXY` The Cube or Mesh being saved to the netCDF file. cube_dim_names : list of str The name of each dimension of the cube. diff --git a/lib/iris/tests/integration/experimental/test_meshcoord_coordsys.py b/lib/iris/tests/integration/experimental/test_meshcoord_coordsys.py index d9ec782108..7a1ac80823 100644 --- a/lib/iris/tests/integration/experimental/test_meshcoord_coordsys.py +++ b/lib/iris/tests/integration/experimental/test_meshcoord_coordsys.py @@ -8,8 +8,8 @@ import iris from iris.coord_systems import GeogCS -from iris.experimental.ugrid.load import PARSE_UGRID_ON_LOAD from iris.tests.stock.netcdf import ncgen_from_cdl +from iris.ugrid.load import PARSE_UGRID_ON_LOAD TEST_CDL = """ netcdf mesh_test { diff --git a/lib/iris/tests/integration/experimental/test_ugrid_load.py b/lib/iris/tests/integration/ugrid/test_ugrid_load.py similarity index 95% rename from lib/iris/tests/integration/experimental/test_ugrid_load.py rename to lib/iris/tests/integration/ugrid/test_ugrid_load.py index 4325532fc6..2281e54b3c 100644 --- a/lib/iris/tests/integration/experimental/test_ugrid_load.py +++ b/lib/iris/tests/integration/ugrid/test_ugrid_load.py @@ -4,8 +4,9 @@ # See LICENSE in the root of the repository for full licensing details. """Integration tests for NetCDF-UGRID file loading. -todo: fold these tests into netcdf tests when experimental.ugrid is folded into +todo: fold these tests into netcdf tests when iris.ugrid is folded into standard behaviour. +TODO: complete iris.ugrid replacement """ @@ -18,12 +19,12 @@ import pytest from iris import Constraint, load -from iris.experimental.ugrid.load import load_mesh, load_meshes -from iris.experimental.ugrid.mesh import MeshXY from iris.tests.stock.netcdf import ( _file_from_cdl_template as create_file_from_cdl_template, ) from iris.tests.unit.tests.stock.test_netcdf import XIOSFileMixin +from iris.ugrid.load import load_mesh, load_meshes +from iris.ugrid.mesh import MeshXY from iris.warnings import IrisCfWarning @@ -58,7 +59,7 @@ def common_test(self, load_filename, assert_filename): ) self.assertEqual(1, len(cube_list)) cube = cube_list[0] - self.assertCML(cube, ["experimental", "ugrid", assert_filename]) + self.assertCML(cube, ["ugrid", assert_filename]) def test_2D_1t_face_half_levels(self): self.common_test( @@ -125,7 +126,7 @@ def test_multiple_phenomena(self): ["NetCDF", "unstructured_grid", "lfric_surface_mean.nc"] ), ) - self.assertCML(cube_list, ("experimental", "ugrid", "surface_mean.cml")) + self.assertCML(cube_list, ("ugrid", "surface_mean.cml")) class TestTolerantLoading(XIOSFileMixin): diff --git a/lib/iris/tests/integration/experimental/test_ugrid_save.py b/lib/iris/tests/integration/ugrid/test_ugrid_save.py similarity index 100% rename from lib/iris/tests/integration/experimental/test_ugrid_save.py rename to lib/iris/tests/integration/ugrid/test_ugrid_save.py diff --git a/lib/iris/tests/integration/experimental/ugrid_conventions_examples/README.txt b/lib/iris/tests/integration/ugrid/ugrid_conventions_examples/README.txt similarity index 100% rename from lib/iris/tests/integration/experimental/ugrid_conventions_examples/README.txt rename to lib/iris/tests/integration/ugrid/ugrid_conventions_examples/README.txt diff --git a/lib/iris/tests/integration/experimental/ugrid_conventions_examples/ugrid_ex1_1d_mesh.cdl b/lib/iris/tests/integration/ugrid/ugrid_conventions_examples/ugrid_ex1_1d_mesh.cdl similarity index 100% rename from lib/iris/tests/integration/experimental/ugrid_conventions_examples/ugrid_ex1_1d_mesh.cdl rename to lib/iris/tests/integration/ugrid/ugrid_conventions_examples/ugrid_ex1_1d_mesh.cdl diff --git a/lib/iris/tests/integration/experimental/ugrid_conventions_examples/ugrid_ex2_2d_triangular.cdl b/lib/iris/tests/integration/ugrid/ugrid_conventions_examples/ugrid_ex2_2d_triangular.cdl similarity index 100% rename from lib/iris/tests/integration/experimental/ugrid_conventions_examples/ugrid_ex2_2d_triangular.cdl rename to lib/iris/tests/integration/ugrid/ugrid_conventions_examples/ugrid_ex2_2d_triangular.cdl diff --git a/lib/iris/tests/integration/experimental/ugrid_conventions_examples/ugrid_ex3_2d_flexible.cdl b/lib/iris/tests/integration/ugrid/ugrid_conventions_examples/ugrid_ex3_2d_flexible.cdl similarity index 100% rename from lib/iris/tests/integration/experimental/ugrid_conventions_examples/ugrid_ex3_2d_flexible.cdl rename to lib/iris/tests/integration/ugrid/ugrid_conventions_examples/ugrid_ex3_2d_flexible.cdl diff --git a/lib/iris/tests/integration/experimental/ugrid_conventions_examples/ugrid_ex4_3d_layered.cdl b/lib/iris/tests/integration/ugrid/ugrid_conventions_examples/ugrid_ex4_3d_layered.cdl similarity index 100% rename from lib/iris/tests/integration/experimental/ugrid_conventions_examples/ugrid_ex4_3d_layered.cdl rename to lib/iris/tests/integration/ugrid/ugrid_conventions_examples/ugrid_ex4_3d_layered.cdl diff --git a/lib/iris/tests/results/experimental/ugrid/2D_1t_face_half_levels.cml b/lib/iris/tests/results/ugrid/2D_1t_face_half_levels.cml similarity index 100% rename from lib/iris/tests/results/experimental/ugrid/2D_1t_face_half_levels.cml rename to lib/iris/tests/results/ugrid/2D_1t_face_half_levels.cml diff --git a/lib/iris/tests/results/experimental/ugrid/2D_72t_face_half_levels.cml b/lib/iris/tests/results/ugrid/2D_72t_face_half_levels.cml similarity index 100% rename from lib/iris/tests/results/experimental/ugrid/2D_72t_face_half_levels.cml rename to lib/iris/tests/results/ugrid/2D_72t_face_half_levels.cml diff --git a/lib/iris/tests/results/experimental/ugrid/3D_1t_face_full_levels.cml b/lib/iris/tests/results/ugrid/3D_1t_face_full_levels.cml similarity index 100% rename from lib/iris/tests/results/experimental/ugrid/3D_1t_face_full_levels.cml rename to lib/iris/tests/results/ugrid/3D_1t_face_full_levels.cml diff --git a/lib/iris/tests/results/experimental/ugrid/3D_1t_face_half_levels.cml b/lib/iris/tests/results/ugrid/3D_1t_face_half_levels.cml similarity index 100% rename from lib/iris/tests/results/experimental/ugrid/3D_1t_face_half_levels.cml rename to lib/iris/tests/results/ugrid/3D_1t_face_half_levels.cml diff --git a/lib/iris/tests/results/experimental/ugrid/3D_snow_pseudo_levels.cml b/lib/iris/tests/results/ugrid/3D_snow_pseudo_levels.cml similarity index 100% rename from lib/iris/tests/results/experimental/ugrid/3D_snow_pseudo_levels.cml rename to lib/iris/tests/results/ugrid/3D_snow_pseudo_levels.cml diff --git a/lib/iris/tests/results/experimental/ugrid/3D_soil_pseudo_levels.cml b/lib/iris/tests/results/ugrid/3D_soil_pseudo_levels.cml similarity index 100% rename from lib/iris/tests/results/experimental/ugrid/3D_soil_pseudo_levels.cml rename to lib/iris/tests/results/ugrid/3D_soil_pseudo_levels.cml diff --git a/lib/iris/tests/results/experimental/ugrid/3D_tile_pseudo_levels.cml b/lib/iris/tests/results/ugrid/3D_tile_pseudo_levels.cml similarity index 100% rename from lib/iris/tests/results/experimental/ugrid/3D_tile_pseudo_levels.cml rename to lib/iris/tests/results/ugrid/3D_tile_pseudo_levels.cml diff --git a/lib/iris/tests/results/experimental/ugrid/3D_veg_pseudo_levels.cml b/lib/iris/tests/results/ugrid/3D_veg_pseudo_levels.cml similarity index 100% rename from lib/iris/tests/results/experimental/ugrid/3D_veg_pseudo_levels.cml rename to lib/iris/tests/results/ugrid/3D_veg_pseudo_levels.cml diff --git a/lib/iris/tests/results/experimental/ugrid/surface_mean.cml b/lib/iris/tests/results/ugrid/surface_mean.cml similarity index 100% rename from lib/iris/tests/results/experimental/ugrid/surface_mean.cml rename to lib/iris/tests/results/ugrid/surface_mean.cml diff --git a/lib/iris/tests/stock/__init__.py b/lib/iris/tests/stock/__init__.py index e6ef0356a6..f664ce012b 100644 --- a/lib/iris/tests/stock/__init__.py +++ b/lib/iris/tests/stock/__init__.py @@ -15,6 +15,7 @@ import numpy as np import numpy.ma as ma +from iris import ugrid from iris.analysis import cartography import iris.aux_factory from iris.coord_systems import GeogCS, RotatedGeogCS @@ -22,7 +23,6 @@ import iris.coords as icoords from iris.coords import AncillaryVariable, AuxCoord, CellMeasure, CellMethod, DimCoord from iris.cube import Cube -from iris.experimental import ugrid from iris.util import mask_cube from ._stock_2d_latlons import ( # noqa diff --git a/lib/iris/tests/stock/mesh.py b/lib/iris/tests/stock/mesh.py index 6333374d6c..22dcff18ce 100644 --- a/lib/iris/tests/stock/mesh.py +++ b/lib/iris/tests/stock/mesh.py @@ -8,7 +8,7 @@ from iris.coords import AuxCoord, DimCoord from iris.cube import Cube -from iris.experimental.ugrid.mesh import Connectivity, MeshCoord, MeshXY +from iris.ugrid.mesh import Connectivity, MeshCoord, MeshXY # Default creation controls for creating a test MeshXY. # Note: we're not creating any kind of sensible 'normal' mesh here, the numbers diff --git a/lib/iris/tests/unit/common/metadata/test_metadata_manager_factory.py b/lib/iris/tests/unit/common/metadata/test_metadata_manager_factory.py index e9ec42e04b..49449ad63b 100644 --- a/lib/iris/tests/unit/common/metadata/test_metadata_manager_factory.py +++ b/lib/iris/tests/unit/common/metadata/test_metadata_manager_factory.py @@ -21,7 +21,7 @@ CubeMetadata, metadata_manager_factory, ) -from iris.experimental.ugrid.metadata import ConnectivityMetadata +from iris.ugrid.metadata import ConnectivityMetadata BASES = [ AncillaryVariableMetadata, diff --git a/lib/iris/tests/unit/common/mixin/test_CFVariableMixin.py b/lib/iris/tests/unit/common/mixin/test_CFVariableMixin.py index 020f18a358..57d56ecfe7 100644 --- a/lib/iris/tests/unit/common/mixin/test_CFVariableMixin.py +++ b/lib/iris/tests/unit/common/mixin/test_CFVariableMixin.py @@ -21,7 +21,7 @@ CubeMetadata, ) from iris.common.mixin import CFVariableMixin, LimitedAttributeDict -from iris.experimental.ugrid.metadata import ConnectivityMetadata +from iris.ugrid.metadata import ConnectivityMetadata class Test__getter(tests.IrisTest): diff --git a/lib/iris/tests/unit/coords/test__DimensionalMetadata.py b/lib/iris/tests/unit/coords/test__DimensionalMetadata.py index 6aaa26e5a9..60d4459ff3 100644 --- a/lib/iris/tests/unit/coords/test__DimensionalMetadata.py +++ b/lib/iris/tests/unit/coords/test__DimensionalMetadata.py @@ -21,9 +21,9 @@ DimCoord, _DimensionalMetadata, ) -from iris.experimental.ugrid.mesh import Connectivity from iris.tests.stock import climatology_3d as cube_with_climatology from iris.tests.stock.mesh import sample_meshcoord +from iris.ugrid.mesh import Connectivity class Test___init____abstractmethod(tests.IrisTest): diff --git a/lib/iris/tests/unit/experimental/ugrid/metadata/__init__.py b/lib/iris/tests/unit/experimental/ugrid/metadata/__init__.py deleted file mode 100644 index a8ad2bc014..0000000000 --- a/lib/iris/tests/unit/experimental/ugrid/metadata/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the BSD license. -# See LICENSE in the root of the repository for full licensing details. -"""Unit tests for the :mod:`iris.experimental.ugrid.metadata` package.""" diff --git a/lib/iris/tests/unit/experimental/ugrid/utils/__init__.py b/lib/iris/tests/unit/experimental/ugrid/utils/__init__.py deleted file mode 100644 index ea8202f8fb..0000000000 --- a/lib/iris/tests/unit/experimental/ugrid/utils/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the BSD license. -# See LICENSE in the root of the repository for full licensing details. -"""Unit tests for the :mod:`iris.experimental.ugrid.utils` package.""" diff --git a/lib/iris/tests/unit/fileformats/netcdf/loader/test_load_cubes.py b/lib/iris/tests/unit/fileformats/netcdf/loader/test_load_cubes.py index 571a749bf0..c90f710110 100644 --- a/lib/iris/tests/unit/fileformats/netcdf/loader/test_load_cubes.py +++ b/lib/iris/tests/unit/fileformats/netcdf/loader/test_load_cubes.py @@ -21,10 +21,10 @@ import numpy as np from iris.coords import AncillaryVariable, CellMeasure -from iris.experimental.ugrid.mesh import MeshCoord from iris.fileformats.netcdf import logger from iris.fileformats.netcdf.loader import load_cubes from iris.tests.stock.netcdf import ncgen_from_cdl +from iris.ugrid.mesh import MeshCoord def setUpModule(): diff --git a/lib/iris/tests/unit/fileformats/netcdf/saver/test_Saver__ugrid.py b/lib/iris/tests/unit/fileformats/netcdf/saver/test_Saver__ugrid.py index e86ecf9a52..c8b422c7b3 100644 --- a/lib/iris/tests/unit/fileformats/netcdf/saver/test_Saver__ugrid.py +++ b/lib/iris/tests/unit/fileformats/netcdf/saver/test_Saver__ugrid.py @@ -22,10 +22,10 @@ from iris import save from iris.coords import AuxCoord from iris.cube import Cube, CubeList -from iris.experimental.ugrid.mesh import Connectivity, MeshXY -from iris.experimental.ugrid.save import save_mesh from iris.fileformats.netcdf import _thread_safe_nc from iris.tests.stock import realistic_4d +from iris.ugrid.mesh import Connectivity, MeshXY +from iris.ugrid.save import save_mesh XY_LOCS = ("x", "y") XY_NAMES = ("longitude", "latitude") @@ -196,7 +196,7 @@ def make_cube(mesh=None, location="face", **kwargs): Parameters ---------- - mesh : :class:`iris.experimental.ugrid.mesh.MeshXY` or None, optional + mesh : :class:`iris.ugrid.mesh.MeshXY` or None, optional If None, use 'default_mesh()' location : str, optional, default="face" Which mesh element to map the cube to. diff --git a/lib/iris/tests/unit/tests/stock/test_netcdf.py b/lib/iris/tests/unit/tests/stock/test_netcdf.py index c218385425..521f03e053 100644 --- a/lib/iris/tests/unit/tests/stock/test_netcdf.py +++ b/lib/iris/tests/unit/tests/stock/test_netcdf.py @@ -8,12 +8,12 @@ import tempfile from iris import load_cube -from iris.experimental.ugrid.mesh import MeshCoord, MeshXY # Import iris.tests first so that some things can be initialised before # importing anything else. -import iris.tests as tests +import iris.tests as tests # isort:skip from iris.tests.stock import netcdf +from iris.ugrid.mesh import MeshCoord, MeshXY class XIOSFileMixin(tests.IrisTest): diff --git a/lib/iris/tests/unit/experimental/ugrid/__init__.py b/lib/iris/tests/unit/ugrid/__init__.py similarity index 72% rename from lib/iris/tests/unit/experimental/ugrid/__init__.py rename to lib/iris/tests/unit/ugrid/__init__.py index 27d7921e5f..d80eae287c 100644 --- a/lib/iris/tests/unit/experimental/ugrid/__init__.py +++ b/lib/iris/tests/unit/ugrid/__init__.py @@ -2,4 +2,4 @@ # # This file is part of Iris and is released under the BSD license. # See LICENSE in the root of the repository for full licensing details. -"""Unit tests for the :mod:`iris.experimental.ugrid` package.""" +"""Unit tests for the :mod:`iris.ugrid` package.""" diff --git a/lib/iris/tests/unit/experimental/ugrid/cf/__init__.py b/lib/iris/tests/unit/ugrid/cf/__init__.py similarity index 71% rename from lib/iris/tests/unit/experimental/ugrid/cf/__init__.py rename to lib/iris/tests/unit/ugrid/cf/__init__.py index 19507555c7..dc57b9d980 100644 --- a/lib/iris/tests/unit/experimental/ugrid/cf/__init__.py +++ b/lib/iris/tests/unit/ugrid/cf/__init__.py @@ -2,4 +2,4 @@ # # This file is part of Iris and is released under the BSD license. # See LICENSE in the root of the repository for full licensing details. -"""Unit tests for the :mod:`iris.experimental.ugrid.cf` package.""" +"""Unit tests for the :mod:`iris.ugrid.cf` package.""" diff --git a/lib/iris/tests/unit/experimental/ugrid/cf/test_CFUGridAuxiliaryCoordinateVariable.py b/lib/iris/tests/unit/ugrid/cf/test_CFUGridAuxiliaryCoordinateVariable.py similarity index 96% rename from lib/iris/tests/unit/experimental/ugrid/cf/test_CFUGridAuxiliaryCoordinateVariable.py rename to lib/iris/tests/unit/ugrid/cf/test_CFUGridAuxiliaryCoordinateVariable.py index f283dd22db..abd4442d3b 100644 --- a/lib/iris/tests/unit/experimental/ugrid/cf/test_CFUGridAuxiliaryCoordinateVariable.py +++ b/lib/iris/tests/unit/ugrid/cf/test_CFUGridAuxiliaryCoordinateVariable.py @@ -2,10 +2,11 @@ # # This file is part of Iris and is released under the BSD license. # See LICENSE in the root of the repository for full licensing details. -"""Unit tests for the :class:`iris.experimental.ugrid.cf.CFUGridAuxiliaryCoordinateVariable` class. +"""Unit tests for the :class:`iris.ugrid.cf.CFUGridAuxiliaryCoordinateVariable` class. -todo: fold these tests into cf tests when experimental.ugrid is folded into +todo: fold these tests into cf tests when iris.ugrid is folded into standard behaviour. +TODO: complete iris.ugrid replacement """ @@ -19,10 +20,10 @@ import numpy as np import pytest -from iris.experimental.ugrid.cf import CFUGridAuxiliaryCoordinateVariable -from iris.tests.unit.experimental.ugrid.cf.test_CFUGridReader import ( +from iris.tests.unit.ugrid.cf.test_CFUGridReader import ( netcdf_ugrid_variable, ) +from iris.ugrid.cf import CFUGridAuxiliaryCoordinateVariable import iris.warnings diff --git a/lib/iris/tests/unit/experimental/ugrid/cf/test_CFUGridConnectivityVariable.py b/lib/iris/tests/unit/ugrid/cf/test_CFUGridConnectivityVariable.py similarity index 95% rename from lib/iris/tests/unit/experimental/ugrid/cf/test_CFUGridConnectivityVariable.py rename to lib/iris/tests/unit/ugrid/cf/test_CFUGridConnectivityVariable.py index d412b8838a..743410a849 100644 --- a/lib/iris/tests/unit/experimental/ugrid/cf/test_CFUGridConnectivityVariable.py +++ b/lib/iris/tests/unit/ugrid/cf/test_CFUGridConnectivityVariable.py @@ -2,10 +2,11 @@ # # This file is part of Iris and is released under the BSD license. # See LICENSE in the root of the repository for full licensing details. -"""Unit tests for the :class:`iris.experimental.ugrid.cf.CFUGridConnectivityVariable` class. +"""Unit tests for the :class:`iris.ugrid.cf.CFUGridConnectivityVariable` class. -todo: fold these tests into cf tests when experimental.ugrid is folded into +todo: fold these tests into cf tests when iris.ugrid is folded into standard behaviour. +TODO: complete iris.ugrid replacement """ @@ -19,11 +20,11 @@ import numpy as np import pytest -from iris.experimental.ugrid.cf import CFUGridConnectivityVariable -from iris.experimental.ugrid.mesh import Connectivity -from iris.tests.unit.experimental.ugrid.cf.test_CFUGridReader import ( +from iris.tests.unit.ugrid.cf.test_CFUGridReader import ( netcdf_ugrid_variable, ) +from iris.ugrid.cf import CFUGridConnectivityVariable +from iris.ugrid.mesh import Connectivity import iris.warnings diff --git a/lib/iris/tests/unit/experimental/ugrid/cf/test_CFUGridGroup.py b/lib/iris/tests/unit/ugrid/cf/test_CFUGridGroup.py similarity index 93% rename from lib/iris/tests/unit/experimental/ugrid/cf/test_CFUGridGroup.py rename to lib/iris/tests/unit/ugrid/cf/test_CFUGridGroup.py index 6db067fe25..0226ec76e2 100644 --- a/lib/iris/tests/unit/experimental/ugrid/cf/test_CFUGridGroup.py +++ b/lib/iris/tests/unit/ugrid/cf/test_CFUGridGroup.py @@ -2,10 +2,11 @@ # # This file is part of Iris and is released under the BSD license. # See LICENSE in the root of the repository for full licensing details. -"""Unit tests for the :class:`iris.experimental.ugrid.cf.CFUGridGroup` class. +"""Unit tests for the :class:`iris.ugrid.cf.CFUGridGroup` class. -todo: fold these tests into cf tests when experimental.ugrid is folded into +todo: fold these tests into cf tests when iris.ugrid is folded into standard behaviour. +TODO: complete iris.ugrid replacement """ @@ -15,13 +16,13 @@ from unittest.mock import MagicMock -from iris.experimental.ugrid.cf import ( +from iris.fileformats.cf import CFCoordinateVariable, CFDataVariable +from iris.ugrid.cf import ( CFUGridAuxiliaryCoordinateVariable, CFUGridConnectivityVariable, CFUGridGroup, CFUGridMeshVariable, ) -from iris.fileformats.cf import CFCoordinateVariable, CFDataVariable class Tests(tests.IrisTest): diff --git a/lib/iris/tests/unit/experimental/ugrid/cf/test_CFUGridMeshVariable.py b/lib/iris/tests/unit/ugrid/cf/test_CFUGridMeshVariable.py similarity index 97% rename from lib/iris/tests/unit/experimental/ugrid/cf/test_CFUGridMeshVariable.py rename to lib/iris/tests/unit/ugrid/cf/test_CFUGridMeshVariable.py index 32c96cacbc..f93c1e89a1 100644 --- a/lib/iris/tests/unit/experimental/ugrid/cf/test_CFUGridMeshVariable.py +++ b/lib/iris/tests/unit/ugrid/cf/test_CFUGridMeshVariable.py @@ -2,10 +2,11 @@ # # This file is part of Iris and is released under the BSD license. # See LICENSE in the root of the repository for full licensing details. -"""Unit tests for the :class:`iris.experimental.ugrid.cf.CFUGridMeshVariable` class. +"""Unit tests for the :class:`iris.ugrid.cf.CFUGridMeshVariable` class. -todo: fold these tests into cf tests when experimental.ugrid is folded into +todo: fold these tests into cf tests when ugrid is folded into standard behaviour. +TODO: complete iris.ugrid replacement """ @@ -19,10 +20,10 @@ import numpy as np import pytest -from iris.experimental.ugrid.cf import CFUGridMeshVariable -from iris.tests.unit.experimental.ugrid.cf.test_CFUGridReader import ( +from iris.tests.unit.ugrid.cf.test_CFUGridReader import ( netcdf_ugrid_variable, ) +from iris.ugrid.cf import CFUGridMeshVariable import iris.warnings diff --git a/lib/iris/tests/unit/experimental/ugrid/cf/test_CFUGridReader.py b/lib/iris/tests/unit/ugrid/cf/test_CFUGridReader.py similarity index 94% rename from lib/iris/tests/unit/experimental/ugrid/cf/test_CFUGridReader.py rename to lib/iris/tests/unit/ugrid/cf/test_CFUGridReader.py index 14278d3dff..5f36958e9a 100644 --- a/lib/iris/tests/unit/experimental/ugrid/cf/test_CFUGridReader.py +++ b/lib/iris/tests/unit/ugrid/cf/test_CFUGridReader.py @@ -2,10 +2,11 @@ # # This file is part of Iris and is released under the BSD license. # See LICENSE in the root of the repository for full licensing details. -"""Unit tests for the :class:`iris.experimental.ugrid.cf.CFUGridGroup` class. +"""Unit tests for the :class:`iris.ugrid.cf.CFUGridGroup` class. -todo: fold these tests into cf tests when experimental.ugrid is folded into +todo: fold these tests into cf tests when iris.ugrid is folded into standard behaviour. +TODO: complete iris.ugrid replacement """ @@ -15,15 +16,15 @@ from unittest import mock -from iris.experimental.ugrid.cf import ( +from iris.fileformats.cf import CFCoordinateVariable, CFDataVariable +from iris.tests.unit.fileformats.cf.test_CFReader import netcdf_variable +from iris.ugrid.cf import ( CFUGridAuxiliaryCoordinateVariable, CFUGridConnectivityVariable, CFUGridGroup, CFUGridMeshVariable, CFUGridReader, ) -from iris.fileformats.cf import CFCoordinateVariable, CFDataVariable -from iris.tests.unit.fileformats.cf.test_CFReader import netcdf_variable def netcdf_ugrid_variable( @@ -90,7 +91,7 @@ def setUpClass(cls): def setUp(self): # Restrict the CFUGridReader functionality to only performing # translations and building first level cf-groups for variables. - self.patch("iris.experimental.ugrid.cf.CFUGridReader._reset") + self.patch("iris.ugrid.cf.CFUGridReader._reset") self.patch( "iris.fileformats.netcdf._thread_safe_nc.DatasetWrapper", return_value=self.dataset, diff --git a/lib/iris/tests/unit/experimental/ugrid/mesh/__init__.py b/lib/iris/tests/unit/ugrid/load/__init__.py similarity index 70% rename from lib/iris/tests/unit/experimental/ugrid/mesh/__init__.py rename to lib/iris/tests/unit/ugrid/load/__init__.py index d485782c11..b3552cc441 100644 --- a/lib/iris/tests/unit/experimental/ugrid/mesh/__init__.py +++ b/lib/iris/tests/unit/ugrid/load/__init__.py @@ -2,4 +2,4 @@ # # This file is part of Iris and is released under the BSD license. # See LICENSE in the root of the repository for full licensing details. -"""Unit tests for the :mod:`iris.experimental.ugrid.mesh` package.""" +"""Unit tests for the :mod:`iris.ugrid.load` package.""" diff --git a/lib/iris/tests/unit/experimental/ugrid/load/test_ParseUgridOnLoad.py b/lib/iris/tests/unit/ugrid/load/test_ParseUgridOnLoad.py similarity index 80% rename from lib/iris/tests/unit/experimental/ugrid/load/test_ParseUgridOnLoad.py rename to lib/iris/tests/unit/ugrid/load/test_ParseUgridOnLoad.py index 0c78fa5880..e47dfe8d50 100644 --- a/lib/iris/tests/unit/experimental/ugrid/load/test_ParseUgridOnLoad.py +++ b/lib/iris/tests/unit/ugrid/load/test_ParseUgridOnLoad.py @@ -2,7 +2,7 @@ # # This file is part of Iris and is released under the BSD license. # See LICENSE in the root of the repository for full licensing details. -"""Unit tests for the :class:`iris.experimental.ugrid.load.ParseUgridOnLoad` class. +"""Unit tests for the :class:`iris.ugrid.load.ParseUgridOnLoad` class. TODO: remove this module when ParseUGridOnLoad itself is removed. @@ -11,7 +11,7 @@ import pytest from iris._deprecation import IrisDeprecation -from iris.experimental.ugrid.load import PARSE_UGRID_ON_LOAD, ParseUGridOnLoad +from iris.ugrid.load import PARSE_UGRID_ON_LOAD, ParseUGridOnLoad def test_creation(): diff --git a/lib/iris/tests/unit/experimental/ugrid/load/test_load_mesh.py b/lib/iris/tests/unit/ugrid/load/test_load_mesh.py similarity index 86% rename from lib/iris/tests/unit/experimental/ugrid/load/test_load_mesh.py rename to lib/iris/tests/unit/ugrid/load/test_load_mesh.py index 6d1bbe995c..5f2308c68e 100644 --- a/lib/iris/tests/unit/experimental/ugrid/load/test_load_mesh.py +++ b/lib/iris/tests/unit/ugrid/load/test_load_mesh.py @@ -2,20 +2,20 @@ # # This file is part of Iris and is released under the BSD license. # See LICENSE in the root of the repository for full licensing details. -"""Unit tests for the :func:`iris.experimental.ugrid.load.load_mesh` function.""" +"""Unit tests for the :func:`iris.ugrid.load.load_mesh` function.""" # Import iris.tests first so that some things can be initialised before # importing anything else. import iris.tests as tests # isort:skip -from iris.experimental.ugrid.load import load_mesh +from iris.ugrid.load import load_mesh class Tests(tests.IrisTest): # All 'real' tests have been done for load_meshes(). Here we just check # that load_mesh() works with load_meshes() correctly, using mocking. def setUp(self): - self.load_meshes_mock = self.patch("iris.experimental.ugrid.load.load_meshes") + self.load_meshes_mock = self.patch("iris.ugrid.load.load_meshes") # The expected return from load_meshes - a dict of files, each with # a list of meshes. self.load_meshes_mock.return_value = {"file": ["mesh"]} diff --git a/lib/iris/tests/unit/experimental/ugrid/load/test_load_meshes.py b/lib/iris/tests/unit/ugrid/load/test_load_meshes.py similarity index 97% rename from lib/iris/tests/unit/experimental/ugrid/load/test_load_meshes.py rename to lib/iris/tests/unit/ugrid/load/test_load_meshes.py index da7cf9b649..3847514a02 100644 --- a/lib/iris/tests/unit/experimental/ugrid/load/test_load_meshes.py +++ b/lib/iris/tests/unit/ugrid/load/test_load_meshes.py @@ -2,7 +2,7 @@ # # This file is part of Iris and is released under the BSD license. # See LICENSE in the root of the repository for full licensing details. -"""Unit tests for the :func:`iris.experimental.ugrid.load.load_meshes` function.""" +"""Unit tests for the :func:`iris.ugrid.load.load_meshes` function.""" # Import iris.tests first so that some things can be initialised before # importing anything else. @@ -13,8 +13,8 @@ import tempfile from uuid import uuid4 -from iris.experimental.ugrid.load import load_meshes, logger from iris.tests.stock.netcdf import ncgen_from_cdl +from iris.ugrid.load import load_meshes, logger def setUpModule(): diff --git a/lib/iris/tests/unit/experimental/ugrid/load/test_meshload_checks.py b/lib/iris/tests/unit/ugrid/load/test_meshload_checks.py similarity index 100% rename from lib/iris/tests/unit/experimental/ugrid/load/test_meshload_checks.py rename to lib/iris/tests/unit/ugrid/load/test_meshload_checks.py diff --git a/lib/iris/tests/unit/experimental/ugrid/load/__init__.py b/lib/iris/tests/unit/ugrid/mesh/__init__.py similarity index 70% rename from lib/iris/tests/unit/experimental/ugrid/load/__init__.py rename to lib/iris/tests/unit/ugrid/mesh/__init__.py index 3248db6e41..f41d747a68 100644 --- a/lib/iris/tests/unit/experimental/ugrid/load/__init__.py +++ b/lib/iris/tests/unit/ugrid/mesh/__init__.py @@ -2,4 +2,4 @@ # # This file is part of Iris and is released under the BSD license. # See LICENSE in the root of the repository for full licensing details. -"""Unit tests for the :mod:`iris.experimental.ugrid.load` package.""" +"""Unit tests for the :mod:`iris.ugrid.mesh` package.""" diff --git a/lib/iris/tests/unit/experimental/ugrid/mesh/test_Connectivity.py b/lib/iris/tests/unit/ugrid/mesh/test_Connectivity.py similarity index 98% rename from lib/iris/tests/unit/experimental/ugrid/mesh/test_Connectivity.py rename to lib/iris/tests/unit/ugrid/mesh/test_Connectivity.py index b84b32cf41..507176a943 100644 --- a/lib/iris/tests/unit/experimental/ugrid/mesh/test_Connectivity.py +++ b/lib/iris/tests/unit/ugrid/mesh/test_Connectivity.py @@ -2,7 +2,7 @@ # # This file is part of Iris and is released under the BSD license. # See LICENSE in the root of the repository for full licensing details. -"""Unit tests for the :class:`iris.experimental.ugrid.mesh.Connectivity` class.""" +"""Unit tests for the :class:`iris.ugrid.mesh.Connectivity` class.""" # Import iris.tests first so that some things can be initialised before # importing anything else. @@ -16,7 +16,7 @@ from packaging import version from iris._lazy_data import as_lazy_data, is_lazy_data -from iris.experimental.ugrid.mesh import Connectivity +from iris.ugrid.mesh import Connectivity class TestStandard(tests.IrisTest): diff --git a/lib/iris/tests/unit/experimental/ugrid/mesh/test_Mesh.py b/lib/iris/tests/unit/ugrid/mesh/test_Mesh.py similarity index 99% rename from lib/iris/tests/unit/experimental/ugrid/mesh/test_Mesh.py rename to lib/iris/tests/unit/ugrid/mesh/test_Mesh.py index a47d093af0..4b26c7b064 100644 --- a/lib/iris/tests/unit/experimental/ugrid/mesh/test_Mesh.py +++ b/lib/iris/tests/unit/ugrid/mesh/test_Mesh.py @@ -12,8 +12,8 @@ from iris.coords import AuxCoord from iris.exceptions import ConnectivityNotFoundError, CoordinateNotFoundError -from iris.experimental.ugrid import mesh, metadata -from iris.experimental.ugrid.mesh import logger +from iris.ugrid import mesh, metadata +from iris.ugrid.mesh import logger class TestMeshCommon(tests.IrisTest): diff --git a/lib/iris/tests/unit/experimental/ugrid/mesh/test_MeshCoord.py b/lib/iris/tests/unit/ugrid/mesh/test_MeshCoord.py similarity index 99% rename from lib/iris/tests/unit/experimental/ugrid/mesh/test_MeshCoord.py rename to lib/iris/tests/unit/ugrid/mesh/test_MeshCoord.py index 3f81085fa9..38d48b8be8 100644 --- a/lib/iris/tests/unit/experimental/ugrid/mesh/test_MeshCoord.py +++ b/lib/iris/tests/unit/ugrid/mesh/test_MeshCoord.py @@ -2,7 +2,7 @@ # # This file is part of Iris and is released under the BSD license. # See LICENSE in the root of the repository for full licensing details. -"""Unit tests for the :class:`iris.experimental.ugrid.mesh.MeshCoord`.""" +"""Unit tests for the :class:`iris.ugrid.mesh.MeshCoord`.""" # Import iris.tests first so that some things can be initialised before # importing anything else. @@ -21,9 +21,9 @@ from iris.common.metadata import BaseMetadata, CoordMetadata from iris.coords import AuxCoord, Coord from iris.cube import Cube -from iris.experimental.ugrid.mesh import Connectivity, MeshCoord, MeshXY import iris.tests.stock.mesh from iris.tests.stock.mesh import sample_mesh, sample_meshcoord +from iris.ugrid.mesh import Connectivity, MeshCoord, MeshXY from iris.warnings import IrisVagueMetadataWarning diff --git a/lib/iris/tests/unit/experimental/ugrid/mesh/test_Mesh__from_coords.py b/lib/iris/tests/unit/ugrid/mesh/test_Mesh__from_coords.py similarity index 97% rename from lib/iris/tests/unit/experimental/ugrid/mesh/test_Mesh__from_coords.py rename to lib/iris/tests/unit/ugrid/mesh/test_Mesh__from_coords.py index 8ea9c81060..3043ae81b5 100644 --- a/lib/iris/tests/unit/experimental/ugrid/mesh/test_Mesh__from_coords.py +++ b/lib/iris/tests/unit/ugrid/mesh/test_Mesh__from_coords.py @@ -2,7 +2,7 @@ # # This file is part of Iris and is released under the BSD license. # See LICENSE in the root of the repository for full licensing details. -"""Unit tests for the :meth:`iris.experimental.ugrid.mesh.MeshXY.from_coords`.""" +"""Unit tests for the :meth:`iris.ugrid.mesh.MeshXY.from_coords`.""" # Import iris.tests first so that some things can be initialised before # importing anything else. @@ -11,9 +11,9 @@ import numpy as np from iris.coords import AuxCoord, DimCoord -from iris.experimental.ugrid import logger -from iris.experimental.ugrid.mesh import Connectivity, MeshXY from iris.tests.stock import simple_2d_w_multidim_coords +from iris.ugrid import logger +from iris.ugrid.mesh import Connectivity, MeshXY class Test1Dim(tests.IrisTest): diff --git a/lib/iris/tests/unit/ugrid/metadata/__init__.py b/lib/iris/tests/unit/ugrid/metadata/__init__.py new file mode 100644 index 0000000000..e1ca32ec43 --- /dev/null +++ b/lib/iris/tests/unit/ugrid/metadata/__init__.py @@ -0,0 +1,7 @@ +# Copyright Iris contributors +# +# This file is part of Iris and is released under the BSD license. +# See LICENSE in the root of the repository for full licensing details. +"""Unit tests for the :mod:`iris.ugrid.metadata` package.""" + +from __future__ import annotations diff --git a/lib/iris/tests/unit/experimental/ugrid/metadata/test_ConnectivityMetadata.py b/lib/iris/tests/unit/ugrid/metadata/test_ConnectivityMetadata.py similarity index 99% rename from lib/iris/tests/unit/experimental/ugrid/metadata/test_ConnectivityMetadata.py rename to lib/iris/tests/unit/ugrid/metadata/test_ConnectivityMetadata.py index 91637ad20b..e53a3d7002 100644 --- a/lib/iris/tests/unit/experimental/ugrid/metadata/test_ConnectivityMetadata.py +++ b/lib/iris/tests/unit/ugrid/metadata/test_ConnectivityMetadata.py @@ -2,7 +2,7 @@ # # This file is part of Iris and is released under the BSD license. # See LICENSE in the root of the repository for full licensing details. -"""Unit tests for the :class:`iris.experimental.ugrid.metadata.ConnectivityMetadata`.""" +"""Unit tests for the :class:`iris.ugrid.metadata.ConnectivityMetadata`.""" # Import iris.tests first so that some things can be initialised before # importing anything else. @@ -14,7 +14,7 @@ from iris.common.lenient import _LENIENT, _qualname from iris.common.metadata import BaseMetadata -from iris.experimental.ugrid.metadata import ConnectivityMetadata +from iris.ugrid.metadata import ConnectivityMetadata class Test(tests.IrisTest): diff --git a/lib/iris/tests/unit/experimental/ugrid/metadata/test_MeshCoordMetadata.py b/lib/iris/tests/unit/ugrid/metadata/test_MeshCoordMetadata.py similarity index 99% rename from lib/iris/tests/unit/experimental/ugrid/metadata/test_MeshCoordMetadata.py rename to lib/iris/tests/unit/ugrid/metadata/test_MeshCoordMetadata.py index 0434149674..403425fb56 100644 --- a/lib/iris/tests/unit/experimental/ugrid/metadata/test_MeshCoordMetadata.py +++ b/lib/iris/tests/unit/ugrid/metadata/test_MeshCoordMetadata.py @@ -2,7 +2,7 @@ # # This file is part of Iris and is released under the BSD license. # See LICENSE in the root of the repository for full licensing details. -"""Unit tests for the :class:`iris.experimental.ugrid.metadata.MeshCoordMetadata`.""" +"""Unit tests for the :class:`iris.ugrid.metadata.MeshCoordMetadata`.""" # Import iris.tests first so that some things can be initialised before # importing anything else. @@ -14,7 +14,7 @@ from iris.common.lenient import _LENIENT, _qualname from iris.common.metadata import BaseMetadata -from iris.experimental.ugrid.metadata import MeshCoordMetadata +from iris.ugrid.metadata import MeshCoordMetadata class Test__identity(tests.IrisTest): diff --git a/lib/iris/tests/unit/experimental/ugrid/metadata/test_MeshMetadata.py b/lib/iris/tests/unit/ugrid/metadata/test_MeshMetadata.py similarity index 99% rename from lib/iris/tests/unit/experimental/ugrid/metadata/test_MeshMetadata.py rename to lib/iris/tests/unit/ugrid/metadata/test_MeshMetadata.py index abbb4c0304..e96a2441c3 100644 --- a/lib/iris/tests/unit/experimental/ugrid/metadata/test_MeshMetadata.py +++ b/lib/iris/tests/unit/ugrid/metadata/test_MeshMetadata.py @@ -2,7 +2,7 @@ # # This file is part of Iris and is released under the BSD license. # See LICENSE in the root of the repository for full licensing details. -"""Unit tests for the :class:`iris.experimental.ugrid.metadata.MeshMetadata`.""" +"""Unit tests for the :class:`iris.ugrid.metadata.MeshMetadata`.""" # Import iris.tests first so that some things can be initialised before # importing anything else. @@ -14,7 +14,7 @@ from iris.common.lenient import _LENIENT, _qualname from iris.common.metadata import BaseMetadata -from iris.experimental.ugrid.metadata import MeshMetadata +from iris.ugrid.metadata import MeshMetadata class Test(tests.IrisTest): diff --git a/lib/iris/tests/unit/ugrid/utils/__init__.py b/lib/iris/tests/unit/ugrid/utils/__init__.py new file mode 100644 index 0000000000..7bc5f68717 --- /dev/null +++ b/lib/iris/tests/unit/ugrid/utils/__init__.py @@ -0,0 +1,7 @@ +# Copyright Iris contributors +# +# This file is part of Iris and is released under the BSD license. +# See LICENSE in the root of the repository for full licensing details. +"""Unit tests for the :mod:`iris.ugrid.utils` package.""" + +from __future__ import annotations diff --git a/lib/iris/tests/unit/experimental/ugrid/utils/test_recombine_submeshes.py b/lib/iris/tests/unit/ugrid/utils/test_recombine_submeshes.py similarity index 99% rename from lib/iris/tests/unit/experimental/ugrid/utils/test_recombine_submeshes.py rename to lib/iris/tests/unit/ugrid/utils/test_recombine_submeshes.py index 1c0fafdfc9..2617000a0e 100644 --- a/lib/iris/tests/unit/experimental/ugrid/utils/test_recombine_submeshes.py +++ b/lib/iris/tests/unit/ugrid/utils/test_recombine_submeshes.py @@ -2,7 +2,7 @@ # # This file is part of Iris and is released under the BSD license. # See LICENSE in the root of the repository for full licensing details. -"""Unit tests for :func:`iris.experimental.ugrid.utils.recombine_submeshes`.""" +"""Unit tests for :func:`iris.ugrid.utils.recombine_submeshes`.""" # Import iris.tests first so that some things can be initialised before # importing anything else. @@ -13,8 +13,8 @@ from iris.coords import AuxCoord from iris.cube import CubeList -from iris.experimental.ugrid.utils import recombine_submeshes from iris.tests.stock.mesh import sample_mesh, sample_mesh_cube +from iris.ugrid.utils import recombine_submeshes def common_test_setup(self, shape_3d=(0, 2), data_chunks=None): diff --git a/lib/iris/experimental/ugrid/__init__.py b/lib/iris/ugrid/__init__.py similarity index 91% rename from lib/iris/experimental/ugrid/__init__.py rename to lib/iris/ugrid/__init__.py index 56c1a73411..1337724a6f 100644 --- a/lib/iris/experimental/ugrid/__init__.py +++ b/lib/iris/ugrid/__init__.py @@ -16,11 +16,11 @@ .. note:: For the docstring of :const:`PARSE_UGRID_ON_LOAD`: see the original - definition at :const:`iris.experimental.ugrid.load.PARSE_UGRID_ON_LOAD`. + definition at :const:`iris.ugrid.load.PARSE_UGRID_ON_LOAD`. """ -from ...config import get_logger +from ..config import get_logger from .load import PARSE_UGRID_ON_LOAD, load_mesh, load_meshes from .mesh import Connectivity, MeshCoord, MeshXY from .save import save_mesh diff --git a/lib/iris/experimental/ugrid/cf.py b/lib/iris/ugrid/cf.py similarity index 98% rename from lib/iris/experimental/ugrid/cf.py rename to lib/iris/ugrid/cf.py index 281bdba878..27856b5d52 100644 --- a/lib/iris/experimental/ugrid/cf.py +++ b/lib/iris/ugrid/cf.py @@ -11,8 +11,8 @@ import warnings -from ...fileformats import cf -from ...warnings import IrisCfLabelVarWarning, IrisCfMissingVarWarning +from ..fileformats import cf +from ..warnings import IrisCfLabelVarWarning, IrisCfMissingVarWarning from .mesh import Connectivity @@ -32,7 +32,7 @@ class CFUGridConnectivityVariable(cf.CFVariable): that specifies for every volume its shape. Identified by a CF-netCDF variable attribute equal to any one of the values - in :attr:`~iris.experimental.ugrid.mesh.Connectivity.UGRID_CF_ROLES`. + in :attr:`~iris.ugrid.mesh.Connectivity.UGRID_CF_ROLES`. .. seealso:: diff --git a/lib/iris/experimental/ugrid/load.py b/lib/iris/ugrid/load.py similarity index 88% rename from lib/iris/experimental/ugrid/load.py rename to lib/iris/ugrid/load.py index e6b3436185..a3f278ef23 100644 --- a/lib/iris/experimental/ugrid/load.py +++ b/lib/iris/ugrid/load.py @@ -3,10 +3,10 @@ # This file is part of Iris and is released under the BSD license. # See LICENSE in the root of the repository for full licensing details. -r"""Allow the construction of :class:`~iris.experimental.ugrid.mesh.MeshXY`. +r"""Allow the construction of :class:`~iris.ugrid.mesh.MeshXY`. Extensions to Iris' NetCDF loading to allow the construction of -:class:`~iris.experimental.ugrid.mesh.MeshXY` from UGRID data in the file. +:class:`~iris.ugrid.mesh.MeshXY` from UGRID data in the file. Eventual destination: :mod:`iris.fileformats.netcdf`. @@ -23,14 +23,14 @@ import threading import warnings -from ..._deprecation import warn_deprecated -from ...config import get_logger -from ...coords import AuxCoord -from ...fileformats._nc_load_rules.helpers import get_attr_units, get_names -from ...fileformats.netcdf import loader as nc_loader -from ...io import decode_uri, expand_filespecs -from ...util import guess_coord_axis -from ...warnings import IrisCfWarning, IrisDefaultingWarning, IrisIgnoringWarning +from .._deprecation import warn_deprecated +from ..config import get_logger +from ..coords import AuxCoord +from ..fileformats._nc_load_rules.helpers import get_attr_units, get_names +from ..fileformats.netcdf import loader as nc_loader +from ..io import decode_uri, expand_filespecs +from ..util import guess_coord_axis +from ..warnings import IrisCfWarning, IrisDefaultingWarning, IrisIgnoringWarning from .cf import ( CFUGridAuxiliaryCoordinateVariable, CFUGridConnectivityVariable, @@ -63,7 +63,7 @@ def __init__(self): version of Iris NetCDF loading. Object is thread-safe. Use via the run-time switch - :const:`~iris.experimental.ugrid.load.PARSE_UGRID_ON_LOAD`. + :const:`~iris.ugrid.load.PARSE_UGRID_ON_LOAD`. Use :meth:`context` to temporarily activate. Notes @@ -86,7 +86,7 @@ def context(self): attached to the resultant cube(s) accordingly. Use via the run-time switch - :const:`~iris.experimental.ugrid.load.PARSE_UGRID_ON_LOAD`. + :const:`~iris.ugrid.load.PARSE_UGRID_ON_LOAD`. For example:: @@ -126,7 +126,7 @@ def context(self): yield -#: Run-time switch for experimental UGRID-aware NetCDF loading. See :class:`~iris.experimental.ugrid.load.ParseUGridOnLoad`. +#: Run-time switch for experimental UGRID-aware NetCDF loading. See :class:`~iris.ugrid.load.ParseUGridOnLoad`. PARSE_UGRID_ON_LOAD = ParseUGridOnLoad() @@ -148,10 +148,10 @@ def _meshes_from_cf(cf_reader): def load_mesh(uris, var_name=None): - """Load single :class:`~iris.experimental.ugrid.mesh.MeshXY` object from 1/more NetCDF files. + """Load a single :class:`~iris.ugrid.mesh.MeshXY` object from one or more NetCDF files. Raises an error if more/less than one - :class:`~iris.experimental.ugrid.mesh.MeshXY` is found. + :class:`~iris.ugrid.mesh.MeshXY` is found. Parameters ---------- @@ -159,12 +159,12 @@ def load_mesh(uris, var_name=None): One or more filenames/URI's. Filenames can include wildcards. Any URI's must support OpenDAP. var_name : str, optional - Only return a :class:`~iris.experimental.ugrid.mesh.MeshXY` if its + Only return a :class:`~iris.ugrid.mesh.MeshXY` if its var_name matches this value. Returns ------- - :class:`iris.experimental.ugrid.mesh.MeshXY` + :class:`iris.ugrid.mesh.MeshXY` """ meshes_result = load_meshes(uris, var_name) @@ -177,7 +177,7 @@ def load_mesh(uris, var_name=None): def load_meshes(uris, var_name=None): - r"""Load :class:`~iris.experimental.ugrid.mesh.MeshXY` objects from one or more NetCDF files. + r"""Load :class:`~iris.ugrid.mesh.MeshXY` objects from one or more NetCDF files. Parameters ---------- @@ -185,7 +185,7 @@ def load_meshes(uris, var_name=None): One or more filenames/URI's. Filenames can include wildcards. Any URI's must support OpenDAP. var_name : str, optional - Only return :class:`~iris.experimental.ugrid.mesh.MeshXY` that have + Only return :class:`~iris.ugrid.mesh.MeshXY` that have var_names matching this value. Returns @@ -193,15 +193,16 @@ def load_meshes(uris, var_name=None): dict A dictionary mapping each mesh-containing file path/URL in the input ``uris`` to a list of the - :class:`~iris.experimental.ugrid.mesh.MeshXY` returned from each. + :class:`~iris.ugrid.mesh.MeshXY` returned from each. """ - # TODO: rationalise UGRID/mesh handling once experimental.ugrid is folded + # TODO: rationalise UGRID/mesh handling once iris.ugrid is folded # into standard behaviour. + # TODO: complete iris.ugrid replacement # No constraints or callbacks supported - these assume they are operating # on a Cube. - from ...fileformats import FORMAT_AGENT + from ..fileformats import FORMAT_AGENT if isinstance(uris, str): uris = [uris] @@ -258,7 +259,7 @@ def _build_aux_coord(coord_var, file_path): """Construct a :class:`~iris.coords.AuxCoord`. Construct a :class:`~iris.coords.AuxCoord` from a given - :class:`~iris.experimental.ugrid.cf.CFUGridAuxiliaryCoordinateVariable`, + :class:`~iris.ugrid.cf.CFUGridAuxiliaryCoordinateVariable`, and guess its mesh axis. todo: integrate with standard loading API post-pyke. @@ -310,10 +311,10 @@ def _build_aux_coord(coord_var, file_path): def _build_connectivity(connectivity_var, file_path, element_dims): - """Construct a :class:`~iris.experimental.ugrid.mesh.Connectivity`. + """Construct a :class:`~iris.ugrid.mesh.Connectivity`. - Construct a :class:`~iris.experimental.ugrid.mesh.Connectivity` from a - given :class:`~iris.experimental.ugrid.cf.CFUGridConnectivityVariable`, + Construct a :class:`~iris.ugrid.mesh.Connectivity` from a + given :class:`~iris.ugrid.cf.CFUGridConnectivityVariable`, and identify the name of its first dimension. todo: integrate with standard loading API post-pyke. @@ -354,10 +355,10 @@ def _build_connectivity(connectivity_var, file_path, element_dims): def _build_mesh(cf, mesh_var, file_path): - """Construct a :class:`~iris.experimental.ugrid.mesh.MeshXY`. + """Construct a :class:`~iris.ugrid.mesh.MeshXY`. - Construct a :class:`~iris.experimental.ugrid.mesh.MeshXY` from a given - :class:`~iris.experimental.ugrid.cf.CFUGridMeshVariable`. + Construct a :class:`~iris.ugrid.mesh.MeshXY` from a given + :class:`~iris.ugrid.cf.CFUGridMeshVariable`. TODO: integrate with standard loading API post-pyke. @@ -489,10 +490,10 @@ def _build_mesh(cf, mesh_var, file_path): def _build_mesh_coords(mesh, cf_var): - """Construct a tuple of :class:`~iris.experimental.ugrid.mesh.MeshCoord`. + """Construct a tuple of :class:`~iris.ugrid.mesh.MeshCoord`. - Construct a tuple of :class:`~iris.experimental.ugrid.mesh.MeshCoord` using - from a given :class:`~iris.experimental.ugrid.mesh.MeshXY` + Construct a tuple of :class:`~iris.ugrid.mesh.MeshCoord` using + from a given :class:`~iris.ugrid.mesh.MeshXY` and :class:`~iris.fileformats.cf.CFVariable`. TODO: integrate with standard loading API post-pyke. diff --git a/lib/iris/experimental/ugrid/mesh.py b/lib/iris/ugrid/mesh.py similarity index 94% rename from lib/iris/experimental/ugrid/mesh.py rename to lib/iris/ugrid/mesh.py index 398c240337..476aaed7fa 100644 --- a/lib/iris/experimental/ugrid/mesh.py +++ b/lib/iris/ugrid/mesh.py @@ -20,14 +20,14 @@ from dask import array as da import numpy as np -from ... import _lazy_data as _lazy -from ...common import CFVariableMixin, metadata_filter, metadata_manager_factory -from ...common.metadata import BaseMetadata -from ...config import get_logger -from ...coords import AuxCoord, _DimensionalMetadata -from ...exceptions import ConnectivityNotFoundError, CoordinateNotFoundError -from ...util import array_equal, clip_string, guess_coord_axis -from ...warnings import IrisVagueMetadataWarning +from .. import _lazy_data as _lazy +from ..common import CFVariableMixin, metadata_filter, metadata_manager_factory +from ..common.metadata import BaseMetadata +from ..config import get_logger +from ..coords import AuxCoord, _DimensionalMetadata +from ..exceptions import ConnectivityNotFoundError, CoordinateNotFoundError +from ..util import array_equal, clip_string, guess_coord_axis +from ..warnings import IrisVagueMetadataWarning from .metadata import ConnectivityMetadata, MeshCoordMetadata, MeshMetadata # Configure the logger. @@ -71,9 +71,9 @@ # MeshXY connectivity manager namedtuples. # -#: Namedtuple for 1D mesh :class:`~iris.experimental.ugrid.mesh.Connectivity` instances. +#: Namedtuple for 1D mesh :class:`~iris.ugrid.mesh.Connectivity` instances. Mesh1DConnectivities = namedtuple("Mesh1DConnectivities", ["edge_node"]) -#: Namedtuple for 2D mesh :class:`~iris.experimental.ugrid.mesh.Connectivity` instances. +#: Namedtuple for 2D mesh :class:`~iris.ugrid.mesh.Connectivity` instances. Mesh2DConnectivities = namedtuple( "Mesh2DConnectivities", [ @@ -785,7 +785,7 @@ def from_coords(cls, *coords): .. testsetup:: from iris import load_cube, sample_data_path - from iris.experimental.ugrid import ( + from iris.ugrid import ( MeshXY, MeshCoord, ) @@ -1134,7 +1134,7 @@ def _set_dimension_names(self, node, edge, face, reset=False): @property def all_connectivities(self): - """All the :class:`~iris.experimental.ugrid.mesh.Connectivity` instances of the :class:`MeshXY`.""" + """All the :class:`~iris.ugrid.mesh.Connectivity` instances of the :class:`MeshXY`.""" return self._connectivity_manager.all_members @property @@ -1144,10 +1144,10 @@ def all_coords(self): @property def boundary_node_connectivity(self): - """The *optional* UGRID ``boundary_node_connectivity`` :class:`~iris.experimental.ugrid.mesh.Connectivity`. + """The *optional* UGRID ``boundary_node_connectivity`` :class:`~iris.ugrid.mesh.Connectivity`. The *optional* UGRID ``boundary_node_connectivity`` - :class:`~iris.experimental.ugrid.mesh.Connectivity` of the + :class:`~iris.ugrid.mesh.Connectivity` of the :class:`MeshXY`. """ @@ -1173,10 +1173,10 @@ def edge_dimension(self, name): @property def edge_face_connectivity(self): - """The *optional* UGRID ``edge_face_connectivity`` :class:`~iris.experimental.ugrid.mesh.Connectivity`. + """The *optional* UGRID ``edge_face_connectivity`` :class:`~iris.ugrid.mesh.Connectivity`. The *optional* UGRID ``edge_face_connectivity`` - :class:`~iris.experimental.ugrid.mesh.Connectivity` of the + :class:`~iris.ugrid.mesh.Connectivity` of the :class:`MeshXY`. """ @@ -1184,10 +1184,10 @@ def edge_face_connectivity(self): @property def edge_node_connectivity(self): - """The UGRID ``edge_node_connectivity`` :class:`~iris.experimental.ugrid.mesh.Connectivity`. + """The UGRID ``edge_node_connectivity`` :class:`~iris.ugrid.mesh.Connectivity`. The UGRID ``edge_node_connectivity`` - :class:`~iris.experimental.ugrid.mesh.Connectivity` of the + :class:`~iris.ugrid.mesh.Connectivity` of the :class:`MeshXY`, which is **required** for :attr:`MeshXY.topology_dimension` of ``1``, and *optionally required* for :attr:`MeshXY.topology_dimension` ``>=2``. @@ -1224,10 +1224,10 @@ def face_dimension(self, name): @property def face_edge_connectivity(self): - """The *optional* UGRID ``face_edge_connectivity``:class:`~iris.experimental.ugrid.mesh.Connectivity`. + """The *optional* UGRID ``face_edge_connectivity``:class:`~iris.ugrid.mesh.Connectivity`. The *optional* UGRID ``face_edge_connectivity`` - :class:`~iris.experimental.ugrid.mesh.Connectivity` of the + :class:`~iris.ugrid.mesh.Connectivity` of the :class:`MeshXY`. """ @@ -1236,10 +1236,10 @@ def face_edge_connectivity(self): @property def face_face_connectivity(self): - """The *optional* UGRID ``face_face_connectivity`` :class:`~iris.experimental.ugrid.mesh.Connectivity`. + """The *optional* UGRID ``face_face_connectivity`` :class:`~iris.ugrid.mesh.Connectivity`. The *optional* UGRID ``face_face_connectivity`` - :class:`~iris.experimental.ugrid.mesh.Connectivity` of the + :class:`~iris.ugrid.mesh.Connectivity` of the :class:`MeshXY`. """ @@ -1247,10 +1247,10 @@ def face_face_connectivity(self): @property def face_node_connectivity(self): - """Return ``face_node_connectivity``:class:`~iris.experimental.ugrid.mesh.Connectivity`. + """Return ``face_node_connectivity``:class:`~iris.ugrid.mesh.Connectivity`. The UGRID ``face_node_connectivity`` - :class:`~iris.experimental.ugrid.mesh.Connectivity` of the + :class:`~iris.ugrid.mesh.Connectivity` of the :class:`MeshXY`, which is **required** for :attr:`MeshXY.topology_dimension` of ``2``, and *optionally required* for :attr:`MeshXY.topology_dimension` of ``3``. @@ -1277,13 +1277,13 @@ def node_dimension(self, name): self._metadata_manager.node_dimension = node_dimension def add_connectivities(self, *connectivities): - """Add one or more :class:`~iris.experimental.ugrid.mesh.Connectivity` instances to the :class:`MeshXY`. + """Add one or more :class:`~iris.ugrid.mesh.Connectivity` instances to the :class:`MeshXY`. Parameters ---------- *connectivities : iterable of object A collection of one or more - :class:`~iris.experimental.ugrid.mesh.Connectivity` instances to + :class:`~iris.ugrid.mesh.Connectivity` instances to add to the :class:`MeshXY`. """ @@ -1342,9 +1342,9 @@ def connectivities( contains_edge=None, contains_face=None, ): - """Return all :class:`~iris.experimental.ugrid.mesh.Connectivity`. + """Return all :class:`~iris.ugrid.mesh.Connectivity`. - Return all :class:`~iris.experimental.ugrid.mesh.Connectivity` + Return all :class:`~iris.ugrid.mesh.Connectivity` instances from the :class:`MeshXY` that match the provided criteria. Criteria can be either specific properties or other objects with @@ -1366,44 +1366,44 @@ def connectivities( * a connectivity or metadata instance equal to that of the desired objects e.g., - :class:`~iris.experimental.ugrid.mesh.Connectivity` or - :class:`~iris.experimental.ugrid.metadata.ConnectivityMetadata`. + :class:`~iris.ugrid.mesh.Connectivity` or + :class:`~iris.ugrid.metadata.ConnectivityMetadata`. standard_name : str, optional The CF standard name of the desired - :class:`~iris.experimental.ugrid.mesh.Connectivity`. If ``None``, + :class:`~iris.ugrid.mesh.Connectivity`. If ``None``, does not check for ``standard_name``. long_name : str, optional An unconstrained description of the - :class:`~iris.experimental.ugrid.mesh.Connectivity`. If ``None``, + :class:`~iris.ugrid.mesh.Connectivity`. If ``None``, does not check for ``long_name``. var_name : str, optional The NetCDF variable name of the desired - :class:`~iris.experimental.ugrid.mesh.Connectivity`. If ``None``, + :class:`~iris.ugrid.mesh.Connectivity`. If ``None``, does not check for ``var_name``. attributes : dict, optional A dictionary of attributes desired on the - :class:`~iris.experimental.ugrid.mesh.Connectivity`. If ``None``, + :class:`~iris.ugrid.mesh.Connectivity`. If ``None``, does not check for ``attributes``. cf_role : str, optional The UGRID ``cf_role`` of the desired - :class:`~iris.experimental.ugrid.mesh.Connectivity`. + :class:`~iris.ugrid.mesh.Connectivity`. contains_node : bool, optional Contains the ``node`` element as part of the - :attr:`~iris.experimental.ugrid.metadata.ConnectivityMetadata.cf_role` + :attr:`~iris.ugrid.metadata.ConnectivityMetadata.cf_role` in the list of objects to be matched. contains_edge : bool, optional Contains the ``edge`` element as part of the - :attr:`~iris.experimental.ugrid.metadata.ConnectivityMetadata.cf_role` + :attr:`~iris.ugrid.metadata.ConnectivityMetadata.cf_role` in the list of objects to be matched. contains_face : bool, optional Contains the ``face`` element as part of the - :attr:`~iris.experimental.ugrid.metadata.ConnectivityMetadata.cf_role` + :attr:`~iris.ugrid.metadata.ConnectivityMetadata.cf_role` in the list of objects to be matched. Returns ------- - list of :class:`~iris.experimental.ugrid.mesh.Connectivity` - A list of :class:`~iris.experimental.ugrid.mesh.Connectivity` + list of :class:`~iris.ugrid.mesh.Connectivity` + A list of :class:`~iris.ugrid.mesh.Connectivity` instances from the :class:`MeshXY` that matched the given criteria. """ @@ -1432,9 +1432,9 @@ def connectivity( contains_edge=None, contains_face=None, ): - """Return a single :class:`~iris.experimental.ugrid.mesh.Connectivity`. + """Return a single :class:`~iris.ugrid.mesh.Connectivity`. - Return a single :class:`~iris.experimental.ugrid.mesh.Connectivity` + Return a single :class:`~iris.ugrid.mesh.Connectivity` from the :class:`MeshXY` that matches the provided criteria. Criteria can be either specific properties or other objects with @@ -1443,7 +1443,7 @@ def connectivity( .. note:: If the given criteria do not return **precisely one** - :class:`~iris.experimental.ugrid.mesh.Connectivity`, then a + :class:`~iris.ugrid.mesh.Connectivity`, then a :class:`~iris.exceptions.ConnectivityNotFoundError` is raised. .. seealso:: @@ -1462,44 +1462,44 @@ def connectivity( * a connectivity or metadata instance equal to that of the desired object e.g., - :class:`~iris.experimental.ugrid.mesh.Connectivity` or - :class:`~iris.experimental.ugrid.metadata.ConnectivityMetadata`. + :class:`~iris.ugrid.mesh.Connectivity` or + :class:`~iris.ugrid.metadata.ConnectivityMetadata`. standard_name : str, optional The CF standard name of the desired - :class:`~iris.experimental.ugrid.mesh.Connectivity`. If ``None``, + :class:`~iris.ugrid.mesh.Connectivity`. If ``None``, does not check for ``standard_name``. long_name : str, optional An unconstrained description of the - :class:`~iris.experimental.ugrid.mesh.Connectivity`. If ``None``, + :class:`~iris.ugrid.mesh.Connectivity`. If ``None``, does not check for ``long_name``. var_name : str, optional The NetCDF variable name of the desired - :class:`~iris.experimental.ugrid.mesh.Connectivity`. If ``None``, + :class:`~iris.ugrid.mesh.Connectivity`. If ``None``, does not check for ``var_name``. attributes : dict, optional A dictionary of attributes desired on the - :class:`~iris.experimental.ugrid.mesh.Connectivity`. If ``None``, + :class:`~iris.ugrid.mesh.Connectivity`. If ``None``, does not check for ``attributes``. cf_role : str, optional The UGRID ``cf_role`` of the desired - :class:`~iris.experimental.ugrid.mesh.Connectivity`. + :class:`~iris.ugrid.mesh.Connectivity`. contains_node : bool, optional Contains the ``node`` element as part of the - :attr:`~iris.experimental.ugrid.metadata.ConnectivityMetadata.cf_role` + :attr:`~iris.ugrid.metadata.ConnectivityMetadata.cf_role` in the list of objects to be matched. contains_edge : bool, optional Contains the ``edge`` element as part of the - :attr:`~iris.experimental.ugrid.metadata.ConnectivityMetadata.cf_role` + :attr:`~iris.ugrid.metadata.ConnectivityMetadata.cf_role` in the list of objects to be matched. contains_face : bool, optional Contains the ``face`` element as part of the - :attr:`~iris.experimental.ugrid.metadata.ConnectivityMetadata.cf_role` + :attr:`~iris.ugrid.metadata.ConnectivityMetadata.cf_role` in the list of objects to be matched. Returns ------- - :class:`~iris.experimental.ugrid.mesh.Connectivity` - The :class:`~iris.experimental.ugrid.mesh.Connectivity` from the + :class:`~iris.ugrid.mesh.Connectivity` + The :class:`~iris.ugrid.mesh.Connectivity` from the :class:`MeshXY` that matched the given criteria. """ @@ -1677,9 +1677,9 @@ def remove_connectivities( contains_edge=None, contains_face=None, ): - """Remove one or more :class:`~iris.experimental.ugrid.mesh.Connectivity`. + """Remove one or more :class:`~iris.ugrid.mesh.Connectivity`. - Remove one or more :class:`~iris.experimental.ugrid.mesh.Connectivity` + Remove one or more :class:`~iris.ugrid.mesh.Connectivity` from the :class:`MeshXY` that match the provided criteria. Criteria can be either specific properties or other objects with @@ -1697,44 +1697,44 @@ def remove_connectivities( * a connectivity or metadata instance equal to that of the desired objects e.g., - :class:`~iris.experimental.ugrid.mesh.Connectivity` or - :class:`~iris.experimental.ugrid.metadata.ConnectivityMetadata`. + :class:`~iris.ugrid.mesh.Connectivity` or + :class:`~iris.ugrid.metadata.ConnectivityMetadata`. standard_name : str, optional The CF standard name of the desired - :class:`~iris.experimental.ugrid.mesh.Connectivity`. If ``None``, + :class:`~iris.ugrid.mesh.Connectivity`. If ``None``, does not check for ``standard_name``. long_name : str, optional An unconstrained description of the - :class:`~iris.experimental.ugrid.mesh.Connectivity`. If ``None``, + :class:`~iris.ugrid.mesh.Connectivity`. If ``None``, does not check for ``long_name``. var_name : str, optional The NetCDF variable name of the desired - :class:`~iris.experimental.ugrid.mesh.Connectivity`. If ``None``, + :class:`~iris.ugrid.mesh.Connectivity`. If ``None``, does not check for ``var_name``. attributes : dict, optional A dictionary of attributes desired on the - :class:`~iris.experimental.ugrid.mesh.Connectivity`. If ``None``, + :class:`~iris.ugrid.mesh.Connectivity`. If ``None``, does not check for ``attributes``. cf_role : str, optional The UGRID ``cf_role`` of the desired - :class:`~iris.experimental.ugrid.mesh.Connectivity`. + :class:`~iris.ugrid.mesh.Connectivity`. contains_node : bool, optional Contains the ``node`` element as part of the - :attr:`~iris.experimental.ugrid.metadata.ConnectivityMetadata.cf_role` + :attr:`~iris.ugrid.metadata.ConnectivityMetadata.cf_role` in the list of objects to be matched for potential removal. contains_edge : bool, optional Contains the ``edge`` element as part of the - :attr:`~iris.experimental.ugrid.metadata.ConnectivityMetadata.cf_role` + :attr:`~iris.ugrid.metadata.ConnectivityMetadata.cf_role` in the list of objects to be matched for potential removal. contains_face : bool, optional Contains the ``face`` element as part of the - :attr:`~iris.experimental.ugrid.metadata.ConnectivityMetadata.cf_role` + :attr:`~iris.ugrid.metadata.ConnectivityMetadata.cf_role` in the list of objects to be matched for potential removal. Returns ------- - list of :class:`~iris.experimental.ugrid.mesh.Connectivity` - A list of :class:`~iris.experimental.ugrid.mesh.Connectivity` + list of :class:`~iris.ugrid.mesh.Connectivity` + A list of :class:`~iris.ugrid.mesh.Connectivity` instances removed from the :class:`MeshXY` that matched the given criteria. @@ -1852,9 +1852,9 @@ def xml_element(self, doc): # # return the lazy AuxCoord(...), AuxCoord(...) def to_MeshCoord(self, location, axis): - """Generate a :class:`~iris.experimental.ugrid.mesh.MeshCoord`. + """Generate a :class:`~iris.ugrid.mesh.MeshCoord`. - Generate a :class:`~iris.experimental.ugrid.mesh.MeshCoord` that + Generate a :class:`~iris.ugrid.mesh.MeshCoord` that references the current :class:`MeshXY`, and passing through the ``location`` and ``axis`` arguments. @@ -1866,25 +1866,25 @@ def to_MeshCoord(self, location, axis): ---------- location : str The ``location`` argument for - :class:`~iris.experimental.ugrid.mesh.MeshCoord` instantiation. + :class:`~iris.ugrid.mesh.MeshCoord` instantiation. axis : str The ``axis`` argument for - :class:`~iris.experimental.ugrid.mesh.MeshCoord` instantiation. + :class:`~iris.ugrid.mesh.MeshCoord` instantiation. Returns ------- - :class:`~iris.experimental.ugrid.mesh.MeshCoord` - A :class:`~iris.experimental.ugrid.mesh.MeshCoord` referencing the + :class:`~iris.ugrid.mesh.MeshCoord` + A :class:`~iris.ugrid.mesh.MeshCoord` referencing the current :class:`MeshXY`. """ return MeshCoord(mesh=self, location=location, axis=axis) def to_MeshCoords(self, location): - r"""Generate a tuple of :class:`~iris.experimental.ugrid.mesh.MeshCoord`. + r"""Generate a tuple of :class:`~iris.ugrid.mesh.MeshCoord`. Generate a tuple of - :class:`~iris.experimental.ugrid.mesh.MeshCoord`, each referencing + :class:`~iris.ugrid.mesh.MeshCoord`, each referencing the current :class:`MeshXY`, one for each :attr:`AXES` value, passing through the ``location`` argument. @@ -1899,8 +1899,8 @@ def to_MeshCoords(self, location): Returns ------- - tuple of :class:`~iris.experimental.ugrid.mesh.MeshCoord` - Tuple of :class:`~iris.experimental.ugrid.mesh.MeshCoord` + tuple of :class:`~iris.ugrid.mesh.MeshCoord` + Tuple of :class:`~iris.ugrid.mesh.MeshCoord` referencing the current :class:`MeshXY`. One for each value in :attr:`AXES`, using the value for the ``axis`` argument. @@ -2651,8 +2651,8 @@ def face_node(self): class MeshCoord(AuxCoord): """Geographic coordinate values of data on an unstructured mesh. - A MeshCoord references a `~iris.experimental.ugrid.mesh.MeshXY`. - When contained in a `~iris.cube.Cube` it connects the cube to the MeshXY. + A MeshCoord references a `~iris.ugrid.mesh.MeshXY`. + When contained in a `~iris.cube.Cube` it connects the cube to the Mesh. It records (a) which 1-D cube dimension represents the unstructured mesh, and (b) which mesh 'location' the cube data is mapped to -- i.e. is it data on 'face's, 'edge's or 'node's. diff --git a/lib/iris/experimental/ugrid/metadata.py b/lib/iris/ugrid/metadata.py similarity index 97% rename from lib/iris/experimental/ugrid/metadata.py rename to lib/iris/ugrid/metadata.py index 28b9f413d9..0165852d5f 100644 --- a/lib/iris/experimental/ugrid/metadata.py +++ b/lib/iris/ugrid/metadata.py @@ -3,7 +3,7 @@ # This file is part of Iris and is released under the BSD license. # See LICENSE in the root of the repository for full licensing details. -"""The common metadata API classes for :mod:`iris.experimental.ugrid.mesh`. +"""The common metadata API classes for :mod:`iris.ugrid.mesh`. Eventual destination: :mod:`iris.common.metadata`. @@ -11,9 +11,9 @@ from functools import wraps -from ...common import BaseMetadata -from ...common.lenient import _lenient_service as lenient_service -from ...common.metadata import ( +from ..common import BaseMetadata +from ..common.lenient import _lenient_service as lenient_service +from ..common.metadata import ( SERVICES, SERVICES_COMBINE, SERVICES_DIFFERENCE, @@ -22,7 +22,7 @@ class ConnectivityMetadata(BaseMetadata): - """Metadata container for a :class:`~iris.experimental.ugrid.mesh.Connectivity`.""" + """Metadata container for a :class:`~iris.ugrid.mesh.Connectivity`.""" # The "location_axis" member is stateful only, and does not participate in # lenient/strict equivalence. @@ -139,7 +139,7 @@ def equal(self, other, lenient=None): class MeshMetadata(BaseMetadata): - """Metadata container for a :class:`~iris.experimental.ugrid.mesh.MeshXY`.""" + """Metadata container for a :class:`~iris.ugrid.mesh.MeshXY`.""" # The node_dimension", "edge_dimension" and "face_dimension" members are # stateful only; they not participate in lenient/strict equivalence. diff --git a/lib/iris/experimental/ugrid/save.py b/lib/iris/ugrid/save.py similarity index 87% rename from lib/iris/experimental/ugrid/save.py rename to lib/iris/ugrid/save.py index ddd7e5333c..79933cbd08 100644 --- a/lib/iris/experimental/ugrid/save.py +++ b/lib/iris/ugrid/save.py @@ -3,7 +3,7 @@ # This file is part of Iris and is released under the BSD license. # See LICENSE in the root of the repository for full licensing details. -"""Extension to Iris' NetCDF saving to allow :class:`~iris.experimental.ugrid.mesh.MeshXY` saving in UGRID format. +"""Extension to Iris' NetCDF saving to allow :class:`~iris.ugrid.mesh.MeshXY` saving in UGRID format. Eventual destination: :mod:`iris.fileformats.netcdf`. @@ -11,7 +11,7 @@ from collections.abc import Iterable -from ...fileformats import netcdf +from ..fileformats import netcdf def save_mesh(mesh, filename, netcdf_format="NETCDF4"): @@ -19,7 +19,7 @@ def save_mesh(mesh, filename, netcdf_format="NETCDF4"): Parameters ---------- - mesh : :class:`iris.experimental.ugrid.MeshXY` or iterable + mesh : :class:`iris.ugrid.MeshXY` or iterable Mesh(es) to save. filename : str Name of the netCDF file to create. diff --git a/lib/iris/experimental/ugrid/utils.py b/lib/iris/ugrid/utils.py similarity index 99% rename from lib/iris/experimental/ugrid/utils.py rename to lib/iris/ugrid/utils.py index b78545c42e..def9c1fccf 100644 --- a/lib/iris/experimental/ugrid/utils.py +++ b/lib/iris/ugrid/utils.py @@ -31,7 +31,7 @@ def recombine_submeshes( Describes the mesh and mesh-location onto which the all the ``submesh-cubes``' data are mapped, and acts as a template for the result. - Must have a :class:`~iris.experimental.ugrid.mesh.MeshXY`. + Must have a :class:`~iris.ugrid.mesh.MeshXY`. submesh_cubes : iterable of Cube, or Cube Cubes, each with data on a _subset_ of the ``mesh_cube`` datapoints From 4585059bad5091064a3c3e4eda9d42296ab2e4d4 Mon Sep 17 00:00:00 2001 From: Patrick Peglar Date: Fri, 26 Jul 2024 11:38:52 +0100 Subject: [PATCH 05/26] Mesh nonexperimental extra (#6077) * Move all iris.experimental.ugrid to iris.ugrid. Replace experiment.ugrid, including docstrings and imports. Fix test_ParseUgridOnLoad Fix ugrid.load. Remove PARSE_UGRID from t/i/ugrid/test_ugrid_save Remove PARSE_UGRID from t/u/ff/nc/saver/test_save Remove PARSE_UGRID from t/i/exp/geovista/(both) Remove PARSE_UGRID from t/u/tests/stock/test_netcdf * Fix type of mesh in iris.tests.stock * Fix Mesh -> MeshXY in experimental.ugrid * Remove PARSE_UGRID_ON_LOAD and ParseUGridOnLoad, leave in iris.experimental.ugrid only. missed * Replace old-style mesh api in benchmarks. * Move misplaced test + fix obsolete PARSE_UGRID usage. * Minimal doctest fixes * Remove obsolete PARSE_UGRID in iris.ugrid * Move all ugrid.metadata into common.metadata. Removed obsolete tests.unit.metadata * Rename iris.ugrid to iris.mesh fix Fix iris.mesh import in tests/stock/__init__ * Move all iris.mesh.save into iris.fileformats.netcdf.save Fix experimental.ugrid import of save_mesh * Fix docs for iris.ugrid -> iris.mesh * Rename iris.mesh.mesh as iris.mesh.components * Tidy imports in experimental.ugrid * Rebrand so mesh.MeshXY is presented as experimental.ugrid.Mesh. * Move ugrid loading support code from iris.mesh to iris.netcdf, and its tests likewise * Fix circular import. * Move mesh.cf code into fileformats.cf, and tests likewise Fix imports in cf ugrid tests. * Reinclude mesh-load functions in iris.mesh; break circular imports * Small fixes to mesh documentation * Added whatsnew * Complete+remove remaining 'TODO's from #6061 * Odd docstring and comment corrections. --- benchmarks/benchmarks/cperf/__init__.py | 6 +- benchmarks/benchmarks/generate_data/stock.py | 31 +- benchmarks/benchmarks/generate_data/ugrid.py | 9 +- benchmarks/benchmarks/load/ugrid.py | 9 +- .../{experimental => mesh}/__init__.py | 2 +- .../benchmarks/mesh/utils}/__init__.py | 2 +- .../ugrid => mesh/utils}/regions_combine.py | 18 +- benchmarks/benchmarks/save.py | 2 +- benchmarks/benchmarks/sperf/__init__.py | 6 +- .../benchmarks/sperf/combine_regions.py | 16 +- benchmarks/benchmarks/sperf/equality.py | 2 +- benchmarks/benchmarks/sperf/save.py | 2 +- .../unit_style/{ugrid.py => mesh.py} | 14 +- docs/src/further_topics/ugrid/data_model.rst | 78 ++-- docs/src/further_topics/ugrid/index.rst | 2 +- docs/src/further_topics/ugrid/operations.rst | 96 ++--- .../further_topics/ugrid/partner_packages.rst | 2 +- docs/src/whatsnew/latest.rst | 7 + lib/iris/analysis/_regrid.py | 3 +- lib/iris/common/metadata.py | 379 ++++++++++++++++- lib/iris/common/resolve.py | 4 +- lib/iris/coords.py | 8 +- lib/iris/cube.py | 24 +- lib/iris/experimental/geovista.py | 4 +- lib/iris/experimental/ugrid.py | 118 +++++- lib/iris/fileformats/cf.py | 250 ++++++++++- lib/iris/fileformats/netcdf/loader.py | 14 +- lib/iris/fileformats/netcdf/saver.py | 55 ++- .../netcdf/ugrid_load.py} | 191 +++------ lib/iris/{ugrid => mesh}/__init__.py | 22 +- .../{ugrid/mesh.py => mesh/components.py} | 157 +++---- lib/iris/{ugrid => mesh}/utils.py | 2 +- .../test_meshcoord_coordsys.py | 10 +- .../{ugrid => mesh}/test_ugrid_save.py | 0 .../ugrid_conventions_examples/README.txt | 0 .../ugrid_ex1_1d_mesh.cdl | 0 .../ugrid_ex2_2d_triangular.cdl | 0 .../ugrid_ex3_2d_flexible.cdl | 0 .../ugrid_ex4_3d_layered.cdl | 0 .../{ugrid => netcdf}/test_ugrid_load.py | 16 +- .../2D_1t_face_half_levels.cml | 0 .../2D_72t_face_half_levels.cml | 0 .../3D_1t_face_full_levels.cml | 0 .../3D_1t_face_half_levels.cml | 0 .../{ugrid => mesh}/3D_snow_pseudo_levels.cml | 0 .../{ugrid => mesh}/3D_soil_pseudo_levels.cml | 0 .../{ugrid => mesh}/3D_tile_pseudo_levels.cml | 0 .../{ugrid => mesh}/3D_veg_pseudo_levels.cml | 0 .../results/{ugrid => mesh}/surface_mean.cml | 0 lib/iris/tests/stock/__init__.py | 2 +- lib/iris/tests/stock/mesh.py | 2 +- lib/iris/tests/stock/netcdf.py | 2 +- .../metadata/test_ConnectivityMetadata.py | 5 +- .../metadata/test_MeshCoordMetadata.py | 5 +- .../metadata/test_MeshMetadata.py | 5 +- .../metadata/test_metadata_manager_factory.py | 2 +- .../unit/common/mixin/test_CFVariableMixin.py | 2 +- .../unit/coords/test__DimensionalMetadata.py | 2 +- .../tests/unit/experimental/ugrid/__init__.py | 5 + .../ugrid}/test_ParseUgridOnLoad.py | 4 +- .../tests/unit/fileformats/cf/test_CFGroup.py | 67 +++ .../unit/fileformats/cf/test_CFReader.py | 96 ++++- ...test_CFUGridAuxiliaryCoordinateVariable.py | 20 +- .../cf/test_CFUGridConnectivityVariable.py | 22 +- .../cf/test_CFUGridMeshVariable.py | 20 +- .../netcdf/loader/test_load_cubes.py | 2 +- .../netcdf/loader/ugrid_load/__init__.py | 5 + .../loader/ugrid_load}/test_load_mesh.py | 7 +- .../loader/ugrid_load}/test_load_meshes.py | 4 +- .../ugrid_load}/test_meshload_checks.py | 0 .../netcdf/saver/test_Saver__ugrid.py | 5 +- .../unit/{ugrid/utils => mesh}/__init__.py | 2 +- .../{ugrid/cf => mesh/components}/__init__.py | 2 +- .../components}/test_Connectivity.py | 4 +- .../components}/test_MeshCoord.py | 4 +- .../components/test_MeshXY.py} | 127 +++--- .../components/test_MeshXY__from_coords.py} | 5 +- .../iris/tests/unit/mesh/utils}/__init__.py | 2 +- .../utils/test_recombine_submeshes.py | 4 +- .../tests/unit/tests/stock/test_netcdf.py | 2 +- .../tests/unit/ugrid/cf/test_CFUGridGroup.py | 87 ---- .../tests/unit/ugrid/cf/test_CFUGridReader.py | 129 ------ lib/iris/tests/unit/ugrid/load/__init__.py | 5 - lib/iris/tests/unit/ugrid/mesh/__init__.py | 5 - .../tests/unit/ugrid/metadata/__init__.py | 7 - lib/iris/ugrid/cf.py | 292 ------------- lib/iris/ugrid/metadata.py | 398 ------------------ lib/iris/ugrid/save.py | 53 --- 88 files changed, 1363 insertions(+), 1610 deletions(-) rename benchmarks/benchmarks/{experimental => mesh}/__init__.py (77%) rename {lib/iris/tests/unit/ugrid => benchmarks/benchmarks/mesh/utils}/__init__.py (76%) rename benchmarks/benchmarks/{experimental/ugrid => mesh/utils}/regions_combine.py (94%) rename benchmarks/benchmarks/unit_style/{ugrid.py => mesh.py} (93%) rename lib/iris/{ugrid/load.py => fileformats/netcdf/ugrid_load.py} (71%) rename lib/iris/{ugrid => mesh}/__init__.py (51%) rename lib/iris/{ugrid/mesh.py => mesh/components.py} (95%) rename lib/iris/{ugrid => mesh}/utils.py (99%) rename lib/iris/tests/integration/{experimental => mesh}/test_meshcoord_coordsys.py (93%) rename lib/iris/tests/integration/{ugrid => mesh}/test_ugrid_save.py (100%) rename lib/iris/tests/integration/{ugrid => mesh}/ugrid_conventions_examples/README.txt (100%) rename lib/iris/tests/integration/{ugrid => mesh}/ugrid_conventions_examples/ugrid_ex1_1d_mesh.cdl (100%) rename lib/iris/tests/integration/{ugrid => mesh}/ugrid_conventions_examples/ugrid_ex2_2d_triangular.cdl (100%) rename lib/iris/tests/integration/{ugrid => mesh}/ugrid_conventions_examples/ugrid_ex3_2d_flexible.cdl (100%) rename lib/iris/tests/integration/{ugrid => mesh}/ugrid_conventions_examples/ugrid_ex4_3d_layered.cdl (100%) rename lib/iris/tests/integration/{ugrid => netcdf}/test_ugrid_load.py (95%) rename lib/iris/tests/results/{ugrid => mesh}/2D_1t_face_half_levels.cml (100%) rename lib/iris/tests/results/{ugrid => mesh}/2D_72t_face_half_levels.cml (100%) rename lib/iris/tests/results/{ugrid => mesh}/3D_1t_face_full_levels.cml (100%) rename lib/iris/tests/results/{ugrid => mesh}/3D_1t_face_half_levels.cml (100%) rename lib/iris/tests/results/{ugrid => mesh}/3D_snow_pseudo_levels.cml (100%) rename lib/iris/tests/results/{ugrid => mesh}/3D_soil_pseudo_levels.cml (100%) rename lib/iris/tests/results/{ugrid => mesh}/3D_tile_pseudo_levels.cml (100%) rename lib/iris/tests/results/{ugrid => mesh}/3D_veg_pseudo_levels.cml (100%) rename lib/iris/tests/results/{ugrid => mesh}/surface_mean.cml (100%) rename lib/iris/tests/unit/{ugrid => common}/metadata/test_ConnectivityMetadata.py (99%) rename lib/iris/tests/unit/{ugrid => common}/metadata/test_MeshCoordMetadata.py (99%) rename lib/iris/tests/unit/{ugrid => common}/metadata/test_MeshMetadata.py (99%) create mode 100644 lib/iris/tests/unit/experimental/ugrid/__init__.py rename lib/iris/tests/unit/{ugrid/load => experimental/ugrid}/test_ParseUgridOnLoad.py (81%) rename lib/iris/tests/unit/{ugrid => fileformats}/cf/test_CFUGridAuxiliaryCoordinateVariable.py (93%) rename lib/iris/tests/unit/{ugrid => fileformats}/cf/test_CFUGridConnectivityVariable.py (92%) rename lib/iris/tests/unit/{ugrid => fileformats}/cf/test_CFUGridMeshVariable.py (94%) create mode 100644 lib/iris/tests/unit/fileformats/netcdf/loader/ugrid_load/__init__.py rename lib/iris/tests/unit/{ugrid/load => fileformats/netcdf/loader/ugrid_load}/test_load_mesh.py (86%) rename lib/iris/tests/unit/{ugrid/load => fileformats/netcdf/loader/ugrid_load}/test_load_meshes.py (98%) rename lib/iris/tests/unit/{ugrid/load => fileformats/netcdf/loader/ugrid_load}/test_meshload_checks.py (100%) rename lib/iris/tests/unit/{ugrid/utils => mesh}/__init__.py (78%) rename lib/iris/tests/unit/{ugrid/cf => mesh/components}/__init__.py (73%) rename lib/iris/tests/unit/{ugrid/mesh => mesh/components}/test_Connectivity.py (99%) rename lib/iris/tests/unit/{ugrid/mesh => mesh/components}/test_MeshCoord.py (99%) rename lib/iris/tests/unit/{ugrid/mesh/test_Mesh.py => mesh/components/test_MeshXY.py} (92%) rename lib/iris/tests/unit/{ugrid/mesh/test_Mesh__from_coords.py => mesh/components/test_MeshXY__from_coords.py} (98%) rename {benchmarks/benchmarks/experimental/ugrid => lib/iris/tests/unit/mesh/utils}/__init__.py (75%) rename lib/iris/tests/unit/{ugrid => mesh}/utils/test_recombine_submeshes.py (99%) delete mode 100644 lib/iris/tests/unit/ugrid/cf/test_CFUGridGroup.py delete mode 100644 lib/iris/tests/unit/ugrid/cf/test_CFUGridReader.py delete mode 100644 lib/iris/tests/unit/ugrid/load/__init__.py delete mode 100644 lib/iris/tests/unit/ugrid/mesh/__init__.py delete mode 100644 lib/iris/tests/unit/ugrid/metadata/__init__.py delete mode 100644 lib/iris/ugrid/cf.py delete mode 100644 lib/iris/ugrid/metadata.py delete mode 100644 lib/iris/ugrid/save.py diff --git a/benchmarks/benchmarks/cperf/__init__.py b/benchmarks/benchmarks/cperf/__init__.py index df28a66265..05a086bc44 100644 --- a/benchmarks/benchmarks/cperf/__init__.py +++ b/benchmarks/benchmarks/cperf/__init__.py @@ -14,9 +14,6 @@ from iris import load_cube -# TODO: remove uses of PARSE_UGRID_ON_LOAD once UGRID parsing is core behaviour. -from iris.experimental.ugrid import PARSE_UGRID_ON_LOAD - from ..generate_data import BENCHMARK_DATA from ..generate_data.ugrid import make_cubesphere_testfile @@ -92,5 +89,4 @@ def setup(self, file_type, three_d, three_times): self.file_type = file_type def load(self): - with PARSE_UGRID_ON_LOAD.context(): - return load_cube(str(self.file_path)) + return load_cube(str(self.file_path)) diff --git a/benchmarks/benchmarks/generate_data/stock.py b/benchmarks/benchmarks/generate_data/stock.py index 61f085195a..04698e8ff5 100644 --- a/benchmarks/benchmarks/generate_data/stock.py +++ b/benchmarks/benchmarks/generate_data/stock.py @@ -14,7 +14,7 @@ import iris from iris import cube -from iris.experimental.ugrid import PARSE_UGRID_ON_LOAD, load_mesh +from iris.mesh import load_mesh from . import BENCHMARK_DATA, REUSE_DATA, load_realised, run_function_elsewhere @@ -90,7 +90,7 @@ def sample_mesh(n_nodes=None, n_faces=None, n_edges=None, lazy_values=False): """Sample mesh wrapper for :meth:iris.tests.stock.mesh.sample_mesh`.""" def _external(*args, **kwargs): - from iris.experimental.ugrid import save_mesh + from iris.mesh import save_mesh from iris.tests.stock.mesh import sample_mesh save_path_ = kwargs.pop("save_path") @@ -104,13 +104,12 @@ def _external(*args, **kwargs): save_path = (BENCHMARK_DATA / f"sample_mesh_{args_hash}").with_suffix(".nc") if not REUSE_DATA or not save_path.is_file(): _ = run_function_elsewhere(_external, *arg_list, save_path=str(save_path)) - with PARSE_UGRID_ON_LOAD.context(): - if not lazy_values: - # Realise everything. - with load_realised(): - mesh = load_mesh(str(save_path)) - else: + if not lazy_values: + # Realise everything. + with load_realised(): mesh = load_mesh(str(save_path)) + else: + mesh = load_mesh(str(save_path)) return mesh @@ -118,7 +117,7 @@ def sample_meshcoord(sample_mesh_kwargs=None, location="face", axis="x"): """Sample meshcoord wrapper for :meth:`iris.tests.stock.mesh.sample_meshcoord`. Parameters deviate from the original as cannot pass a - :class:`iris.experimental.ugrid.Mesh to the separate Python instance - must + :class:`iris.mesh.Mesh to the separate Python instance - must instead generate the Mesh as well. MeshCoords cannot be saved to file, so the _external method saves the @@ -127,7 +126,7 @@ def sample_meshcoord(sample_mesh_kwargs=None, location="face", axis="x"): """ def _external(sample_mesh_kwargs_, save_path_): - from iris.experimental.ugrid import save_mesh + from iris.mesh import save_mesh from iris.tests.stock.mesh import sample_mesh, sample_meshcoord if sample_mesh_kwargs_: @@ -147,9 +146,8 @@ def _external(sample_mesh_kwargs_, save_path_): sample_mesh_kwargs_=sample_mesh_kwargs, save_path_=str(save_path), ) - with PARSE_UGRID_ON_LOAD.context(): - with load_realised(): - source_mesh = load_mesh(str(save_path)) + with load_realised(): + source_mesh = load_mesh(str(save_path)) # Regenerate MeshCoord from its Mesh, which we saved. return source_mesh.to_MeshCoord(location=location, axis=axis) @@ -180,7 +178,6 @@ def _external(w_mesh_: str, save_path_: str): ) if not REUSE_DATA or not save_path.is_file(): _ = run_function_elsewhere(_external, w_mesh_=w_mesh, save_path_=str(save_path)) - with PARSE_UGRID_ON_LOAD.context(): - context = nullcontext() if lazy else load_realised() - with context: - return iris.load_cube(save_path, "air_potential_temperature") + context = nullcontext() if lazy else load_realised() + with context: + return iris.load_cube(save_path, "air_potential_temperature") diff --git a/benchmarks/benchmarks/generate_data/ugrid.py b/benchmarks/benchmarks/generate_data/ugrid.py index de76d63798..2cef4752ee 100644 --- a/benchmarks/benchmarks/generate_data/ugrid.py +++ b/benchmarks/benchmarks/generate_data/ugrid.py @@ -5,7 +5,6 @@ """Scripts for generating supporting data for UGRID-related benchmarking.""" from iris import load_cube as iris_loadcube -from iris.experimental.ugrid import PARSE_UGRID_ON_LOAD from . import BENCHMARK_DATA, REUSE_DATA, load_realised, run_function_elsewhere from .stock import ( @@ -85,8 +84,7 @@ def make_cube_like_2d_cubesphere(n_cube: int, with_mesh: bool): ) # File now *should* definitely exist: content is simply the desired cube. - with PARSE_UGRID_ON_LOAD.context(): - cube = iris_loadcube(str(filepath)) + cube = iris_loadcube(str(filepath)) # Ensure correct laziness. _ = cube.data @@ -155,9 +153,8 @@ def _external(xy_dims_, save_path_): ) if not REUSE_DATA or not save_path.is_file(): _ = run_function_elsewhere(_external, xy_dims, str(save_path)) - with PARSE_UGRID_ON_LOAD.context(): - with load_realised(): - cube = iris_loadcube(str(save_path)) + with load_realised(): + cube = iris_loadcube(str(save_path)) return cube diff --git a/benchmarks/benchmarks/load/ugrid.py b/benchmarks/benchmarks/load/ugrid.py index 47e23dc050..5ad0086ef3 100644 --- a/benchmarks/benchmarks/load/ugrid.py +++ b/benchmarks/benchmarks/load/ugrid.py @@ -5,8 +5,7 @@ """Mesh data loading benchmark tests.""" from iris import load_cube as iris_load_cube -from iris.experimental.ugrid import PARSE_UGRID_ON_LOAD -from iris.experimental.ugrid import load_mesh as iris_load_mesh +from iris.mesh import load_mesh as iris_load_mesh from ..generate_data.stock import create_file__xios_2d_face_half_levels @@ -18,13 +17,11 @@ def synthetic_data(**kwargs): def load_cube(*args, **kwargs): - with PARSE_UGRID_ON_LOAD.context(): - return iris_load_cube(*args, **kwargs) + return iris_load_cube(*args, **kwargs) def load_mesh(*args, **kwargs): - with PARSE_UGRID_ON_LOAD.context(): - return iris_load_mesh(*args, **kwargs) + return iris_load_mesh(*args, **kwargs) class BasicLoading: diff --git a/benchmarks/benchmarks/experimental/__init__.py b/benchmarks/benchmarks/mesh/__init__.py similarity index 77% rename from benchmarks/benchmarks/experimental/__init__.py rename to benchmarks/benchmarks/mesh/__init__.py index ce727a7286..9cc76ce0aa 100644 --- a/benchmarks/benchmarks/experimental/__init__.py +++ b/benchmarks/benchmarks/mesh/__init__.py @@ -2,4 +2,4 @@ # # This file is part of Iris and is released under the BSD license. # See LICENSE in the root of the repository for full licensing details. -"""Benchmark tests for the experimental module.""" +"""Benchmark tests for the iris.mesh module.""" diff --git a/lib/iris/tests/unit/ugrid/__init__.py b/benchmarks/benchmarks/mesh/utils/__init__.py similarity index 76% rename from lib/iris/tests/unit/ugrid/__init__.py rename to benchmarks/benchmarks/mesh/utils/__init__.py index d80eae287c..e20973c0a7 100644 --- a/lib/iris/tests/unit/ugrid/__init__.py +++ b/benchmarks/benchmarks/mesh/utils/__init__.py @@ -2,4 +2,4 @@ # # This file is part of Iris and is released under the BSD license. # See LICENSE in the root of the repository for full licensing details. -"""Unit tests for the :mod:`iris.ugrid` package.""" +"""Benchmark tests for the iris.mesh.utils module.""" diff --git a/benchmarks/benchmarks/experimental/ugrid/regions_combine.py b/benchmarks/benchmarks/mesh/utils/regions_combine.py similarity index 94% rename from benchmarks/benchmarks/experimental/ugrid/regions_combine.py rename to benchmarks/benchmarks/mesh/utils/regions_combine.py index d3781a183f..1a1a43a622 100644 --- a/benchmarks/benchmarks/experimental/ugrid/regions_combine.py +++ b/benchmarks/benchmarks/mesh/utils/regions_combine.py @@ -5,7 +5,7 @@ """Benchmarks stages of operation. Benchmarks stages of operation of the function -:func:`iris.experimental.ugrid.utils.recombine_submeshes`. +:func:`iris.mesh.utils.recombine_submeshes`. """ @@ -15,8 +15,7 @@ import numpy as np from iris import load, load_cube, save -from iris.experimental.ugrid import PARSE_UGRID_ON_LOAD -from iris.experimental.ugrid.utils import recombine_submeshes +from iris.mesh.utils import recombine_submeshes from ... import TrackAddedMemoryAllocation from ...generate_data.ugrid import make_cube_like_2d_cubesphere @@ -103,13 +102,12 @@ def setup(self, n_cubesphere, imaginary_data=True, create_result_cube=True): """ # Load source cubes (full-mesh and regions) - with PARSE_UGRID_ON_LOAD.context(): - self.full_mesh_cube = load_cube( - self._parametrised_cache_filename(n_cubesphere, "meshcube") - ) - self.region_cubes = load( - self._parametrised_cache_filename(n_cubesphere, "regioncubes") - ) + self.full_mesh_cube = load_cube( + self._parametrised_cache_filename(n_cubesphere, "meshcube") + ) + self.region_cubes = load( + self._parametrised_cache_filename(n_cubesphere, "regioncubes") + ) # Remove all var-names from loaded cubes, which can otherwise cause # problems. Also implement 'imaginary' data. diff --git a/benchmarks/benchmarks/save.py b/benchmarks/benchmarks/save.py index f78734835d..aaa8480d64 100644 --- a/benchmarks/benchmarks/save.py +++ b/benchmarks/benchmarks/save.py @@ -5,7 +5,7 @@ """File saving benchmarks.""" from iris import save -from iris.experimental.ugrid import save_mesh +from iris.mesh import save_mesh from . import TrackAddedMemoryAllocation, on_demand_benchmark from .generate_data.ugrid import make_cube_like_2d_cubesphere diff --git a/benchmarks/benchmarks/sperf/__init__.py b/benchmarks/benchmarks/sperf/__init__.py index e51bef5ca2..2b8b508fd5 100644 --- a/benchmarks/benchmarks/sperf/__init__.py +++ b/benchmarks/benchmarks/sperf/__init__.py @@ -10,9 +10,6 @@ from iris import load_cube -# TODO: remove uses of PARSE_UGRID_ON_LOAD once UGRID parsing is core behaviour. -from iris.experimental.ugrid import PARSE_UGRID_ON_LOAD - from ..generate_data.ugrid import make_cubesphere_testfile @@ -38,5 +35,4 @@ def setup(self, c_size, n_levels, n_times): ) def load_cube(self): - with PARSE_UGRID_ON_LOAD.context(): - return load_cube(str(self.file_path)) + return load_cube(str(self.file_path)) diff --git a/benchmarks/benchmarks/sperf/combine_regions.py b/benchmarks/benchmarks/sperf/combine_regions.py index d375f44719..b106befcae 100644 --- a/benchmarks/benchmarks/sperf/combine_regions.py +++ b/benchmarks/benchmarks/sperf/combine_regions.py @@ -10,8 +10,7 @@ import numpy as np from iris import load, load_cube, save -from iris.experimental.ugrid import PARSE_UGRID_ON_LOAD -from iris.experimental.ugrid.utils import recombine_submeshes +from iris.mesh.utils import recombine_submeshes from .. import TrackAddedMemoryAllocation, on_demand_benchmark from ..generate_data.ugrid import BENCHMARK_DATA, make_cube_like_2d_cubesphere @@ -102,13 +101,12 @@ def setup(self, n_cubesphere, imaginary_data=True, create_result_cube=True): """ # Load source cubes (full-mesh and regions) - with PARSE_UGRID_ON_LOAD.context(): - self.full_mesh_cube = load_cube( - self._parametrised_cache_filename(n_cubesphere, "meshcube") - ) - self.region_cubes = load( - self._parametrised_cache_filename(n_cubesphere, "regioncubes") - ) + self.full_mesh_cube = load_cube( + self._parametrised_cache_filename(n_cubesphere, "meshcube") + ) + self.region_cubes = load( + self._parametrised_cache_filename(n_cubesphere, "regioncubes") + ) # Remove all var-names from loaded cubes, which can otherwise cause # problems. Also implement 'imaginary' data. diff --git a/benchmarks/benchmarks/sperf/equality.py b/benchmarks/benchmarks/sperf/equality.py index f67935c9ef..ddee90cd28 100644 --- a/benchmarks/benchmarks/sperf/equality.py +++ b/benchmarks/benchmarks/sperf/equality.py @@ -13,7 +13,7 @@ class CubeEquality(FileMixin): r"""Benchmark time and memory costs. Benchmark time and memory costs of comparing :class:`~iris.cube.Cube`\\ s - with attached :class:`~iris.experimental.ugrid.mesh.MeshXY`\\ es. + with attached :class:`~iris.mesh.MeshXY`\\ es. Uses :class:`FileMixin` as the realistic case will be comparing :class:`~iris.cube.Cube`\\ s that have been loaded from file. diff --git a/benchmarks/benchmarks/sperf/save.py b/benchmarks/benchmarks/sperf/save.py index 8d9a90f7cf..d8a03798f0 100644 --- a/benchmarks/benchmarks/sperf/save.py +++ b/benchmarks/benchmarks/sperf/save.py @@ -7,7 +7,7 @@ import os.path from iris import save -from iris.experimental.ugrid import save_mesh +from iris.mesh import save_mesh from .. import TrackAddedMemoryAllocation, on_demand_benchmark from ..generate_data.ugrid import make_cube_like_2d_cubesphere diff --git a/benchmarks/benchmarks/unit_style/ugrid.py b/benchmarks/benchmarks/unit_style/mesh.py similarity index 93% rename from benchmarks/benchmarks/unit_style/ugrid.py rename to benchmarks/benchmarks/unit_style/mesh.py index e2f235eb28..ed3aad1428 100644 --- a/benchmarks/benchmarks/unit_style/ugrid.py +++ b/benchmarks/benchmarks/unit_style/mesh.py @@ -2,22 +2,22 @@ # # This file is part of Iris and is released under the BSD license. # See LICENSE in the root of the repository for full licensing details. -"""Benchmark tests for the experimental.ugrid module.""" +"""Benchmark tests for the iris.mesh module.""" from copy import deepcopy import numpy as np -from iris.experimental import ugrid +from iris import mesh from .. import disable_repeat_between_setup from ..generate_data.stock import sample_mesh class UGridCommon: - """Run a generalised suite of benchmarks for any ugrid object. + """Run a generalised suite of benchmarks for any mesh object. - A base class running a generalised suite of benchmarks for any ugrid object. + A base class running a generalised suite of benchmarks for any mesh object. Object to be specified in a subclass. ASV will run the benchmarks within this class for any subclasses. @@ -53,7 +53,7 @@ def setup(self, n_faces): super().setup(n_faces) def create(self): - return ugrid.Connectivity(indices=self.array, cf_role="face_node_connectivity") + return mesh.Connectivity(indices=self.array, cf_role="face_node_connectivity") def time_indices(self, n_faces): _ = self.object.indices @@ -123,7 +123,7 @@ def get_coords_and_axes(location): self.eq_object = deepcopy(self.object) def create(self): - return ugrid.MeshXY(**self.mesh_kwargs) + return mesh.MeshXY(**self.mesh_kwargs) def time_add_connectivities(self, n_faces): self.object.add_connectivities(self.face_node) @@ -170,7 +170,7 @@ def setup(self, n_faces, lazy=False): super().setup(n_faces) def create(self): - return ugrid.MeshCoord(mesh=self.mesh, location="face", axis="x") + return mesh.MeshCoord(mesh=self.mesh, location="face", axis="x") def time_points(self, n_faces): _ = self.object.points diff --git a/docs/src/further_topics/ugrid/data_model.rst b/docs/src/further_topics/ugrid/data_model.rst index 9e74647e96..1660f6d08c 100644 --- a/docs/src/further_topics/ugrid/data_model.rst +++ b/docs/src/further_topics/ugrid/data_model.rst @@ -298,7 +298,7 @@ How Iris Represents This .. seealso:: Remember this is a prose summary. Precise documentation is at: - :mod:`iris.ugrid`. + :mod:`iris.mesh`. .. note:: @@ -310,7 +310,7 @@ The Basics The Iris :class:`~iris.cube.Cube` has several new members: * | :attr:`~iris.cube.Cube.mesh` - | The :class:`iris.ugrid.MeshXY` that describes the + | The :class:`iris.mesh.MeshXY` that describes the :class:`~iris.cube.Cube`\'s horizontal geography. * | :attr:`~iris.cube.Cube.location` | ``node``/``edge``/``face`` - the mesh element type with which this @@ -320,10 +320,10 @@ The Iris :class:`~iris.cube.Cube` has several new members: indexes over the horizontal :attr:`~iris.cube.Cube.data` positions. These members will all be ``None`` for a :class:`~iris.cube.Cube` with no -associated :class:`~iris.ugrid.MeshXY`. +associated :class:`~iris.mesh.MeshXY`. This :class:`~iris.cube.Cube`\'s unstructured dimension has multiple attached -:class:`iris.ugrid.MeshCoord`\s (one for each axis e.g. +:class:`iris.mesh.MeshCoord`\s (one for each axis e.g. ``x``/``y``), which can be used to infer the points and bounds of any index on the :class:`~iris.cube.Cube`\'s unstructured dimension. @@ -333,7 +333,7 @@ the :class:`~iris.cube.Cube`\'s unstructured dimension. from iris.coords import AuxCoord, DimCoord from iris.cube import Cube - from iris.ugrid import Connectivity, MeshXY + from iris.mesh import Connectivity, MeshXY node_x = AuxCoord( points=[0.0, 5.0, 0.0, 5.0, 8.0], @@ -422,38 +422,38 @@ The Detail ---------- How UGRID information is stored ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -* | :class:`iris.ugrid.MeshXY` +* | :class:`iris.mesh.MeshXY` | Contains all information about the mesh. | Includes: - * | :attr:`~iris.ugrid.MeshXY.topology_dimension` + * | :attr:`~iris.mesh.MeshXY.topology_dimension` | The maximum dimensionality of shape (1D=edge, 2D=face) supported - by this :class:`~iris.ugrid.MeshXY`. Determines which - :class:`~iris.ugrid.Connectivity`\s are required/optional + by this :class:`~iris.mesh.MeshXY`. Determines which + :class:`~iris.mesh.Connectivity`\s are required/optional (see below). * 1-3 collections of :class:`iris.coords.AuxCoord`\s: - * | **Required**: :attr:`~iris.ugrid.MeshXY.node_coords` + * | **Required**: :attr:`~iris.mesh.MeshXY.node_coords` | The nodes that are the basis for the mesh. - * | Optional: :attr:`~iris.ugrid.Mesh.edge_coords`, - :attr:`~iris.ugrid.MeshXY.face_coords` + * | Optional: :attr:`~iris.mesh.Mesh.edge_coords`, + :attr:`~iris.mesh.MeshXY.face_coords` | For indicating the 'centres' of the edges/faces. - | **NOTE:** generating a :class:`~iris.ugrid.MeshCoord` from - a :class:`~iris.ugrid.MeshXY` currently (``Jan 2022``) + | **NOTE:** generating a :class:`~iris.mesh.MeshCoord` from + a :class:`~iris.mesh.MeshXY` currently (``Jan 2022``) requires centre coordinates for the given ``location``; to be rectified in future. - * 1 or more :class:`iris.ugrid.Connectivity`\s: + * 1 or more :class:`iris.mesh.Connectivity`\s: * | **Required for 1D (edge) elements**: - :attr:`~iris.ugrid.MeshXY.edge_node_connectivity` + :attr:`~iris.mesh.MeshXY.edge_node_connectivity` | Define the edges by connecting nodes. * | **Required for 2D (face) elements**: - :attr:`~iris.ugrid.MeshXY.face_node_connectivity` + :attr:`~iris.mesh.MeshXY.face_node_connectivity` | Define the faces by connecting nodes. * Optional: any other connectivity type. See - :attr:`iris.ugrid.mesh.Connectivity.UGRID_CF_ROLES` for the + :attr:`iris.mesh.Connectivity.UGRID_CF_ROLES` for the full list of types. .. doctest:: ugrid_summaries @@ -480,30 +480,30 @@ How UGRID information is stored long_name: 'my_mesh' -* | :class:`iris.ugrid.MeshCoord` +* | :class:`iris.mesh.MeshCoord` | Described in detail in `MeshCoords`_. | Stores the following information: - * | :attr:`~iris.ugrid.MeshCoord.mesh` - | The :class:`~iris.ugrid.MeshXY` associated with this - :class:`~iris.ugrid.MeshCoord`. This determines the + * | :attr:`~iris.mesh.MeshCoord.mesh` + | The :class:`~iris.mesh.MeshXY` associated with this + :class:`~iris.mesh.MeshCoord`. This determines the :attr:`~iris.cube.Cube.mesh` attribute of any :class:`~iris.cube.Cube` - this :class:`~iris.ugrid.MeshCoord` is attached to (see + this :class:`~iris.mesh.MeshCoord` is attached to (see `The Basics`_) - * | :attr:`~iris.ugrid.MeshCoord.location` + * | :attr:`~iris.mesh.MeshCoord.location` | ``node``/``edge``/``face`` - the element detailed by this - :class:`~iris.ugrid.MeshCoord`. This determines the + :class:`~iris.mesh.MeshCoord`. This determines the :attr:`~iris.cube.Cube.location` attribute of any :class:`~iris.cube.Cube` this - :class:`~iris.ugrid.MeshCoord` is attached to (see + :class:`~iris.mesh.MeshCoord` is attached to (see `The Basics`_). .. _ugrid MeshCoords: MeshCoords ~~~~~~~~~~ -Links a :class:`~iris.cube.Cube` to a :class:`~iris.ugrid.MeshXY` by +Links a :class:`~iris.cube.Cube` to a :class:`~iris.mesh.MeshXY` by attaching to the :class:`~iris.cube.Cube`\'s unstructured dimension, in the same way that all :class:`~iris.coords.Coord`\s attach to :class:`~iris.cube.Cube` dimensions. This allows a single @@ -511,23 +511,23 @@ same way that all :class:`~iris.coords.Coord`\s attach to dimensions (e.g. horizontal mesh plus vertical levels and a time series), using the same logic for every dimension. -:class:`~iris.ugrid.MeshCoord`\s are instantiated using a given -:class:`~iris.ugrid.MeshXY`, ``location`` +:class:`~iris.mesh.MeshCoord`\s are instantiated using a given +:class:`~iris.mesh.MeshXY`, ``location`` ("node"/"edge"/"face") and ``axis``. The process interprets the -:class:`~iris.ugrid.MeshXY`\'s -:attr:`~iris.ugrid.MeshXY.node_coords` and if appropriate the -:attr:`~iris.ugrid.MeshXY.edge_node_connectivity`/ -:attr:`~iris.ugrid.MeshXY.face_node_connectivity` and -:attr:`~iris.ugrid.MeshXY.edge_coords`/ -:attr:`~iris.ugrid.MeshXY.face_coords` +:class:`~iris.mesh.MeshXY`\'s +:attr:`~iris.mesh.MeshXY.node_coords` and if appropriate the +:attr:`~iris.mesh.MeshXY.edge_node_connectivity`/ +:attr:`~iris.mesh.MeshXY.face_node_connectivity` and +:attr:`~iris.mesh.MeshXY.edge_coords`/ +:attr:`~iris.mesh.MeshXY.face_coords` to produce a :class:`~iris.coords.Coord` :attr:`~iris.coords.Coord.points` and :attr:`~iris.coords.Coord.bounds` -representation of all the :class:`~iris.ugrid.MeshXY`\'s +representation of all the :class:`~iris.mesh.MeshXY`\'s nodes/edges/faces for the given axis. -The method :meth:`iris.ugrid.MeshXY.to_MeshCoords` is available to -create a :class:`~iris.ugrid.MeshCoord` for -every axis represented by that :class:`~iris.ugrid.MeshXY`, +The method :meth:`iris.mesh.MeshXY.to_MeshCoords` is available to +create a :class:`~iris.mesh.MeshCoord` for +every axis represented by that :class:`~iris.mesh.MeshXY`, given only the ``location`` argument .. doctest:: ugrid_summaries diff --git a/docs/src/further_topics/ugrid/index.rst b/docs/src/further_topics/ugrid/index.rst index e21730bb6e..c247a9dc6d 100644 --- a/docs/src/further_topics/ugrid/index.rst +++ b/docs/src/further_topics/ugrid/index.rst @@ -9,7 +9,7 @@ Iris includes specialised handling of mesh-located data (as opposed to grid-located data). Iris and its :ref:`partner packages ` are designed to make working with mesh-located data as simple as possible, with new capabilities being added all the time. More detail is in this section and in -the :mod:`iris.ugrid` API documentation. +the :mod:`iris.mesh` API documentation. This mesh support is based on the `CF-UGRID Conventions`__; UGRID-conformant meshes + data can be loaded from a file into Iris' data model, and meshes + diff --git a/docs/src/further_topics/ugrid/operations.rst b/docs/src/further_topics/ugrid/operations.rst index f7b9eb2fca..97dfaaa5b1 100644 --- a/docs/src/further_topics/ugrid/operations.rst +++ b/docs/src/further_topics/ugrid/operations.rst @@ -61,7 +61,7 @@ subsequent example operations on this page. >>> import numpy as np >>> from iris.coords import AuxCoord - >>> from iris.ugrid import Connectivity, MeshXY + >>> from iris.mesh import Connectivity, MeshXY # Going to create the following mesh # (node indices are shown to aid understanding): @@ -143,8 +143,8 @@ Making a Cube (with a Mesh) .. rubric:: |tagline: making a cube| Creating a :class:`~iris.cube.Cube` is unchanged; the -:class:`~iris.ugrid.MeshXY` is linked via a -:class:`~iris.ugrid.MeshCoord` (see :ref:`ugrid MeshCoords`): +:class:`~iris.mesh.MeshXY` is linked via a +:class:`~iris.mesh.MeshCoord` (see :ref:`ugrid MeshCoords`): .. dropdown:: Code :icon: code @@ -205,7 +205,7 @@ Save .. note:: UGRID saving support is limited to the NetCDF file format. The Iris saving process automatically detects if the :class:`~iris.cube.Cube` -has an associated :class:`~iris.ugrid.MeshXY` and automatically +has an associated :class:`~iris.mesh.MeshXY` and automatically saves the file in a UGRID-conformant format: .. dropdown:: Code @@ -282,8 +282,8 @@ saves the file in a UGRID-conformant format: } -The :func:`iris.ugrid.save_mesh` function allows -:class:`~iris.ugrid.MeshXY`\es to be saved to file without +The :func:`iris.mesh.save_mesh` function allows +:class:`~iris.mesh.MeshXY`\es to be saved to file without associated :class:`~iris.cube.Cube`\s: .. dropdown:: Code @@ -293,7 +293,7 @@ associated :class:`~iris.cube.Cube`\s: >>> from subprocess import run - >>> from iris.ugrid import save_mesh + >>> from iris.mesh import save_mesh >>> mesh_path = "my_mesh.nc" >>> save_mesh(my_mesh, mesh_path) @@ -347,16 +347,14 @@ associated :class:`~iris.cube.Cube`\s: Load ---- -.. |tagline: load| replace:: |different| - UGRID parsing is opt-in +.. |tagline: load| replace:: |unchanged| .. rubric:: |tagline: load| .. note:: UGRID loading support is limited to the NetCDF file format. -While Iris' UGRID support remains :mod:`~iris.experimental`, parsing UGRID when -loading a file remains **optional**. To load UGRID data from a file into the -Iris mesh data model, use the -:const:`iris.ugrid.PARSE_UGRID_ON_LOAD` context manager: +Iris mesh support detects + parses any UGRID information when loading files, to +produce cubes with a non-empty ".mesh" property. .. dropdown:: Code :icon: code @@ -364,10 +362,8 @@ Iris mesh data model, use the .. doctest:: ugrid_operations >>> from iris import load - >>> from iris.ugrid import PARSE_UGRID_ON_LOAD - >>> with PARSE_UGRID_ON_LOAD.context(): - ... loaded_cubelist = load(cubelist_path) + >>> loaded_cubelist = load(cubelist_path) # Sort CubeList to ensure consistent result. >>> loaded_cubelist.sort(key=lambda cube: cube.name()) @@ -386,9 +382,8 @@ etcetera: >>> from iris import Constraint, load_cube - >>> with PARSE_UGRID_ON_LOAD.context(): - ... ground_cubelist = load(cubelist_path, Constraint(height=0)) - ... face_cube = load_cube(cubelist_path, "face_data") + >>> ground_cubelist = load(cubelist_path, Constraint(height=0)) + >>> face_cube = load_cube(cubelist_path, "face_data") # Sort CubeList to ensure consistent result. >>> ground_cubelist.sort(key=lambda cube: cube.name()) @@ -412,15 +407,15 @@ etcetera: .. note:: We recommend caution if constraining on coordinates associated with a - :class:`~iris.ugrid.MeshXY`. An individual coordinate value + :class:`~iris.mesh.MeshXY`. An individual coordinate value might not be shared by any other data points, and using a coordinate range will demand notably higher performance given the size of the dimension versus structured grids (:ref:`see the data model detail `). -The :func:`iris.ugrid.load_mesh` and -:func:`~iris.ugrid.load_meshes` functions allow only -:class:`~iris.ugrid.MeshXY`\es to be loaded from a file without +The :func:`iris.mesh.load_mesh` and +:func:`~iris.mesh.load_meshes` functions allow only +:class:`~iris.mesh.MeshXY`\es to be loaded from a file without creating any associated :class:`~iris.cube.Cube`\s: .. dropdown:: Code @@ -428,10 +423,9 @@ creating any associated :class:`~iris.cube.Cube`\s: .. doctest:: ugrid_operations - >>> from iris.ugrid import load_mesh + >>> from iris.mesh import load_mesh - >>> with PARSE_UGRID_ON_LOAD.context(): - ... loaded_mesh = load_mesh(cubelist_path) + >>> loaded_mesh = load_mesh(cubelist_path) >>> print(loaded_mesh) MeshXY : 'my_mesh' @@ -493,10 +487,8 @@ GeoVista :external+geovista:doc:`generated/gallery/index`. >>> from iris import load_cube, sample_data_path >>> from iris.experimental.geovista import cube_to_polydata - >>> from iris.ugrid import PARSE_UGRID_ON_LOAD - >>> with PARSE_UGRID_ON_LOAD.context(): - ... sample_mesh_cube = load_cube(sample_data_path("mesh_C4_synthetic_float.nc")) + >>> sample_mesh_cube = load_cube(sample_data_path("mesh_C4_synthetic_float.nc")) >>> print(sample_mesh_cube) synthetic / (1) (-- : 96) Mesh coordinates: @@ -541,11 +533,11 @@ As described in :doc:`data_model`, indexing for a range along a :class:`~iris.cube.Cube`\'s :meth:`~iris.cube.Cube.mesh_dim` will not provide a contiguous region, since **position on the unstructured dimension is unrelated to spatial position**. This means that subsetted -:class:`~iris.ugrid.MeshCoord`\s cannot be reliably interpreted -as intended, and subsetting a :class:`~iris.ugrid.MeshCoord` is +:class:`~iris.mesh.MeshCoord`\s cannot be reliably interpreted +as intended, and subsetting a :class:`~iris.mesh.MeshCoord` is therefore set to return an :class:`~iris.coords.AuxCoord` instead - breaking the link between :class:`~iris.cube.Cube` and -:class:`~iris.ugrid.MeshXY`: +:class:`~iris.mesh.MeshXY`: .. dropdown:: Code :icon: code @@ -595,10 +587,8 @@ below: >>> from geovista.geodesic import BBox >>> from iris import load_cube, sample_data_path >>> from iris.experimental.geovista import cube_to_polydata, extract_unstructured_region - >>> from iris.ugrid import PARSE_UGRID_ON_LOAD - >>> with PARSE_UGRID_ON_LOAD.context(): - ... sample_mesh_cube = load_cube(sample_data_path("mesh_C4_synthetic_float.nc")) + >>> sample_mesh_cube = load_cube(sample_data_path("mesh_C4_synthetic_float.nc")) >>> print(sample_mesh_cube) synthetic / (1) (-- : 96) Mesh coordinates: @@ -667,7 +657,6 @@ with the >>> from esmf_regrid.experimental.unstructured_scheme import MeshToGridESMFRegridder >>> from iris import load, load_cube - >>> from iris.ugrid import PARSE_UGRID_ON_LOAD # You could also download these files from github.com/SciTools/iris-test-data. >>> from iris.tests import get_data_path @@ -679,8 +668,7 @@ with the ... ) # Load a list of cubes defined on the same Mesh. - >>> with PARSE_UGRID_ON_LOAD.context(): - ... mesh_cubes = load(mesh_file) + >>> mesh_cubes = load(mesh_file) # Extract a specific cube. >>> mesh_cube1 = mesh_cubes.extract_cube("sea_surface_temperature") @@ -751,7 +739,7 @@ with the The initialisation process is computationally expensive so we use caching to improve performance. Once a regridder has been initialised, it can be used on any :class:`~iris.cube.Cube` which has been defined on the same -:class:`~iris.ugrid.MeshXY` (or on the same **grid** in the case of +:class:`~iris.mesh.MeshXY` (or on the same **grid** in the case of :class:`~esmf_regrid.experimental.unstructured_scheme.GridToMeshESMFRegridder`). Since calling a regridder is usually a lot faster than initialising, reusing regridders can save a lot of time. We can demonstrate the reuse of the @@ -819,19 +807,19 @@ Equality .. rubric:: |tagline: equality| -:class:`~iris.ugrid.MeshXY` comparison is supported, and comparing -two ':class:`~iris.ugrid.MeshXY`-:class:`~iris.cube.Cube`\s' will +:class:`~iris.mesh.MeshXY` comparison is supported, and comparing +two ':class:`~iris.mesh.MeshXY`-:class:`~iris.cube.Cube`\s' will include a comparison of the respective -:class:`~iris.ugrid.MeshXY`\es, with no extra action needed by the +:class:`~iris.mesh.MeshXY`\es, with no extra action needed by the user. .. note:: Keep an eye on memory demand when comparing large - :class:`~iris.ugrid.MeshXY`\es, but note that - :class:`~iris.ugrid.MeshXY`\ equality is enabled for lazy + :class:`~iris.mesh.MeshXY`\es, but note that + :class:`~iris.mesh.MeshXY`\ equality is enabled for lazy processing (:doc:`/userguide/real_and_lazy_data`), so if the - :class:`~iris.ugrid.MeshXY`\es being compared are lazy the + :class:`~iris.mesh.MeshXY`\es being compared are lazy the process will use less memory than their total size. Combining Cubes @@ -842,23 +830,23 @@ Combining Cubes Merging or concatenating :class:`~iris.cube.Cube`\s (described in :doc:`/userguide/merge_and_concat`) with two different -:class:`~iris.ugrid.MeshXY`\es is not possible - a +:class:`~iris.mesh.MeshXY`\es is not possible - a :class:`~iris.cube.Cube` must be associated with just a single -:class:`~iris.ugrid.MeshXY`, and merge/concatenate are not yet -capable of combining multiple :class:`~iris.ugrid.MeshXY`\es into +:class:`~iris.mesh.MeshXY`, and merge/concatenate are not yet +capable of combining multiple :class:`~iris.mesh.MeshXY`\es into one. :class:`~iris.cube.Cube`\s that include -:class:`~iris.ugrid.MeshCoord`\s can still be merged/concatenated -on dimensions other than the :meth:`~iris.cube.Cube.mesh_dim`, since such -:class:`~iris.cube.Cube`\s will by definition share the same -:class:`~iris.ugrid.MeshXY`. +:class:`~iris.mesh.MeshCoord`\s can still be merged/concatenated +on dimensions other than the :meth:`~iris.cube.Cube.mesh_dim`, but only if their +:class:`~iris.cube.Cube.mesh`\es are *equal* (in practice, identical, even to +matching ``var_name``\s). .. seealso:: You may wish to investigate - :func:`iris.ugrid.recombine_submeshes`, which can be used - for a very specific type of :class:`~iris.ugrid.MeshXY` + :func:`iris.mesh.recombine_submeshes`, which can be used + for a very specific type of :class:`~iris.mesh.MeshXY` combination not detailed here. Arithmetic @@ -869,7 +857,7 @@ Arithmetic Cube Arithmetic (described in :doc:`/userguide/cube_maths`) has been extended to handle :class:`~iris.cube.Cube`\s that include -:class:`~iris.ugrid.MeshCoord`\s, and hence have a ``cube.mesh``. +:class:`~iris.mesh.MeshCoord`\s, and hence have a ``cube.mesh``. Cubes with meshes can be combined in arithmetic operations like "ordinary" cubes. They can combine with other cubes without that mesh diff --git a/docs/src/further_topics/ugrid/partner_packages.rst b/docs/src/further_topics/ugrid/partner_packages.rst index 5dea58b752..f69546446c 100644 --- a/docs/src/further_topics/ugrid/partner_packages.rst +++ b/docs/src/further_topics/ugrid/partner_packages.rst @@ -58,7 +58,7 @@ PyVista is described as "VTK for humans" - VTK is a very powerful toolkit for working with meshes, and PyVista brings that power into the Python ecosystem. GeoVista in turn makes it easy to use PyVista specifically for cartographic work, designed from the start with the Iris -:class:`~iris.ugrid.MeshXY` in mind. +:class:`~iris.mesh.MeshXY` in mind. Applications ------------ diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst index 2976ae56ca..e04b832e23 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/latest.rst @@ -88,6 +88,13 @@ This document explains the changes made to Iris for this release :meth:`~iris.experimental.ugrid.Mesh.coord`, :meth:`~iris.experimental.ugrid.Mesh.coords` and :meth:`~iris.experimental.ugrid.Mesh.remove_coords`. (:pull:`6055`) +#. `@pp-mo`_ moved all the mesh API from the :mod:`iris.experimental.ugrid` module to + to :mod:`iris.mesh`, making this public supported API. Note that the + :class:`iris.experimental.ugrid.Mesh` class is renamed as :class:`iris.mesh.MeshXY`, + to allow for possible future mesh types with different properties to exist as + subclasses of a common generic :class:`~iris.mesh.components.Mesh` class. + (:issue:`6057`, :pull:`6061`, :pull:`6077`) + 🚀 Performance Enhancements =========================== diff --git a/lib/iris/analysis/_regrid.py b/lib/iris/analysis/_regrid.py index 31ceafb025..431871de2c 100644 --- a/lib/iris/analysis/_regrid.py +++ b/lib/iris/analysis/_regrid.py @@ -997,8 +997,7 @@ def _create_cube(data, src, src_dims, tgt_coords, num_tgt_dims, regrid_callback) src_dims : tuple of int The dimensions of the X and Y coordinate within the source Cube. tgt_coords : tuple of :class:`iris.coords.Coord - Either two 1D :class:`iris.coords.DimCoord`, two 1D - :class:`iris.ugrid.DimCoord` or two n-D + Either two 1D :class:`iris.coords.DimCoord`, or two n-D :class:`iris.coords.AuxCoord` representing the new grid's X and Y coordinates. num_tgt_dims : int diff --git a/lib/iris/common/metadata.py b/lib/iris/common/metadata.py index c705054725..6f3e455b4d 100644 --- a/lib/iris/common/metadata.py +++ b/lib/iris/common/metadata.py @@ -1334,6 +1334,357 @@ def equal(self, other, lenient=None): return super().equal(other, lenient=lenient) +class ConnectivityMetadata(BaseMetadata): + """Metadata container for a :class:`~iris.mesh.Connectivity`.""" + + # The "location_axis" member is stateful only, and does not participate in + # lenient/strict equivalence. + _members = ("cf_role", "start_index", "location_axis") + + __slots__ = () + + @wraps(BaseMetadata.__eq__, assigned=("__doc__",), updated=()) + @lenient_service + def __eq__(self, other): + return super().__eq__(other) + + def _combine_lenient(self, other): + """Perform lenient combination of metadata members for connectivities. + + Parameters + ---------- + other : ConnectivityMetadata + The other connectivity metadata participating in the lenient + combination. + + Returns + ------- + A list of combined metadata member values. + + """ + + # Perform "strict" combination for "cf_role", "start_index", "location_axis". + def func(field): + left = getattr(self, field) + right = getattr(other, field) + return left if left == right else None + + # Note that, we use "_members" not "_fields". + values = [func(field) for field in ConnectivityMetadata._members] + # Perform lenient combination of the other parent members. + result = super()._combine_lenient(other) + result.extend(values) + + return result + + def _compare_lenient(self, other): + """Perform lenient equality of metadata members for connectivities. + + Parameters + ---------- + other : ConnectivityMetadata + The other connectivity metadata participating in the lenient + comparison. + + Returns + ------- + bool + + """ + # Perform "strict" comparison for "cf_role", "start_index". + # The "location_axis" member is not part of lenient equivalence. + members = filter( + lambda member: member != "location_axis", + ConnectivityMetadata._members, + ) + result = all( + [getattr(self, field) == getattr(other, field) for field in members] + ) + if result: + # Perform lenient comparison of the other parent members. + result = super()._compare_lenient(other) + + return result + + def _difference_lenient(self, other): + """Perform lenient difference of metadata members for connectivities. + + Parameters + ---------- + other : ConnectivityMetadata + The other connectivity metadata participating in the lenient + difference. + + Returns + ------- + A list of difference metadata member values. + + """ + + # Perform "strict" difference for "cf_role", "start_index", "location_axis". + def func(field): + left = getattr(self, field) + right = getattr(other, field) + return None if left == right else (left, right) + + # Note that, we use "_members" not "_fields". + values = [func(field) for field in ConnectivityMetadata._members] + # Perform lenient difference of the other parent members. + result = super()._difference_lenient(other) + result.extend(values) + + return result + + @wraps(BaseMetadata.combine, assigned=("__doc__",), updated=()) + @lenient_service + def combine(self, other, lenient=None): + return super().combine(other, lenient=lenient) + + @wraps(BaseMetadata.difference, assigned=("__doc__",), updated=()) + @lenient_service + def difference(self, other, lenient=None): + return super().difference(other, lenient=lenient) + + @wraps(BaseMetadata.equal, assigned=("__doc__",), updated=()) + @lenient_service + def equal(self, other, lenient=None): + return super().equal(other, lenient=lenient) + + +class MeshMetadata(BaseMetadata): + """Metadata container for a :class:`~iris.mesh.MeshXY`.""" + + # The node_dimension", "edge_dimension" and "face_dimension" members are + # stateful only; they not participate in lenient/strict equivalence. + _members = ( + "topology_dimension", + "node_dimension", + "edge_dimension", + "face_dimension", + ) + + __slots__ = () + + @wraps(BaseMetadata.__eq__, assigned=("__doc__",), updated=()) + @lenient_service + def __eq__(self, other): + return super().__eq__(other) + + def _combine_lenient(self, other): + """Perform lenient combination of metadata members for meshes. + + Parameters + ---------- + other : MeshMetadata + The other mesh metadata participating in the lenient + combination. + + Returns + ------- + A list of combined metadata member values. + + """ + + # Perform "strict" combination for "topology_dimension", + # "node_dimension", "edge_dimension" and "face_dimension". + def func(field): + left = getattr(self, field) + right = getattr(other, field) + return left if left == right else None + + # Note that, we use "_members" not "_fields". + values = [func(field) for field in MeshMetadata._members] + # Perform lenient combination of the other parent members. + result = super()._combine_lenient(other) + result.extend(values) + + return result + + def _compare_lenient(self, other): + """Perform lenient equality of metadata members for meshes. + + Parameters + ---------- + other : MeshMetadata + The other mesh metadata participating in the lenient + comparison. + + Returns + ------- + bool + + """ + # Perform "strict" comparison for "topology_dimension". + # "node_dimension", "edge_dimension" and "face_dimension" are not part + # of lenient equivalence at all. + result = self.topology_dimension == other.topology_dimension + if result: + # Perform lenient comparison of the other parent members. + result = super()._compare_lenient(other) + + return result + + def _difference_lenient(self, other): + """Perform lenient difference of metadata members for meshes. + + Parameters + ---------- + other : MeshMetadata + The other mesh metadata participating in the lenient + difference. + + Returns + ------- + A list of difference metadata member values. + + """ + + # Perform "strict" difference for "topology_dimension", + # "node_dimension", "edge_dimension" and "face_dimension". + def func(field): + left = getattr(self, field) + right = getattr(other, field) + return None if left == right else (left, right) + + # Note that, we use "_members" not "_fields". + values = [func(field) for field in MeshMetadata._members] + # Perform lenient difference of the other parent members. + result = super()._difference_lenient(other) + result.extend(values) + + return result + + @wraps(BaseMetadata.combine, assigned=("__doc__",), updated=()) + @lenient_service + def combine(self, other, lenient=None): + return super().combine(other, lenient=lenient) + + @wraps(BaseMetadata.difference, assigned=("__doc__",), updated=()) + @lenient_service + def difference(self, other, lenient=None): + return super().difference(other, lenient=lenient) + + @wraps(BaseMetadata.equal, assigned=("__doc__",), updated=()) + @lenient_service + def equal(self, other, lenient=None): + return super().equal(other, lenient=lenient) + + +class MeshCoordMetadata(BaseMetadata): + """Metadata container for a :class:`~iris.coords.MeshCoord`.""" + + _members = ("location", "axis") + # NOTE: in future, we may add 'mesh' as part of this metadata, + # as the MeshXY seems part of the 'identity' of a MeshCoord. + # For now we omit it, particularly as we don't yet implement MeshXY.__eq__. + # + # Thus, for now, the MeshCoord class will need to handle 'mesh' explicitly + # in identity / comparison, but in future that may be simplified. + + __slots__ = () + + @wraps(BaseMetadata.__eq__, assigned=("__doc__",), updated=()) + @lenient_service + def __eq__(self, other): + return super().__eq__(other) + + def _combine_lenient(self, other): + """Perform lenient combination of metadata members for MeshCoord. + + Parameters + ---------- + other : MeshCoordMetadata + The other metadata participating in the lenient combination. + + Returns + ------- + A list of combined metadata member values. + + """ + + # It is actually "strict" : return None except where members are equal. + def func(field): + left = getattr(self, field) + right = getattr(other, field) + return left if left == right else None + + # Note that, we use "_members" not "_fields". + values = [func(field) for field in self._members] + # Perform lenient combination of the other parent members. + result = super()._combine_lenient(other) + result.extend(values) + + return result + + def _compare_lenient(self, other): + """Perform lenient equality of metadata members for MeshCoord. + + Parameters + ---------- + other : MeshCoordMetadata + The other metadata participating in the lenient comparison. + + Returns + ------- + bool + + """ + # Perform "strict" comparison for the MeshCoord specific members + # 'location', 'axis' : for equality, they must all match. + result = all( + [getattr(self, field) == getattr(other, field) for field in self._members] + ) + if result: + # Perform lenient comparison of the other parent members. + result = super()._compare_lenient(other) + + return result + + def _difference_lenient(self, other): + """Perform lenient difference of metadata members for MeshCoord. + + Parameters + ---------- + other : MeshCoordMetadata + The other MeshCoord metadata participating in the lenient + difference. + + Returns + ------- + A list of different metadata member values. + + """ + + # Perform "strict" difference for location / axis. + def func(field): + left = getattr(self, field) + right = getattr(other, field) + return None if left == right else (left, right) + + # Note that, we use "_members" not "_fields". + values = [func(field) for field in self._members] + # Perform lenient difference of the other parent members. + result = super()._difference_lenient(other) + result.extend(values) + + return result + + @wraps(BaseMetadata.combine, assigned=("__doc__",), updated=()) + @lenient_service + def combine(self, other, lenient=None): + return super().combine(other, lenient=lenient) + + @wraps(BaseMetadata.difference, assigned=("__doc__",), updated=()) + @lenient_service + def difference(self, other, lenient=None): + return super().difference(other, lenient=lenient) + + @wraps(BaseMetadata.equal, assigned=("__doc__",), updated=()) + @lenient_service + def equal(self, other, lenient=None): + return super().equal(other, lenient=lenient) + + def metadata_filter( instances, item=None, @@ -1601,46 +1952,54 @@ def metadata_manager_factory(cls, **kwargs): #: Convenience collection of lenient metadata combine services. -# TODO: change lists back to tuples once CellMeasureMetadata is re-integrated -# here (currently in iris.ugrid). -# TODO: complete iris.ugrid replacement -SERVICES_COMBINE = [ +SERVICES_COMBINE = ( AncillaryVariableMetadata.combine, BaseMetadata.combine, CellMeasureMetadata.combine, + ConnectivityMetadata.combine, CoordMetadata.combine, CubeMetadata.combine, DimCoordMetadata.combine, -] + MeshCoordMetadata.combine, + MeshMetadata.combine, +) #: Convenience collection of lenient metadata difference services. -SERVICES_DIFFERENCE = [ +SERVICES_DIFFERENCE = ( AncillaryVariableMetadata.difference, BaseMetadata.difference, CellMeasureMetadata.difference, + ConnectivityMetadata.difference, CoordMetadata.difference, CubeMetadata.difference, DimCoordMetadata.difference, -] + MeshCoordMetadata.difference, + MeshMetadata.difference, +) #: Convenience collection of lenient metadata equality services. -SERVICES_EQUAL = [ +SERVICES_EQUAL = ( AncillaryVariableMetadata.__eq__, AncillaryVariableMetadata.equal, BaseMetadata.__eq__, BaseMetadata.equal, CellMeasureMetadata.__eq__, CellMeasureMetadata.equal, + ConnectivityMetadata.__eq__, + ConnectivityMetadata.equal, CoordMetadata.__eq__, CoordMetadata.equal, CubeMetadata.__eq__, CubeMetadata.equal, DimCoordMetadata.__eq__, DimCoordMetadata.equal, -] - + MeshCoordMetadata.__eq__, + MeshCoordMetadata.equal, + MeshMetadata.__eq__, + MeshMetadata.equal, +) #: Convenience collection of lenient metadata services. SERVICES = SERVICES_COMBINE + SERVICES_DIFFERENCE + SERVICES_EQUAL diff --git a/lib/iris/common/resolve.py b/lib/iris/common/resolve.py index d678d13cf8..87ad05791b 100644 --- a/lib/iris/common/resolve.py +++ b/lib/iris/common/resolve.py @@ -71,7 +71,7 @@ class _PreparedItem: axis: Any = None def create_coord(self, metadata): - from iris.ugrid.mesh import MeshCoord + from iris.mesh import MeshCoord if issubclass(self.container, MeshCoord): # Make a MeshCoord, for which we have mesh/location/axis. @@ -741,7 +741,7 @@ def _create_prepared_item( if container is None: container = type(coord) - from iris.ugrid.mesh import MeshCoord + from iris.mesh import MeshCoord if issubclass(container, MeshCoord): # Build a prepared-item to make a MeshCoord. diff --git a/lib/iris/coords.py b/lib/iris/coords.py index 40b131e496..a56c13d9af 100644 --- a/lib/iris/coords.py +++ b/lib/iris/coords.py @@ -804,6 +804,9 @@ def xml_element(self, doc): :class:`_DimensionalMetadata`. """ + # deferred import to avoid possible circularity + from iris.mesh import Connectivity + # Create the XML element as the camelCaseEquivalent of the # class name. element_name = type(self).__name__ @@ -843,10 +846,7 @@ def xml_element(self, doc): # otherwise. if isinstance(self, Coord): values_term = "points" - # TODO: replace with isinstance(self, Connectivity) once Connectivity - # is re-integrated here (currently in iris.ugrid). - # TODO: complete iris.ugrid replacement - elif hasattr(self, "indices"): + elif isinstance(self, Connectivity): values_term = "indices" else: values_term = "data" diff --git a/lib/iris/cube.py b/lib/iris/cube.py index eb4b82fd73..54e086937d 100644 --- a/lib/iris/cube.py +++ b/lib/iris/cube.py @@ -2088,7 +2088,7 @@ def coords( If ``None``, returns all coordinates. mesh_coords : optional Set to ``True`` to return only coordinates which are - :class:`~iris.ugrid.MeshCoord`\'s. + :class:`~iris.mesh.MeshCoord`\'s. Set to ``False`` to return only non-mesh coordinates. If ``None``, returns all coordinates. @@ -2115,7 +2115,7 @@ def coords( if mesh_coords is not None: # Select on mesh or non-mesh. mesh_coords = bool(mesh_coords) - # Use duck typing to avoid importing from iris.ugrid, + # Use duck typing to avoid importing from iris.mesh, # which could be a circular import. if mesh_coords: # *only* MeshCoords @@ -2245,7 +2245,7 @@ def coord( If ``None``, returns all coordinates. mesh_coords : optional Set to ``True`` to return only coordinates which are - :class:`~iris.ugrid.MeshCoord`\'s. + :class:`~iris.mesh.MeshCoord`\'s. Set to ``False`` to return only non-mesh coordinates. If ``None``, returns all coordinates. @@ -2365,18 +2365,18 @@ def _any_meshcoord(self): @property def mesh(self): - r"""Return the unstructured :class:`~iris.ugrid.MeshXY` associated with the cube. + r"""Return the unstructured :class:`~iris.mesh.MeshXY` associated with the cube. - Return the unstructured :class:`~iris.ugrid.MeshXY` + Return the unstructured :class:`~iris.mesh.MeshXY` associated with the cube, if the cube has any - :class:`~iris.ugrid.MeshCoord`, + :class:`~iris.mesh.MeshCoord`, or ``None`` if it has none. Returns ------- - :class:`iris.ugrid.mesh.MeshXY` or None + :class:`iris.mesh.MeshXY` or None The mesh of the cube - :class:`~iris.ugrid.MeshCoord`'s, + :class:`~iris.mesh.MeshCoord`'s, or ``None``. """ @@ -2390,14 +2390,14 @@ def location(self): r"""Return the mesh "location" of the cube data. Return the mesh "location" of the cube data, if the cube has any - :class:`~iris.ugrid.MeshCoord`, + :class:`~iris.mesh.MeshCoord`, or ``None`` if it has none. Returns ------- str or None The mesh location of the cube - :class:`~iris.ugrid.MeshCoords` + :class:`~iris.mesh.MeshCoords` (i.e. one of 'face' / 'edge' / 'node'), or ``None``. """ @@ -2410,14 +2410,14 @@ def mesh_dim(self): r"""Return the cube dimension of the mesh. Return the cube dimension of the mesh, if the cube has any - :class:`~iris.ugrid.MeshCoord`, + :class:`~iris.mesh.MeshCoord`, or ``None`` if it has none. Returns ------- int or None The cube dimension which the cube - :class:`~iris.ugrid.MeshCoord` map to, + :class:`~iris.mesh.MeshCoord` map to, or ``None``. """ diff --git a/lib/iris/experimental/geovista.py b/lib/iris/experimental/geovista.py index 690e19d543..07413f1529 100644 --- a/lib/iris/experimental/geovista.py +++ b/lib/iris/experimental/geovista.py @@ -8,7 +8,7 @@ from geovista.common import VTK_CELL_IDS, VTK_POINT_IDS from iris.exceptions import CoordinateNotFoundError -from iris.ugrid import MeshXY +from iris.mesh import MeshXY def _get_coord(cube, axis): @@ -52,7 +52,7 @@ def cube_to_polydata(cube, **kwargs): If a :class:`~iris.cube.Cube` with too many dimensions is passed. Only the horizontal data can be represented, meaning a 2D Cube, or 1D Cube if the horizontal space is described by - :class:`~iris.ugrid.MeshCoord`\ s. + :class:`~iris.mesh.MeshCoord`\ s. Examples -------- diff --git a/lib/iris/experimental/ugrid.py b/lib/iris/experimental/ugrid.py index bbc8ef93b3..6e036ad96e 100644 --- a/lib/iris/experimental/ugrid.py +++ b/lib/iris/experimental/ugrid.py @@ -5,7 +5,7 @@ """Legacy import location for mesh support. -See :mod:`iris.ugrid` for the new, correct import location. +See :mod:`iris.mesh` for the new, correct import location. Notes ----- @@ -17,37 +17,39 @@ experimental. .. deprecated:: 3.10 - All the former :mod:`iris.experimental.ugrid` modules have been relocated to - :mod:`iris.ugrid` and its submodules. Please re-write code to import from the new - module path. - This import path alios is provided for backwards compatibility, but will be removed + All the former :mod:`iris.experimental.mesh` modules have been relocated to + :mod:`iris.mesh` and its submodules. Please re-write code to import from the new + module path, and replace any 'iris.experimental.ugrid.Mesh' with + 'iris.mesh.MeshXY'. + + This import path alias is provided for backwards compatibility, but will be removed in a future release : N.B. removing this does **not** need to wait for a major - release, since the former API was experimental. + release, since the API is experimental. """ from __future__ import annotations +from contextlib import contextmanager +import threading + from .._deprecation import warn_deprecated -from ..ugrid.load import PARSE_UGRID_ON_LOAD, load_mesh, load_meshes -from ..ugrid.mesh import Connectivity as _Connectivity -from ..ugrid.mesh import MeshCoord as _MeshCoord -from ..ugrid.mesh import MeshXY as _MeshXY -from ..ugrid.save import save_mesh -from ..ugrid.utils import recombine_submeshes +from ..mesh import Connectivity as _Connectivity +from ..mesh import MeshCoord as _MeshCoord +from ..mesh import MeshXY as _MeshXY +from ..mesh import load_mesh, load_meshes, recombine_submeshes, save_mesh # NOTE: publishing the original Mesh, MeshCoord and Connectivity here causes a Sphinx # Sphinx warning, E.G.: -# "WARNING: duplicate object description of iris.ugrid.mesh.Mesh, other instance -# in generated/api/iris.experimental.ugrid, use :no-index: for one of them" +# "WARNING: duplicate object description of iris.mesh.Mesh, other instance +# in generated/api/iris.experimental.mesh, use :no-index: for one of them" # For some reason, this only happens for the classes, and not the functions. # # This is a fatal problem, i.e. breaks the build since we are building with -W. # We couldn't fix this with "autodoc_suppress_warnings", so the solution for now is to # wrap the classes. Which is really ugly. -# TODO: remove this when we remove iris.experimental.ugrid -class MeshXY(_MeshXY): +class Mesh(_MeshXY): pass @@ -59,10 +61,85 @@ class Connectivity(_Connectivity): pass +class ParseUGridOnLoad(threading.local): + def __init__(self): + """Thead-safe state to enable UGRID-aware NetCDF loading. + + A flag for dictating whether to use the experimental UGRID-aware + version of Iris NetCDF loading. Object is thread-safe. + + Use via the run-time switch + :const:`~iris.mesh.load.PARSE_UGRID_ON_LOAD`. + Use :meth:`context` to temporarily activate. + + Notes + ----- + .. deprecated:: 1.10 + Do not use -- due to be removed at next major release : + UGRID loading is now **always** active for files containing a UGRID mesh. + + """ + + def __bool__(self): + return True + + @contextmanager + def context(self): + """Activate UGRID-aware NetCDF loading. + + Use the standard Iris loading API while within the context manager. If + the loaded file(s) include any UGRID content, this will be parsed and + attached to the resultant cube(s) accordingly. + + Use via the run-time switch + :const:`~iris.mesh.load.PARSE_UGRID_ON_LOAD`. + + For example:: + + with PARSE_UGRID_ON_LOAD.context(): + my_cube_list = iris.load([my_file_path, my_file_path2], + constraint=my_constraint, + callback=my_callback) + + Notes + ----- + .. deprecated:: 1.10 + Do not use -- due to be removed at next major release : + UGRID loading is now **always** active for files containing a UGRID mesh. + + Examples + -------- + Replace usage, for example: + + .. code-block:: python + + with iris.experimental.mesh.PARSE_UGRID_ON_LOAD.context(): + mesh_cubes = iris.load(path) + + with: + + .. code-block:: python + + mesh_cubes = iris.load(path) + + """ + wmsg = ( + "iris.experimental.mesh.load.PARSE_UGRID_ON_LOAD has been deprecated " + "and will be removed. Please remove all uses : these are no longer needed, " + "as UGRID loading is now applied to any file containing a mesh." + ) + warn_deprecated(wmsg) + yield + + +#: Run-time switch for experimental UGRID-aware NetCDF loading. See :class:`~iris.mesh.load.ParseUGridOnLoad`. +PARSE_UGRID_ON_LOAD = ParseUGridOnLoad() + + __all__ = [ "Connectivity", + "Mesh", "MeshCoord", - "MeshXY", "PARSE_UGRID_ON_LOAD", "load_mesh", "load_meshes", @@ -71,7 +148,8 @@ class Connectivity(_Connectivity): ] warn_deprecated( - "All the former :mod:`iris.experimental.ugrid` modules have been relocated to " - "module 'iris.ugrid' and its submodules. " - "Please re-write code to import from the new module path." + "All the former :mod:`iris.experimental.mesh` modules have been relocated to " + "module 'iris.mesh' and its submodules. " + "Please re-write code to import from the new module path, and replace any " + "'iris.experimental.ugrid.Mesh' with 'iris.mesh.MeshXY'." ) diff --git a/lib/iris/fileformats/cf.py b/lib/iris/fileformats/cf.py index b4c754e4a6..556642003a 100644 --- a/lib/iris/fileformats/cf.py +++ b/lib/iris/fileformats/cf.py @@ -25,8 +25,10 @@ import numpy.ma as ma from iris.fileformats.netcdf import _thread_safe_nc +from iris.mesh.components import Connectivity import iris.util import iris.warnings +from iris.warnings import IrisCfLabelVarWarning, IrisCfMissingVarWarning # # CF parse pattern common to both formula terms and measure CF variables. @@ -889,6 +891,226 @@ def identify(cls, variables, ignore=None, target=None, warn=True): return result +class CFUGridConnectivityVariable(CFVariable): + """A CF_UGRID connectivity variable. + + A CF_UGRID connectivity variable points to an index variable identifying + for every element (edge/face/volume) the indices of its corner nodes. The + connectivity array will thus be a matrix of size n-elements x n-corners. + For the indexing one may use either 0- or 1-based indexing; the convention + used should be specified using a ``start_index`` attribute to the index + variable. + + For face elements: the corner nodes should be specified in anticlockwise + direction as viewed from above. For volume elements: use the + additional attribute ``volume_shape_type`` which points to a flag variable + that specifies for every volume its shape. + + Identified by a CF-netCDF variable attribute equal to any one of the values + in :attr:`~iris.mesh.Connectivity.UGRID_CF_ROLES`. + + .. seealso:: + + The UGRID Conventions, https://ugrid-conventions.github.io/ugrid-conventions/ + + """ + + cf_identity = NotImplemented + cf_identities = Connectivity.UGRID_CF_ROLES + + @classmethod + def identify(cls, variables, ignore=None, target=None, warn=True): + result = {} + ignore, target = cls._identify_common(variables, ignore, target) + + # Identify all CF-UGRID connectivity variables. + for nc_var_name, nc_var in target.items(): + # Check for connectivity variable references, iterating through + # the valid cf roles. + for identity in cls.cf_identities: + nc_var_att = getattr(nc_var, identity, None) + + if nc_var_att is not None: + # UGRID only allows for one of each connectivity cf role. + name = nc_var_att.strip() + if name not in ignore: + if name not in variables: + message = ( + f"Missing CF-UGRID connectivity variable " + f"{name}, referenced by netCDF variable " + f"{nc_var_name}" + ) + if warn: + warnings.warn(message, category=IrisCfMissingVarWarning) + else: + # Restrict to non-string type i.e. not a + # CFLabelVariable. + if not _is_str_dtype(variables[name]): + result[name] = CFUGridConnectivityVariable( + name, variables[name] + ) + else: + message = ( + f"Ignoring variable {name}, identified " + f"as a CF-UGRID connectivity - is a " + f"CF-netCDF label variable." + ) + if warn: + warnings.warn( + message, category=IrisCfLabelVarWarning + ) + + return result + + +class CFUGridAuxiliaryCoordinateVariable(CFVariable): + """A CF-UGRID auxiliary coordinate variable. + + A CF-UGRID auxiliary coordinate variable is a CF-netCDF auxiliary + coordinate variable representing the element (node/edge/face/volume) + locations (latitude, longitude or other spatial coordinates, and optional + elevation or other coordinates). These auxiliary coordinate variables will + have length n-elements. + + For elements other than nodes, these auxiliary coordinate variables may + have in turn a ``bounds`` attribute that specifies the bounding coordinates + of the element (thereby duplicating the data in the ``node_coordinates`` + variables). + + Identified by the CF-netCDF variable attribute + ``node_``/``edge_``/``face_``/``volume_coordinates``. + + .. seealso:: + + The UGRID Conventions, https://ugrid-conventions.github.io/ugrid-conventions/ + + """ + + cf_identity = NotImplemented + cf_identities = [ + "node_coordinates", + "edge_coordinates", + "face_coordinates", + "volume_coordinates", + ] + + @classmethod + def identify(cls, variables, ignore=None, target=None, warn=True): + result = {} + ignore, target = cls._identify_common(variables, ignore, target) + + # Identify any CF-UGRID-relevant auxiliary coordinate variables. + for nc_var_name, nc_var in target.items(): + # Check for UGRID auxiliary coordinate variable references. + for identity in cls.cf_identities: + nc_var_att = getattr(nc_var, identity, None) + + if nc_var_att is not None: + for name in nc_var_att.split(): + if name not in ignore: + if name not in variables: + message = ( + f"Missing CF-netCDF auxiliary coordinate " + f"variable {name}, referenced by netCDF " + f"variable {nc_var_name}" + ) + if warn: + warnings.warn( + message, + category=IrisCfMissingVarWarning, + ) + else: + # Restrict to non-string type i.e. not a + # CFLabelVariable. + if not _is_str_dtype(variables[name]): + result[name] = CFUGridAuxiliaryCoordinateVariable( + name, variables[name] + ) + else: + message = ( + f"Ignoring variable {name}, " + f"identified as a CF-netCDF " + f"auxiliary coordinate - is a " + f"CF-netCDF label variable." + ) + if warn: + warnings.warn( + message, + category=IrisCfLabelVarWarning, + ) + + return result + + +class CFUGridMeshVariable(CFVariable): + """A CF-UGRID mesh variable is a dummy variable for storing topology information as attributes. + + A CF-UGRID mesh variable is a dummy variable for storing topology + information as attributes. The mesh variable has the ``cf_role`` + 'mesh_topology'. + + The UGRID conventions describe define the mesh topology as the + interconnection of various geometrical elements of the mesh. The pure + interconnectivity is independent of georeferencing the individual + geometrical elements, but for the practical applications for which the + UGRID CF extension is defined, coordinate data will always be added. + + Identified by the CF-netCDF variable attribute 'mesh'. + + .. seealso:: + + The UGRID Conventions, https://ugrid-conventions.github.io/ugrid-conventions/ + + """ + + cf_identity = "mesh" + + @classmethod + def identify(cls, variables, ignore=None, target=None, warn=True): + result = {} + ignore, target = cls._identify_common(variables, ignore, target) + + # Identify all CF-UGRID mesh variables. + all_vars = target == variables + for nc_var_name, nc_var in target.items(): + if all_vars: + # SPECIAL BEHAVIOUR FOR MESH VARIABLES. + # We are looking for all mesh variables. Check if THIS variable + # is a mesh using its own attributes. + if getattr(nc_var, "cf_role", "") == "mesh_topology": + result[nc_var_name] = CFUGridMeshVariable(nc_var_name, nc_var) + + # Check for mesh variable references. + nc_var_att = getattr(nc_var, cls.cf_identity, None) + + if nc_var_att is not None: + # UGRID only allows for 1 mesh per variable. + name = nc_var_att.strip() + if name not in ignore: + if name not in variables: + message = ( + f"Missing CF-UGRID mesh variable {name}, " + f"referenced by netCDF variable {nc_var_name}" + ) + if warn: + warnings.warn(message, category=IrisCfMissingVarWarning) + else: + # Restrict to non-string type i.e. not a + # CFLabelVariable. + if not _is_str_dtype(variables[name]): + result[name] = CFUGridMeshVariable(name, variables[name]) + else: + message = ( + f"Ignoring variable {name}, identified as a " + f"CF-UGRID mesh - is a CF-netCDF label " + f"variable." + ) + if warn: + warnings.warn(message, category=IrisCfLabelVarWarning) + + return result + + ################################################################################ class CFGroup(MutableMapping): """Collection of 'NetCDF CF Metadata Conventions variables and netCDF global attributes. @@ -980,12 +1202,30 @@ def non_data_variable_names(self): self.grid_mappings, self.labels, self.cell_measures, + self.connectivities, + self.ugrid_coords, + self.meshes, ) result = set() for variable in non_data_variables: result |= set(variable) return result + @property + def connectivities(self): + """Collection of CF-UGRID connectivity variables.""" + return self._cf_getter(CFUGridConnectivityVariable) + + @property + def ugrid_coords(self): + """Collection of CF-UGRID-relevant auxiliary coordinate variables.""" + return self._cf_getter(CFUGridAuxiliaryCoordinateVariable) + + @property + def meshes(self): + """Collection of CF-UGRID mesh variables.""" + return self._cf_getter(CFUGridMeshVariable) + def keys(self): """Return the names of all the CF-netCDF variables in the group.""" return self._cf_variables.keys() @@ -1054,10 +1294,11 @@ class CFReader: CFGridMappingVariable, CFLabelVariable, CFMeasureVariable, + CFUGridConnectivityVariable, + CFUGridAuxiliaryCoordinateVariable, + CFUGridMeshVariable, ) - # TODO: remove once iris.ugrid.CFUGridReader is folded in. - # TODO: complete iris.ugrid replacement CFGroup = CFGroup def __init__(self, file_source, warn=False, monotonic=False): @@ -1174,10 +1415,7 @@ def _build_cf_groups(self): """Build the first order relationships between CF-netCDF variables.""" def _build(cf_variable): - # TODO: isinstance(cf_variable, UGridMeshVariable) - # UGridMeshVariable currently in iris.ugrid - circular import. - # TODO: complete iris.ugrid replacement - is_mesh_var = cf_variable.cf_identity == "mesh" + is_mesh_var = isinstance(cf_variable, CFUGridMeshVariable) ugrid_coord_names = [] ugrid_coords = getattr(self.cf_group, "ugrid_coords", None) if ugrid_coords is not None: diff --git a/lib/iris/fileformats/netcdf/loader.py b/lib/iris/fileformats/netcdf/loader.py index 2bdfed9fff..55d0a88b79 100644 --- a/lib/iris/fileformats/netcdf/loader.py +++ b/lib/iris/fileformats/netcdf/loader.py @@ -578,13 +578,11 @@ def load_cubes(file_sources, callback=None, constraints=None): Generator of loaded NetCDF :class:`iris.cube.Cube`. """ - # TODO: rationalise UGRID/mesh handling once iris.ugrid is folded - # TODO: complete iris.ugrid replacement - # into standard behaviour. # Deferred import to avoid circular imports. + from iris.fileformats.cf import CFReader from iris.io import run_callback - from iris.ugrid.cf import CFUGridReader - from iris.ugrid.load import ( + + from .ugrid_load import ( _build_mesh_coords, _meshes_from_cf, ) @@ -600,7 +598,7 @@ def load_cubes(file_sources, callback=None, constraints=None): for file_source in file_sources: # Ingest the file. At present may be a filepath or an open netCDF4.Dataset. - with CFUGridReader(file_source) as cf: + with CFReader(file_source) as cf: meshes = _meshes_from_cf(cf) # Process each CF data variable. @@ -684,8 +682,8 @@ def __init__(self, var_dim_chunksizes=None): :class:`~iris.coords.AncillaryVariable` etc. This can be overridden, if required, by variable-specific settings. - For this purpose, :class:`~iris.ugrid.mesh.MeshCoord` and - :class:`~iris.ugrid.mesh.Connectivity` are not + For this purpose, :class:`~iris.mesh.MeshCoord` and + :class:`~iris.mesh.Connectivity` are not :class:`~iris.cube.Cube` components, and chunk control on a :class:`~iris.cube.Cube` data-variable will not affect them. diff --git a/lib/iris/fileformats/netcdf/saver.py b/lib/iris/fileformats/netcdf/saver.py index 1981091717..cfc69143ae 100644 --- a/lib/iris/fileformats/netcdf/saver.py +++ b/lib/iris/fileformats/netcdf/saver.py @@ -271,7 +271,7 @@ def _setncattr(variable, name, attribute): return variable.setncattr(name, attribute) -# NOTE : this matches :class:`iris.ugrid.mesh.MeshXY.ELEMENTS`, +# NOTE : this matches :class:`iris.mesh.MeshXY.ELEMENTS`, # but in the preferred order for coord/connectivity variables in the file. MESH_ELEMENTS = ("node", "edge", "face") @@ -766,7 +766,7 @@ def _add_mesh(self, cube_or_mesh): Parameters ---------- - cube_or_mesh : :class:`iris.cube.Cube` or :class:`iris.ugrid.MeshXY` + cube_or_mesh : :class:`iris.cube.Cube` or :class:`iris.mesh.MeshXY` The Cube or Mesh being saved to the netCDF file. Returns @@ -941,7 +941,7 @@ def _add_aux_coords(self, cube, cf_var_cube, dimension_names): dimension_names : list Names associated with the dimensions of the cube. """ - from iris.ugrid.mesh import ( + from iris.mesh.components import ( MeshEdgeCoords, MeshFaceCoords, MeshNodeCoords, @@ -1120,7 +1120,7 @@ def _get_dim_names(self, cube_or_mesh): Parameters ---------- - cube_or_mesh : :class:`iris.cube.Cube` or :class:`iris.ugrid.MeshXY` + cube_or_mesh : :class:`iris.cube.Cube` or :class:`iris.mesh.MeshXY` The Cube or Mesh being saved to the netCDF file. Returns @@ -1482,7 +1482,7 @@ def _get_coord_variable_name(self, cube_or_mesh, coord): Parameters ---------- - cube_or_mesh : :class:`iris.cube.Cube` or :class:`iris.ugrid.MeshXY` + cube_or_mesh : :class:`iris.cube.Cube` or :class:`iris.mesh.MeshXY` The Cube or Mesh being saved to the netCDF file. coord : :class:`iris.coords._DimensionalMetadata` An instance of a coordinate (or similar), for which a CF-netCDF @@ -1524,7 +1524,7 @@ def _get_coord_variable_name(self, cube_or_mesh, coord): # element-coordinate of the mesh. # Name it for it's first dim, i.e. mesh-dim of its location. - from iris.ugrid.mesh import Connectivity + from iris.mesh import Connectivity # At present, a location-coord cannot be nameless, as the # MeshXY code relies on guess_coord_axis. @@ -1544,7 +1544,7 @@ def _get_mesh_variable_name(self, mesh): Parameters ---------- - mesh : :class:`iris.ugrid.mesh.MeshXY` + mesh : :class:`iris.mesh.MeshXY` An instance of a Mesh for which a CF-netCDF variable name is required. @@ -1570,7 +1570,7 @@ def _create_mesh(self, mesh): Parameters ---------- - mesh : :class:`iris.ugrid.mesh.MeshXY` + mesh : :class:`iris.mesh.MeshXY` The Mesh to be saved to CF-netCDF file. Returns @@ -1660,7 +1660,7 @@ def _create_generic_cf_array_var( Parameters ---------- - cube_or_mesh : :class:`iris.cube.Cube` or :class:`iris.ugrid.MeshXY` + cube_or_mesh : :class:`iris.cube.Cube` or :class:`iris.mesh.MeshXY` The Cube or Mesh being saved to the netCDF file. cube_dim_names : list of str The name of each dimension of the cube. @@ -2796,3 +2796,40 @@ def is_valid_packspec(p): result = sman.delayed_completion() return result + + +def save_mesh(mesh, filename, netcdf_format="NETCDF4"): + """Save mesh(es) to a netCDF file. + + Parameters + ---------- + mesh : :class:`iris.mesh.MeshXY` or iterable + Mesh(es) to save. + filename : str + Name of the netCDF file to create. + netcdf_format : str, default="NETCDF4" + Underlying netCDF file format, one of 'NETCDF4', 'NETCDF4_CLASSIC', + 'NETCDF3_CLASSIC' or 'NETCDF3_64BIT'. Default is 'NETCDF4' format. + + """ + if isinstance(mesh, typing.Iterable): + meshes = mesh + else: + meshes = [mesh] + + # Initialise Manager for saving + with Saver(filename, netcdf_format) as sman: + # Iterate through the list. + for mesh in meshes: + # Get suitable dimension names. + mesh_dimensions, _ = sman._get_dim_names(mesh) + + # Create dimensions. + sman._create_cf_dimensions(cube=None, dimension_names=mesh_dimensions) + + # Create the mesh components. + sman._add_mesh(mesh) + + # Add a conventions attribute. + # TODO: add 'UGRID' to conventions, when this is agreed with CF ? + sman.update_global_attributes(Conventions=CF_CONVENTIONS_VERSION) diff --git a/lib/iris/ugrid/load.py b/lib/iris/fileformats/netcdf/ugrid_load.py similarity index 71% rename from lib/iris/ugrid/load.py rename to lib/iris/fileformats/netcdf/ugrid_load.py index a3f278ef23..210e112629 100644 --- a/lib/iris/ugrid/load.py +++ b/lib/iris/fileformats/netcdf/ugrid_load.py @@ -3,12 +3,10 @@ # This file is part of Iris and is released under the BSD license. # See LICENSE in the root of the repository for full licensing details. -r"""Allow the construction of :class:`~iris.ugrid.mesh.MeshXY`. +r"""Allow the construction of :class:`~iris.mesh.MeshXY`. -Extensions to Iris' NetCDF loading to allow the construction of -:class:`~iris.ugrid.mesh.MeshXY` from UGRID data in the file. - -Eventual destination: :mod:`iris.fileformats.netcdf`. +Extension functions for Iris NetCDF loading, to construct +:class:`~iris.mesh.MeshXY` from UGRID data in files. .. seealso:: @@ -17,27 +15,21 @@ """ -from contextlib import contextmanager from itertools import groupby from pathlib import Path -import threading import warnings -from .._deprecation import warn_deprecated -from ..config import get_logger -from ..coords import AuxCoord -from ..fileformats._nc_load_rules.helpers import get_attr_units, get_names -from ..fileformats.netcdf import loader as nc_loader -from ..io import decode_uri, expand_filespecs -from ..util import guess_coord_axis -from ..warnings import IrisCfWarning, IrisDefaultingWarning, IrisIgnoringWarning -from .cf import ( - CFUGridAuxiliaryCoordinateVariable, - CFUGridConnectivityVariable, - CFUGridMeshVariable, - CFUGridReader, -) -from .mesh import Connectivity, MeshXY +from iris.config import get_logger +from iris.coords import AuxCoord +from iris.io import decode_uri, expand_filespecs +from iris.mesh.components import Connectivity, MeshXY +from iris.util import guess_coord_axis +from iris.warnings import IrisCfWarning, IrisDefaultingWarning, IrisIgnoringWarning + +# NOTE: all imports from iris.fileformats.netcdf must be deferred, to avoid circular +# imports. +# This is needed so that load_mesh/load_meshes can be included in the iris.mesh API. + # Configure the logger. logger = get_logger(__name__, propagate=True, handler=False) @@ -55,81 +47,6 @@ class _WarnComboCfDefaultingIgnoring(_WarnComboCfDefaulting, IrisIgnoringWarning pass -class ParseUGridOnLoad(threading.local): - def __init__(self): - """Thead-safe state to enable UGRID-aware NetCDF loading. - - A flag for dictating whether to use the experimental UGRID-aware - version of Iris NetCDF loading. Object is thread-safe. - - Use via the run-time switch - :const:`~iris.ugrid.load.PARSE_UGRID_ON_LOAD`. - Use :meth:`context` to temporarily activate. - - Notes - ----- - .. deprecated:: 1.10 - Do not use -- due to be removed at next major release : - UGRID loading is now **always** active for files containing a UGRID mesh. - - """ - - def __bool__(self): - return True - - @contextmanager - def context(self): - """Activate UGRID-aware NetCDF loading. - - Use the standard Iris loading API while within the context manager. If - the loaded file(s) include any UGRID content, this will be parsed and - attached to the resultant cube(s) accordingly. - - Use via the run-time switch - :const:`~iris.ugrid.load.PARSE_UGRID_ON_LOAD`. - - For example:: - - with PARSE_UGRID_ON_LOAD.context(): - my_cube_list = iris.load([my_file_path, my_file_path2], - constraint=my_constraint, - callback=my_callback) - - Notes - ----- - .. deprecated:: 1.10 - Do not use -- due to be removed at next major release : - UGRID loading is now **always** active for files containing a UGRID mesh. - - Examples - -------- - Replace usage, for example: - - .. code-block:: python - - with iris.experimental.ugrid.PARSE_UGRID_ON_LOAD.context(): - mesh_cubes = iris.load(path) - - with: - - .. code-block:: python - - mesh_cubes = iris.load(path) - - """ - wmsg = ( - "iris.experimental.ugrid.load.PARSE_UGRID_ON_LOAD has been deprecated " - "and will be removed. Please remove all uses : these are no longer needed, " - "as UGRID loading is now applied to any file containing a mesh." - ) - warn_deprecated(wmsg) - yield - - -#: Run-time switch for experimental UGRID-aware NetCDF loading. See :class:`~iris.ugrid.load.ParseUGridOnLoad`. -PARSE_UGRID_ON_LOAD = ParseUGridOnLoad() - - def _meshes_from_cf(cf_reader): """Mesh from cf, common behaviour for extracting meshes from a CFReader. @@ -148,10 +65,10 @@ def _meshes_from_cf(cf_reader): def load_mesh(uris, var_name=None): - """Load a single :class:`~iris.ugrid.mesh.MeshXY` object from one or more NetCDF files. + """Load a single :class:`~iris.mesh.MeshXY` object from one or more NetCDF files. Raises an error if more/less than one - :class:`~iris.ugrid.mesh.MeshXY` is found. + :class:`~iris.mesh.MeshXY` is found. Parameters ---------- @@ -159,12 +76,12 @@ def load_mesh(uris, var_name=None): One or more filenames/URI's. Filenames can include wildcards. Any URI's must support OpenDAP. var_name : str, optional - Only return a :class:`~iris.ugrid.mesh.MeshXY` if its + Only return a :class:`~iris.mesh.MeshXY` if its var_name matches this value. Returns ------- - :class:`iris.ugrid.mesh.MeshXY` + :class:`iris.mesh.MeshXY` """ meshes_result = load_meshes(uris, var_name) @@ -177,7 +94,7 @@ def load_mesh(uris, var_name=None): def load_meshes(uris, var_name=None): - r"""Load :class:`~iris.ugrid.mesh.MeshXY` objects from one or more NetCDF files. + r"""Load :class:`~iris.mesh.MeshXY` objects from one or more NetCDF files. Parameters ---------- @@ -185,7 +102,7 @@ def load_meshes(uris, var_name=None): One or more filenames/URI's. Filenames can include wildcards. Any URI's must support OpenDAP. var_name : str, optional - Only return :class:`~iris.ugrid.mesh.MeshXY` that have + Only return :class:`~iris.mesh.MeshXY` that have var_names matching this value. Returns @@ -193,16 +110,15 @@ def load_meshes(uris, var_name=None): dict A dictionary mapping each mesh-containing file path/URL in the input ``uris`` to a list of the - :class:`~iris.ugrid.mesh.MeshXY` returned from each. + :class:`~iris.mesh.MeshXY` returned from each. """ - # TODO: rationalise UGRID/mesh handling once iris.ugrid is folded - # into standard behaviour. - # TODO: complete iris.ugrid replacement - # No constraints or callbacks supported - these assume they are operating + # NOTE: no constraints or callbacks supported - these assume they are operating # on a Cube. - - from ..fileformats import FORMAT_AGENT + # NOTE: dynamic imports avoid circularity : see note with module imports + from iris.fileformats import FORMAT_AGENT + from iris.fileformats.cf import CFReader + import iris.fileformats.netcdf.loader as nc_loader if isinstance(uris, str): uris = [uris] @@ -238,7 +154,7 @@ def load_meshes(uris, var_name=None): result = {} for source in valid_sources: - with CFUGridReader(source) as cf_reader: + with CFReader(source) as cf_reader: meshes_dict = _meshes_from_cf(cf_reader) meshes = list(meshes_dict.values()) if var_name is not None: @@ -249,23 +165,19 @@ def load_meshes(uris, var_name=None): return result -############ -# Object construction. -# Helper functions, supporting netcdf.load_cubes ONLY, expected to -# altered/moved when pyke is removed. - - def _build_aux_coord(coord_var, file_path): """Construct a :class:`~iris.coords.AuxCoord`. Construct a :class:`~iris.coords.AuxCoord` from a given - :class:`~iris.ugrid.cf.CFUGridAuxiliaryCoordinateVariable`, + :class:`~iris.fileformats.cf.CFUGridAuxiliaryCoordinateVariable`, and guess its mesh axis. - todo: integrate with standard loading API post-pyke. - """ - # TODO: integrate with standard saving API when no longer 'experimental'. + # NOTE: dynamic imports avoid circularity : see note with module imports + from iris.fileformats._nc_load_rules.helpers import get_attr_units, get_names + from iris.fileformats.cf import CFUGridAuxiliaryCoordinateVariable + from iris.fileformats.netcdf import loader as nc_loader + assert isinstance(coord_var, CFUGridAuxiliaryCoordinateVariable) attributes = {} attr_units = get_attr_units(coord_var, attributes) @@ -311,16 +223,18 @@ def _build_aux_coord(coord_var, file_path): def _build_connectivity(connectivity_var, file_path, element_dims): - """Construct a :class:`~iris.ugrid.mesh.Connectivity`. + """Construct a :class:`~iris.mesh.Connectivity`. - Construct a :class:`~iris.ugrid.mesh.Connectivity` from a - given :class:`~iris.ugrid.cf.CFUGridConnectivityVariable`, + Construct a :class:`~iris.mesh.Connectivity` from a + given :class:`~iris.fileformats.cf.CFUGridConnectivityVariable`, and identify the name of its first dimension. - todo: integrate with standard loading API post-pyke. - """ - # TODO: integrate with standard saving API when no longer 'experimental'. + # NOTE: dynamic imports avoid circularity : see note with module imports + from iris.fileformats._nc_load_rules.helpers import get_attr_units, get_names + from iris.fileformats.cf import CFUGridConnectivityVariable + from iris.fileformats.netcdf import loader as nc_loader + assert isinstance(connectivity_var, CFUGridConnectivityVariable) attributes = {} attr_units = get_attr_units(connectivity_var, attributes) @@ -355,15 +269,17 @@ def _build_connectivity(connectivity_var, file_path, element_dims): def _build_mesh(cf, mesh_var, file_path): - """Construct a :class:`~iris.ugrid.mesh.MeshXY`. - - Construct a :class:`~iris.ugrid.mesh.MeshXY` from a given - :class:`~iris.ugrid.cf.CFUGridMeshVariable`. + """Construct a :class:`~iris.mesh.MeshXY`. - TODO: integrate with standard loading API post-pyke. + Construct a :class:`~iris.mesh.MeshXY` from a given + :class:`~iris.fileformats.cf.CFUGridMeshVariable`. """ - # TODO: integrate with standard saving API when no longer 'experimental'. + # NOTE: dynamic imports avoid circularity : see note with module imports + from iris.fileformats._nc_load_rules.helpers import get_attr_units, get_names + from iris.fileformats.cf import CFUGridMeshVariable + from iris.fileformats.netcdf import loader as nc_loader + assert isinstance(mesh_var, CFUGridMeshVariable) attributes = {} attr_units = get_attr_units(mesh_var, attributes) @@ -490,16 +406,13 @@ def _build_mesh(cf, mesh_var, file_path): def _build_mesh_coords(mesh, cf_var): - """Construct a tuple of :class:`~iris.ugrid.mesh.MeshCoord`. + """Construct a tuple of :class:`~iris.mesh.MeshCoord`. - Construct a tuple of :class:`~iris.ugrid.mesh.MeshCoord` using - from a given :class:`~iris.ugrid.mesh.MeshXY` + Construct a tuple of :class:`~iris.mesh.MeshCoord` using + from a given :class:`~iris.mesh.MeshXY` and :class:`~iris.fileformats.cf.CFVariable`. - TODO: integrate with standard loading API post-pyke. - """ - # TODO: integrate with standard saving API when no longer 'experimental'. # Identify the cube's mesh dimension, for attaching MeshCoords. element_dimensions = { "node": mesh.node_dimension, diff --git a/lib/iris/ugrid/__init__.py b/lib/iris/mesh/__init__.py similarity index 51% rename from lib/iris/ugrid/__init__.py rename to lib/iris/mesh/__init__.py index 1337724a6f..9a2c10b7ca 100644 --- a/lib/iris/ugrid/__init__.py +++ b/lib/iris/mesh/__init__.py @@ -5,32 +5,20 @@ """Infra-structure for unstructured mesh support. -.. deprecated:: 1.10 - - :data:`PARSE_UGRID_ON_LOAD` is due to be removed at next major release. - Please remove all uses of this, which are no longer needed : - UGRID loading is now **always** active for files containing a UGRID mesh. - Based on CF UGRID Conventions (v1.0), https://ugrid-conventions.github.io/ugrid-conventions/. - -.. note:: - - For the docstring of :const:`PARSE_UGRID_ON_LOAD`: see the original - definition at :const:`iris.ugrid.load.PARSE_UGRID_ON_LOAD`. - """ -from ..config import get_logger -from .load import PARSE_UGRID_ON_LOAD, load_mesh, load_meshes -from .mesh import Connectivity, MeshCoord, MeshXY -from .save import save_mesh +from iris.config import get_logger +from iris.fileformats.netcdf.saver import save_mesh +from iris.fileformats.netcdf.ugrid_load import load_mesh, load_meshes + +from .components import Connectivity, MeshCoord, MeshXY from .utils import recombine_submeshes __all__ = [ "Connectivity", "MeshCoord", "MeshXY", - "PARSE_UGRID_ON_LOAD", "load_mesh", "load_meshes", "recombine_submeshes", diff --git a/lib/iris/ugrid/mesh.py b/lib/iris/mesh/components.py similarity index 95% rename from lib/iris/ugrid/mesh.py rename to lib/iris/mesh/components.py index 476aaed7fa..a5936388f8 100644 --- a/lib/iris/ugrid/mesh.py +++ b/lib/iris/mesh/components.py @@ -20,6 +20,8 @@ from dask import array as da import numpy as np +from iris.common.metadata import ConnectivityMetadata, MeshCoordMetadata, MeshMetadata + from .. import _lazy_data as _lazy from ..common import CFVariableMixin, metadata_filter, metadata_manager_factory from ..common.metadata import BaseMetadata @@ -28,7 +30,6 @@ from ..exceptions import ConnectivityNotFoundError, CoordinateNotFoundError from ..util import array_equal, clip_string, guess_coord_axis from ..warnings import IrisVagueMetadataWarning -from .metadata import ConnectivityMetadata, MeshCoordMetadata, MeshMetadata # Configure the logger. logger = get_logger(__name__, propagate=True, handler=False) @@ -71,9 +72,9 @@ # MeshXY connectivity manager namedtuples. # -#: Namedtuple for 1D mesh :class:`~iris.ugrid.mesh.Connectivity` instances. +#: Namedtuple for 1D mesh :class:`~iris.mesh.Connectivity` instances. Mesh1DConnectivities = namedtuple("Mesh1DConnectivities", ["edge_node"]) -#: Namedtuple for 2D mesh :class:`~iris.ugrid.mesh.Connectivity` instances. +#: Namedtuple for 2D mesh :class:`~iris.mesh.Connectivity` instances. Mesh2DConnectivities = namedtuple( "Mesh2DConnectivities", [ @@ -785,7 +786,7 @@ def from_coords(cls, *coords): .. testsetup:: from iris import load_cube, sample_data_path - from iris.ugrid import ( + from iris.mesh import ( MeshXY, MeshCoord, ) @@ -1134,7 +1135,7 @@ def _set_dimension_names(self, node, edge, face, reset=False): @property def all_connectivities(self): - """All the :class:`~iris.ugrid.mesh.Connectivity` instances of the :class:`MeshXY`.""" + """All the :class:`~iris.mesh.Connectivity` instances of the :class:`MeshXY`.""" return self._connectivity_manager.all_members @property @@ -1144,10 +1145,10 @@ def all_coords(self): @property def boundary_node_connectivity(self): - """The *optional* UGRID ``boundary_node_connectivity`` :class:`~iris.ugrid.mesh.Connectivity`. + """The *optional* UGRID ``boundary_node_connectivity`` :class:`~iris.mesh.Connectivity`. The *optional* UGRID ``boundary_node_connectivity`` - :class:`~iris.ugrid.mesh.Connectivity` of the + :class:`~iris.mesh.Connectivity` of the :class:`MeshXY`. """ @@ -1173,10 +1174,10 @@ def edge_dimension(self, name): @property def edge_face_connectivity(self): - """The *optional* UGRID ``edge_face_connectivity`` :class:`~iris.ugrid.mesh.Connectivity`. + """The *optional* UGRID ``edge_face_connectivity`` :class:`~iris.mesh.Connectivity`. The *optional* UGRID ``edge_face_connectivity`` - :class:`~iris.ugrid.mesh.Connectivity` of the + :class:`~iris.mesh.Connectivity` of the :class:`MeshXY`. """ @@ -1184,10 +1185,10 @@ def edge_face_connectivity(self): @property def edge_node_connectivity(self): - """The UGRID ``edge_node_connectivity`` :class:`~iris.ugrid.mesh.Connectivity`. + """The UGRID ``edge_node_connectivity`` :class:`~iris.mesh.Connectivity`. The UGRID ``edge_node_connectivity`` - :class:`~iris.ugrid.mesh.Connectivity` of the + :class:`~iris.mesh.Connectivity` of the :class:`MeshXY`, which is **required** for :attr:`MeshXY.topology_dimension` of ``1``, and *optionally required* for :attr:`MeshXY.topology_dimension` ``>=2``. @@ -1224,10 +1225,10 @@ def face_dimension(self, name): @property def face_edge_connectivity(self): - """The *optional* UGRID ``face_edge_connectivity``:class:`~iris.ugrid.mesh.Connectivity`. + """The *optional* UGRID ``face_edge_connectivity``:class:`~iris.mesh.Connectivity`. The *optional* UGRID ``face_edge_connectivity`` - :class:`~iris.ugrid.mesh.Connectivity` of the + :class:`~iris.mesh.Connectivity` of the :class:`MeshXY`. """ @@ -1236,10 +1237,10 @@ def face_edge_connectivity(self): @property def face_face_connectivity(self): - """The *optional* UGRID ``face_face_connectivity`` :class:`~iris.ugrid.mesh.Connectivity`. + """The *optional* UGRID ``face_face_connectivity`` :class:`~iris.mesh.Connectivity`. The *optional* UGRID ``face_face_connectivity`` - :class:`~iris.ugrid.mesh.Connectivity` of the + :class:`~iris.mesh.Connectivity` of the :class:`MeshXY`. """ @@ -1247,10 +1248,10 @@ def face_face_connectivity(self): @property def face_node_connectivity(self): - """Return ``face_node_connectivity``:class:`~iris.ugrid.mesh.Connectivity`. + """Return ``face_node_connectivity``:class:`~iris.mesh.Connectivity`. The UGRID ``face_node_connectivity`` - :class:`~iris.ugrid.mesh.Connectivity` of the + :class:`~iris.mesh.Connectivity` of the :class:`MeshXY`, which is **required** for :attr:`MeshXY.topology_dimension` of ``2``, and *optionally required* for :attr:`MeshXY.topology_dimension` of ``3``. @@ -1277,13 +1278,13 @@ def node_dimension(self, name): self._metadata_manager.node_dimension = node_dimension def add_connectivities(self, *connectivities): - """Add one or more :class:`~iris.ugrid.mesh.Connectivity` instances to the :class:`MeshXY`. + """Add one or more :class:`~iris.mesh.Connectivity` instances to the :class:`MeshXY`. Parameters ---------- *connectivities : iterable of object A collection of one or more - :class:`~iris.ugrid.mesh.Connectivity` instances to + :class:`~iris.mesh.Connectivity` instances to add to the :class:`MeshXY`. """ @@ -1342,10 +1343,10 @@ def connectivities( contains_edge=None, contains_face=None, ): - """Return all :class:`~iris.ugrid.mesh.Connectivity`. + r"""Return all :class:`~iris.mesh.Connectivity`\s. - Return all :class:`~iris.ugrid.mesh.Connectivity` - instances from the :class:`MeshXY` that match the provided criteria. + Return all :class:`~iris.mesh.Connectivity` + instances from the :class:`~iris.mesh.MeshXY` which match the provided criteria. Criteria can be either specific properties or other objects with metadata to be matched. @@ -1366,44 +1367,44 @@ def connectivities( * a connectivity or metadata instance equal to that of the desired objects e.g., - :class:`~iris.ugrid.mesh.Connectivity` or - :class:`~iris.ugrid.metadata.ConnectivityMetadata`. + :class:`~iris.mesh.Connectivity` or + :class:`~iris.common.metadata.ConnectivityMetadata`. standard_name : str, optional The CF standard name of the desired - :class:`~iris.ugrid.mesh.Connectivity`. If ``None``, + :class:`~iris.mesh.Connectivity`. If ``None``, does not check for ``standard_name``. long_name : str, optional An unconstrained description of the - :class:`~iris.ugrid.mesh.Connectivity`. If ``None``, + :class:`~iris.mesh.Connectivity`. If ``None``, does not check for ``long_name``. var_name : str, optional The NetCDF variable name of the desired - :class:`~iris.ugrid.mesh.Connectivity`. If ``None``, + :class:`~iris.mesh.Connectivity`. If ``None``, does not check for ``var_name``. attributes : dict, optional A dictionary of attributes desired on the - :class:`~iris.ugrid.mesh.Connectivity`. If ``None``, + :class:`~iris.mesh.Connectivity`. If ``None``, does not check for ``attributes``. cf_role : str, optional The UGRID ``cf_role`` of the desired - :class:`~iris.ugrid.mesh.Connectivity`. + :class:`~iris.mesh.Connectivity`. contains_node : bool, optional Contains the ``node`` element as part of the - :attr:`~iris.ugrid.metadata.ConnectivityMetadata.cf_role` + :attr:`~iris.common.metadata.ConnectivityMetadata.cf_role` in the list of objects to be matched. contains_edge : bool, optional Contains the ``edge`` element as part of the - :attr:`~iris.ugrid.metadata.ConnectivityMetadata.cf_role` + :attr:`~iris.common.metadata.ConnectivityMetadata.cf_role` in the list of objects to be matched. contains_face : bool, optional Contains the ``face`` element as part of the - :attr:`~iris.ugrid.metadata.ConnectivityMetadata.cf_role` + :attr:`~iris.common.metadata.ConnectivityMetadata.cf_role` in the list of objects to be matched. Returns ------- - list of :class:`~iris.ugrid.mesh.Connectivity` - A list of :class:`~iris.ugrid.mesh.Connectivity` + list of :class:`~iris.mesh.Connectivity` + A list of :class:`~iris.mesh.Connectivity` instances from the :class:`MeshXY` that matched the given criteria. """ @@ -1432,9 +1433,9 @@ def connectivity( contains_edge=None, contains_face=None, ): - """Return a single :class:`~iris.ugrid.mesh.Connectivity`. + """Return a single :class:`~iris.mesh.Connectivity`. - Return a single :class:`~iris.ugrid.mesh.Connectivity` + Return a single :class:`~iris.mesh.Connectivity` from the :class:`MeshXY` that matches the provided criteria. Criteria can be either specific properties or other objects with @@ -1443,7 +1444,7 @@ def connectivity( .. note:: If the given criteria do not return **precisely one** - :class:`~iris.ugrid.mesh.Connectivity`, then a + :class:`~iris.mesh.Connectivity`, then a :class:`~iris.exceptions.ConnectivityNotFoundError` is raised. .. seealso:: @@ -1462,44 +1463,44 @@ def connectivity( * a connectivity or metadata instance equal to that of the desired object e.g., - :class:`~iris.ugrid.mesh.Connectivity` or - :class:`~iris.ugrid.metadata.ConnectivityMetadata`. + :class:`~iris.mesh.Connectivity` or + :class:`~iris.common.metadata.ConnectivityMetadata`. standard_name : str, optional The CF standard name of the desired - :class:`~iris.ugrid.mesh.Connectivity`. If ``None``, + :class:`~iris.mesh.Connectivity`. If ``None``, does not check for ``standard_name``. long_name : str, optional An unconstrained description of the - :class:`~iris.ugrid.mesh.Connectivity`. If ``None``, + :class:`~iris.mesh.Connectivity`. If ``None``, does not check for ``long_name``. var_name : str, optional The NetCDF variable name of the desired - :class:`~iris.ugrid.mesh.Connectivity`. If ``None``, + :class:`~iris.mesh.Connectivity`. If ``None``, does not check for ``var_name``. attributes : dict, optional A dictionary of attributes desired on the - :class:`~iris.ugrid.mesh.Connectivity`. If ``None``, + :class:`~iris.mesh.Connectivity`. If ``None``, does not check for ``attributes``. cf_role : str, optional The UGRID ``cf_role`` of the desired - :class:`~iris.ugrid.mesh.Connectivity`. + :class:`~iris.mesh.Connectivity`. contains_node : bool, optional Contains the ``node`` element as part of the - :attr:`~iris.ugrid.metadata.ConnectivityMetadata.cf_role` + :attr:`~iris.common.metadata.ConnectivityMetadata.cf_role` in the list of objects to be matched. contains_edge : bool, optional Contains the ``edge`` element as part of the - :attr:`~iris.ugrid.metadata.ConnectivityMetadata.cf_role` + :attr:`~iris.common.metadata.ConnectivityMetadata.cf_role` in the list of objects to be matched. contains_face : bool, optional Contains the ``face`` element as part of the - :attr:`~iris.ugrid.metadata.ConnectivityMetadata.cf_role` + :attr:`~iris.common.metadata.ConnectivityMetadata.cf_role` in the list of objects to be matched. Returns ------- - :class:`~iris.ugrid.mesh.Connectivity` - The :class:`~iris.ugrid.mesh.Connectivity` from the + :class:`~iris.mesh.Connectivity` + The :class:`~iris.mesh.Connectivity` from the :class:`MeshXY` that matched the given criteria. """ @@ -1605,8 +1606,8 @@ def coords( ): """Return all :class:`~iris.coords.AuxCoord` coordinates from the :class:`MeshXY`. - Return all :class:`~iris.coords.AuxCoord` coordinates from the :class:`MeshXY` that - match the provided criteria. + Return all :class:`~iris.coords.AuxCoord` coordinates from the :class:`MeshXY` + which match the provided criteria. Criteria can be either specific properties or other objects with metadata to be matched. @@ -1677,10 +1678,10 @@ def remove_connectivities( contains_edge=None, contains_face=None, ): - """Remove one or more :class:`~iris.ugrid.mesh.Connectivity`. + """Remove one or more :class:`~iris.mesh.Connectivity`. - Remove one or more :class:`~iris.ugrid.mesh.Connectivity` - from the :class:`MeshXY` that match the provided criteria. + Remove one or more :class:`~iris.mesh.Connectivity` + from the :class:`MeshXY` which match the provided criteria. Criteria can be either specific properties or other objects with metadata to be matched. @@ -1697,44 +1698,44 @@ def remove_connectivities( * a connectivity or metadata instance equal to that of the desired objects e.g., - :class:`~iris.ugrid.mesh.Connectivity` or - :class:`~iris.ugrid.metadata.ConnectivityMetadata`. + :class:`~iris.mesh.Connectivity` or + :class:`~iris.common.metadata.ConnectivityMetadata`. standard_name : str, optional The CF standard name of the desired - :class:`~iris.ugrid.mesh.Connectivity`. If ``None``, + :class:`~iris.mesh.Connectivity`. If ``None``, does not check for ``standard_name``. long_name : str, optional An unconstrained description of the - :class:`~iris.ugrid.mesh.Connectivity`. If ``None``, + :class:`~iris.mesh.Connectivity`. If ``None``, does not check for ``long_name``. var_name : str, optional The NetCDF variable name of the desired - :class:`~iris.ugrid.mesh.Connectivity`. If ``None``, + :class:`~iris.mesh.Connectivity`. If ``None``, does not check for ``var_name``. attributes : dict, optional A dictionary of attributes desired on the - :class:`~iris.ugrid.mesh.Connectivity`. If ``None``, + :class:`~iris.mesh.Connectivity`. If ``None``, does not check for ``attributes``. cf_role : str, optional The UGRID ``cf_role`` of the desired - :class:`~iris.ugrid.mesh.Connectivity`. + :class:`~iris.mesh.Connectivity`. contains_node : bool, optional Contains the ``node`` element as part of the - :attr:`~iris.ugrid.metadata.ConnectivityMetadata.cf_role` + :attr:`~iris.common.metadata.ConnectivityMetadata.cf_role` in the list of objects to be matched for potential removal. contains_edge : bool, optional Contains the ``edge`` element as part of the - :attr:`~iris.ugrid.metadata.ConnectivityMetadata.cf_role` + :attr:`~iris.common.metadata.ConnectivityMetadata.cf_role` in the list of objects to be matched for potential removal. contains_face : bool, optional Contains the ``face`` element as part of the - :attr:`~iris.ugrid.metadata.ConnectivityMetadata.cf_role` + :attr:`~iris.common.metadata.ConnectivityMetadata.cf_role` in the list of objects to be matched for potential removal. Returns ------- - list of :class:`~iris.ugrid.mesh.Connectivity` - A list of :class:`~iris.ugrid.mesh.Connectivity` + list of :class:`~iris.mesh.Connectivity` + A list of :class:`~iris.mesh.Connectivity` instances removed from the :class:`MeshXY` that matched the given criteria. @@ -1764,7 +1765,7 @@ def remove_coords( """Remove one or more :class:`~iris.coords.AuxCoord` from the :class:`MeshXY`. Remove one or more :class:`~iris.coords.AuxCoord` from the :class:`MeshXY` - that match the provided criteria. + which match the provided criteria. Criteria can be either specific properties or other objects with metadata to be matched. @@ -1852,9 +1853,9 @@ def xml_element(self, doc): # # return the lazy AuxCoord(...), AuxCoord(...) def to_MeshCoord(self, location, axis): - """Generate a :class:`~iris.ugrid.mesh.MeshCoord`. + """Generate a :class:`~iris.mesh.MeshCoord`. - Generate a :class:`~iris.ugrid.mesh.MeshCoord` that + Generate a :class:`~iris.mesh.MeshCoord` that references the current :class:`MeshXY`, and passing through the ``location`` and ``axis`` arguments. @@ -1866,25 +1867,25 @@ def to_MeshCoord(self, location, axis): ---------- location : str The ``location`` argument for - :class:`~iris.ugrid.mesh.MeshCoord` instantiation. + :class:`~iris.mesh.MeshCoord` instantiation. axis : str The ``axis`` argument for - :class:`~iris.ugrid.mesh.MeshCoord` instantiation. + :class:`~iris.mesh.MeshCoord` instantiation. Returns ------- - :class:`~iris.ugrid.mesh.MeshCoord` - A :class:`~iris.ugrid.mesh.MeshCoord` referencing the + :class:`~iris.mesh.mesh.MeshCoord` + A :class:`~iris.mesh.mesh.MeshCoord` referencing the current :class:`MeshXY`. """ return MeshCoord(mesh=self, location=location, axis=axis) def to_MeshCoords(self, location): - r"""Generate a tuple of :class:`~iris.ugrid.mesh.MeshCoord`. + r"""Generate a tuple of :class:`~iris.mesh.mesh.MeshCoord`. Generate a tuple of - :class:`~iris.ugrid.mesh.MeshCoord`, each referencing + :class:`~iris.mesh.mesh.MeshCoord`, each referencing the current :class:`MeshXY`, one for each :attr:`AXES` value, passing through the ``location`` argument. @@ -1899,8 +1900,8 @@ def to_MeshCoords(self, location): Returns ------- - tuple of :class:`~iris.ugrid.mesh.MeshCoord` - Tuple of :class:`~iris.ugrid.mesh.MeshCoord` + tuple of :class:`~iris.mesh.mesh.MeshCoord` + Tuple of :class:`~iris.mesh.mesh.MeshCoord` referencing the current :class:`MeshXY`. One for each value in :attr:`AXES`, using the value for the ``axis`` argument. @@ -2651,7 +2652,7 @@ def face_node(self): class MeshCoord(AuxCoord): """Geographic coordinate values of data on an unstructured mesh. - A MeshCoord references a `~iris.ugrid.mesh.MeshXY`. + A MeshCoord references a `~iris.mesh.mesh.MeshXY`. When contained in a `~iris.cube.Cube` it connects the cube to the Mesh. It records (a) which 1-D cube dimension represents the unstructured mesh, and (b) which mesh 'location' the cube data is mapped to -- i.e. is it diff --git a/lib/iris/ugrid/utils.py b/lib/iris/mesh/utils.py similarity index 99% rename from lib/iris/ugrid/utils.py rename to lib/iris/mesh/utils.py index def9c1fccf..1117c3c7d7 100644 --- a/lib/iris/ugrid/utils.py +++ b/lib/iris/mesh/utils.py @@ -31,7 +31,7 @@ def recombine_submeshes( Describes the mesh and mesh-location onto which the all the ``submesh-cubes``' data are mapped, and acts as a template for the result. - Must have a :class:`~iris.ugrid.mesh.MeshXY`. + Must have a :class:`~iris.mesh.MeshXY`. submesh_cubes : iterable of Cube, or Cube Cubes, each with data on a _subset_ of the ``mesh_cube`` datapoints diff --git a/lib/iris/tests/integration/experimental/test_meshcoord_coordsys.py b/lib/iris/tests/integration/mesh/test_meshcoord_coordsys.py similarity index 93% rename from lib/iris/tests/integration/experimental/test_meshcoord_coordsys.py rename to lib/iris/tests/integration/mesh/test_meshcoord_coordsys.py index 7a1ac80823..9e14b12c9a 100644 --- a/lib/iris/tests/integration/experimental/test_meshcoord_coordsys.py +++ b/lib/iris/tests/integration/mesh/test_meshcoord_coordsys.py @@ -9,7 +9,6 @@ import iris from iris.coord_systems import GeogCS from iris.tests.stock.netcdf import ncgen_from_cdl -from iris.ugrid.load import PARSE_UGRID_ON_LOAD TEST_CDL = """ netcdf mesh_test { @@ -80,8 +79,7 @@ def test_default_mesh_cs(tmp_path, cs_axes): do_x = "x" in cs_axes do_y = "y" in cs_axes make_file(nc_path, node_x_crs=do_x, node_y_crs=do_y) - with PARSE_UGRID_ON_LOAD.context(): - cube = iris.load_cube(nc_path, "node_data") + cube = iris.load_cube(nc_path, "node_data") meshco_x, meshco_y = [cube.coord(mesh_coords=True, axis=ax) for ax in ("x", "y")] # NOTE: at present, none of these load with a coordinate system, # because we don't support the extended grid-mapping syntax. @@ -95,8 +93,7 @@ def test_assigned_mesh_cs(tmp_path): # the corresponding meshcoord reports the same cs. nc_path = tmp_path / "test_temp.nc" make_file(nc_path) - with PARSE_UGRID_ON_LOAD.context(): - cube = iris.load_cube(nc_path, "node_data") + cube = iris.load_cube(nc_path, "node_data") nodeco_x = cube.mesh.coord(location="node", axis="x") meshco_x, meshco_y = [cube.coord(axis=ax) for ax in ("x", "y")] assert nodeco_x.coord_system is None @@ -116,8 +113,7 @@ def test_meshcoord_coordsys_copy(tmp_path): # Check that copying a meshcoord with a coord system works properly. nc_path = tmp_path / "test_temp.nc" make_file(nc_path) - with PARSE_UGRID_ON_LOAD.context(): - cube = iris.load_cube(nc_path, "node_data") + cube = iris.load_cube(nc_path, "node_data") node_coord = cube.mesh.coord(location="node", axis="x") assigned_cs = GeogCS(1.0) node_coord.coord_system = assigned_cs diff --git a/lib/iris/tests/integration/ugrid/test_ugrid_save.py b/lib/iris/tests/integration/mesh/test_ugrid_save.py similarity index 100% rename from lib/iris/tests/integration/ugrid/test_ugrid_save.py rename to lib/iris/tests/integration/mesh/test_ugrid_save.py diff --git a/lib/iris/tests/integration/ugrid/ugrid_conventions_examples/README.txt b/lib/iris/tests/integration/mesh/ugrid_conventions_examples/README.txt similarity index 100% rename from lib/iris/tests/integration/ugrid/ugrid_conventions_examples/README.txt rename to lib/iris/tests/integration/mesh/ugrid_conventions_examples/README.txt diff --git a/lib/iris/tests/integration/ugrid/ugrid_conventions_examples/ugrid_ex1_1d_mesh.cdl b/lib/iris/tests/integration/mesh/ugrid_conventions_examples/ugrid_ex1_1d_mesh.cdl similarity index 100% rename from lib/iris/tests/integration/ugrid/ugrid_conventions_examples/ugrid_ex1_1d_mesh.cdl rename to lib/iris/tests/integration/mesh/ugrid_conventions_examples/ugrid_ex1_1d_mesh.cdl diff --git a/lib/iris/tests/integration/ugrid/ugrid_conventions_examples/ugrid_ex2_2d_triangular.cdl b/lib/iris/tests/integration/mesh/ugrid_conventions_examples/ugrid_ex2_2d_triangular.cdl similarity index 100% rename from lib/iris/tests/integration/ugrid/ugrid_conventions_examples/ugrid_ex2_2d_triangular.cdl rename to lib/iris/tests/integration/mesh/ugrid_conventions_examples/ugrid_ex2_2d_triangular.cdl diff --git a/lib/iris/tests/integration/ugrid/ugrid_conventions_examples/ugrid_ex3_2d_flexible.cdl b/lib/iris/tests/integration/mesh/ugrid_conventions_examples/ugrid_ex3_2d_flexible.cdl similarity index 100% rename from lib/iris/tests/integration/ugrid/ugrid_conventions_examples/ugrid_ex3_2d_flexible.cdl rename to lib/iris/tests/integration/mesh/ugrid_conventions_examples/ugrid_ex3_2d_flexible.cdl diff --git a/lib/iris/tests/integration/ugrid/ugrid_conventions_examples/ugrid_ex4_3d_layered.cdl b/lib/iris/tests/integration/mesh/ugrid_conventions_examples/ugrid_ex4_3d_layered.cdl similarity index 100% rename from lib/iris/tests/integration/ugrid/ugrid_conventions_examples/ugrid_ex4_3d_layered.cdl rename to lib/iris/tests/integration/mesh/ugrid_conventions_examples/ugrid_ex4_3d_layered.cdl diff --git a/lib/iris/tests/integration/ugrid/test_ugrid_load.py b/lib/iris/tests/integration/netcdf/test_ugrid_load.py similarity index 95% rename from lib/iris/tests/integration/ugrid/test_ugrid_load.py rename to lib/iris/tests/integration/netcdf/test_ugrid_load.py index 2281e54b3c..82098a5d5a 100644 --- a/lib/iris/tests/integration/ugrid/test_ugrid_load.py +++ b/lib/iris/tests/integration/netcdf/test_ugrid_load.py @@ -2,13 +2,7 @@ # # This file is part of Iris and is released under the BSD license. # See LICENSE in the root of the repository for full licensing details. -"""Integration tests for NetCDF-UGRID file loading. - -todo: fold these tests into netcdf tests when iris.ugrid is folded into - standard behaviour. -TODO: complete iris.ugrid replacement - -""" +"""Integration tests for NetCDF-UGRID file loading.""" # Import iris.tests first so that some things can be initialised before # importing anything else. @@ -19,12 +13,12 @@ import pytest from iris import Constraint, load +from iris.fileformats.netcdf.ugrid_load import load_mesh, load_meshes +from iris.mesh import MeshXY from iris.tests.stock.netcdf import ( _file_from_cdl_template as create_file_from_cdl_template, ) from iris.tests.unit.tests.stock.test_netcdf import XIOSFileMixin -from iris.ugrid.load import load_mesh, load_meshes -from iris.ugrid.mesh import MeshXY from iris.warnings import IrisCfWarning @@ -59,7 +53,7 @@ def common_test(self, load_filename, assert_filename): ) self.assertEqual(1, len(cube_list)) cube = cube_list[0] - self.assertCML(cube, ["ugrid", assert_filename]) + self.assertCML(cube, ["mesh", assert_filename]) def test_2D_1t_face_half_levels(self): self.common_test( @@ -126,7 +120,7 @@ def test_multiple_phenomena(self): ["NetCDF", "unstructured_grid", "lfric_surface_mean.nc"] ), ) - self.assertCML(cube_list, ("ugrid", "surface_mean.cml")) + self.assertCML(cube_list, ("mesh", "surface_mean.cml")) class TestTolerantLoading(XIOSFileMixin): diff --git a/lib/iris/tests/results/ugrid/2D_1t_face_half_levels.cml b/lib/iris/tests/results/mesh/2D_1t_face_half_levels.cml similarity index 100% rename from lib/iris/tests/results/ugrid/2D_1t_face_half_levels.cml rename to lib/iris/tests/results/mesh/2D_1t_face_half_levels.cml diff --git a/lib/iris/tests/results/ugrid/2D_72t_face_half_levels.cml b/lib/iris/tests/results/mesh/2D_72t_face_half_levels.cml similarity index 100% rename from lib/iris/tests/results/ugrid/2D_72t_face_half_levels.cml rename to lib/iris/tests/results/mesh/2D_72t_face_half_levels.cml diff --git a/lib/iris/tests/results/ugrid/3D_1t_face_full_levels.cml b/lib/iris/tests/results/mesh/3D_1t_face_full_levels.cml similarity index 100% rename from lib/iris/tests/results/ugrid/3D_1t_face_full_levels.cml rename to lib/iris/tests/results/mesh/3D_1t_face_full_levels.cml diff --git a/lib/iris/tests/results/ugrid/3D_1t_face_half_levels.cml b/lib/iris/tests/results/mesh/3D_1t_face_half_levels.cml similarity index 100% rename from lib/iris/tests/results/ugrid/3D_1t_face_half_levels.cml rename to lib/iris/tests/results/mesh/3D_1t_face_half_levels.cml diff --git a/lib/iris/tests/results/ugrid/3D_snow_pseudo_levels.cml b/lib/iris/tests/results/mesh/3D_snow_pseudo_levels.cml similarity index 100% rename from lib/iris/tests/results/ugrid/3D_snow_pseudo_levels.cml rename to lib/iris/tests/results/mesh/3D_snow_pseudo_levels.cml diff --git a/lib/iris/tests/results/ugrid/3D_soil_pseudo_levels.cml b/lib/iris/tests/results/mesh/3D_soil_pseudo_levels.cml similarity index 100% rename from lib/iris/tests/results/ugrid/3D_soil_pseudo_levels.cml rename to lib/iris/tests/results/mesh/3D_soil_pseudo_levels.cml diff --git a/lib/iris/tests/results/ugrid/3D_tile_pseudo_levels.cml b/lib/iris/tests/results/mesh/3D_tile_pseudo_levels.cml similarity index 100% rename from lib/iris/tests/results/ugrid/3D_tile_pseudo_levels.cml rename to lib/iris/tests/results/mesh/3D_tile_pseudo_levels.cml diff --git a/lib/iris/tests/results/ugrid/3D_veg_pseudo_levels.cml b/lib/iris/tests/results/mesh/3D_veg_pseudo_levels.cml similarity index 100% rename from lib/iris/tests/results/ugrid/3D_veg_pseudo_levels.cml rename to lib/iris/tests/results/mesh/3D_veg_pseudo_levels.cml diff --git a/lib/iris/tests/results/ugrid/surface_mean.cml b/lib/iris/tests/results/mesh/surface_mean.cml similarity index 100% rename from lib/iris/tests/results/ugrid/surface_mean.cml rename to lib/iris/tests/results/mesh/surface_mean.cml diff --git a/lib/iris/tests/stock/__init__.py b/lib/iris/tests/stock/__init__.py index f664ce012b..31fa7e653d 100644 --- a/lib/iris/tests/stock/__init__.py +++ b/lib/iris/tests/stock/__init__.py @@ -15,7 +15,7 @@ import numpy as np import numpy.ma as ma -from iris import ugrid +from iris import mesh as ugrid from iris.analysis import cartography import iris.aux_factory from iris.coord_systems import GeogCS, RotatedGeogCS diff --git a/lib/iris/tests/stock/mesh.py b/lib/iris/tests/stock/mesh.py index 22dcff18ce..3824ba84fc 100644 --- a/lib/iris/tests/stock/mesh.py +++ b/lib/iris/tests/stock/mesh.py @@ -8,7 +8,7 @@ from iris.coords import AuxCoord, DimCoord from iris.cube import Cube -from iris.ugrid.mesh import Connectivity, MeshCoord, MeshXY +from iris.mesh import Connectivity, MeshCoord, MeshXY # Default creation controls for creating a test MeshXY. # Note: we're not creating any kind of sensible 'normal' mesh here, the numbers diff --git a/lib/iris/tests/stock/netcdf.py b/lib/iris/tests/stock/netcdf.py index c063f3af23..0f5fb0f144 100644 --- a/lib/iris/tests/stock/netcdf.py +++ b/lib/iris/tests/stock/netcdf.py @@ -116,7 +116,7 @@ def _add_standard_data(nc_path, unlimited_dim_size=0): var[:] = data else: # Fill with a plain value. But avoid zeros, so we can simulate - # valid ugrid connectivities even when start_index=1. + # valid mesh connectivities even when start_index=1. with dask.config.set({"array.chunk-size": "2048MiB"}): data = da.ones(shape, dtype=var.dtype) # Do not use zero da.store(data, var) diff --git a/lib/iris/tests/unit/ugrid/metadata/test_ConnectivityMetadata.py b/lib/iris/tests/unit/common/metadata/test_ConnectivityMetadata.py similarity index 99% rename from lib/iris/tests/unit/ugrid/metadata/test_ConnectivityMetadata.py rename to lib/iris/tests/unit/common/metadata/test_ConnectivityMetadata.py index e53a3d7002..6f3c9f7429 100644 --- a/lib/iris/tests/unit/ugrid/metadata/test_ConnectivityMetadata.py +++ b/lib/iris/tests/unit/common/metadata/test_ConnectivityMetadata.py @@ -2,7 +2,7 @@ # # This file is part of Iris and is released under the BSD license. # See LICENSE in the root of the repository for full licensing details. -"""Unit tests for the :class:`iris.ugrid.metadata.ConnectivityMetadata`.""" +"""Unit tests for the :class:`iris.common.metadata.ConnectivityMetadata`.""" # Import iris.tests first so that some things can be initialised before # importing anything else. @@ -13,8 +13,7 @@ from unittest.mock import sentinel from iris.common.lenient import _LENIENT, _qualname -from iris.common.metadata import BaseMetadata -from iris.ugrid.metadata import ConnectivityMetadata +from iris.common.metadata import BaseMetadata, ConnectivityMetadata class Test(tests.IrisTest): diff --git a/lib/iris/tests/unit/ugrid/metadata/test_MeshCoordMetadata.py b/lib/iris/tests/unit/common/metadata/test_MeshCoordMetadata.py similarity index 99% rename from lib/iris/tests/unit/ugrid/metadata/test_MeshCoordMetadata.py rename to lib/iris/tests/unit/common/metadata/test_MeshCoordMetadata.py index 403425fb56..3bdf261165 100644 --- a/lib/iris/tests/unit/ugrid/metadata/test_MeshCoordMetadata.py +++ b/lib/iris/tests/unit/common/metadata/test_MeshCoordMetadata.py @@ -2,7 +2,7 @@ # # This file is part of Iris and is released under the BSD license. # See LICENSE in the root of the repository for full licensing details. -"""Unit tests for the :class:`iris.ugrid.metadata.MeshCoordMetadata`.""" +"""Unit tests for the :class:`iris.common.metadata.MeshCoordMetadata`.""" # Import iris.tests first so that some things can be initialised before # importing anything else. @@ -13,8 +13,7 @@ from unittest.mock import sentinel from iris.common.lenient import _LENIENT, _qualname -from iris.common.metadata import BaseMetadata -from iris.ugrid.metadata import MeshCoordMetadata +from iris.common.metadata import BaseMetadata, MeshCoordMetadata class Test__identity(tests.IrisTest): diff --git a/lib/iris/tests/unit/ugrid/metadata/test_MeshMetadata.py b/lib/iris/tests/unit/common/metadata/test_MeshMetadata.py similarity index 99% rename from lib/iris/tests/unit/ugrid/metadata/test_MeshMetadata.py rename to lib/iris/tests/unit/common/metadata/test_MeshMetadata.py index e96a2441c3..7dae56bbe6 100644 --- a/lib/iris/tests/unit/ugrid/metadata/test_MeshMetadata.py +++ b/lib/iris/tests/unit/common/metadata/test_MeshMetadata.py @@ -2,7 +2,7 @@ # # This file is part of Iris and is released under the BSD license. # See LICENSE in the root of the repository for full licensing details. -"""Unit tests for the :class:`iris.ugrid.metadata.MeshMetadata`.""" +"""Unit tests for the :class:`iris.common.metadata.MeshMetadata`.""" # Import iris.tests first so that some things can be initialised before # importing anything else. @@ -13,8 +13,7 @@ from unittest.mock import sentinel from iris.common.lenient import _LENIENT, _qualname -from iris.common.metadata import BaseMetadata -from iris.ugrid.metadata import MeshMetadata +from iris.common.metadata import BaseMetadata, MeshMetadata class Test(tests.IrisTest): diff --git a/lib/iris/tests/unit/common/metadata/test_metadata_manager_factory.py b/lib/iris/tests/unit/common/metadata/test_metadata_manager_factory.py index 49449ad63b..1fbf0da084 100644 --- a/lib/iris/tests/unit/common/metadata/test_metadata_manager_factory.py +++ b/lib/iris/tests/unit/common/metadata/test_metadata_manager_factory.py @@ -17,11 +17,11 @@ AncillaryVariableMetadata, BaseMetadata, CellMeasureMetadata, + ConnectivityMetadata, CoordMetadata, CubeMetadata, metadata_manager_factory, ) -from iris.ugrid.metadata import ConnectivityMetadata BASES = [ AncillaryVariableMetadata, diff --git a/lib/iris/tests/unit/common/mixin/test_CFVariableMixin.py b/lib/iris/tests/unit/common/mixin/test_CFVariableMixin.py index 57d56ecfe7..7d414bfb54 100644 --- a/lib/iris/tests/unit/common/mixin/test_CFVariableMixin.py +++ b/lib/iris/tests/unit/common/mixin/test_CFVariableMixin.py @@ -17,11 +17,11 @@ AncillaryVariableMetadata, BaseMetadata, CellMeasureMetadata, + ConnectivityMetadata, CoordMetadata, CubeMetadata, ) from iris.common.mixin import CFVariableMixin, LimitedAttributeDict -from iris.ugrid.metadata import ConnectivityMetadata class Test__getter(tests.IrisTest): diff --git a/lib/iris/tests/unit/coords/test__DimensionalMetadata.py b/lib/iris/tests/unit/coords/test__DimensionalMetadata.py index 60d4459ff3..64246261ca 100644 --- a/lib/iris/tests/unit/coords/test__DimensionalMetadata.py +++ b/lib/iris/tests/unit/coords/test__DimensionalMetadata.py @@ -21,9 +21,9 @@ DimCoord, _DimensionalMetadata, ) +from iris.mesh import Connectivity from iris.tests.stock import climatology_3d as cube_with_climatology from iris.tests.stock.mesh import sample_meshcoord -from iris.ugrid.mesh import Connectivity class Test___init____abstractmethod(tests.IrisTest): diff --git a/lib/iris/tests/unit/experimental/ugrid/__init__.py b/lib/iris/tests/unit/experimental/ugrid/__init__.py new file mode 100644 index 0000000000..27d7921e5f --- /dev/null +++ b/lib/iris/tests/unit/experimental/ugrid/__init__.py @@ -0,0 +1,5 @@ +# Copyright Iris contributors +# +# This file is part of Iris and is released under the BSD license. +# See LICENSE in the root of the repository for full licensing details. +"""Unit tests for the :mod:`iris.experimental.ugrid` package.""" diff --git a/lib/iris/tests/unit/ugrid/load/test_ParseUgridOnLoad.py b/lib/iris/tests/unit/experimental/ugrid/test_ParseUgridOnLoad.py similarity index 81% rename from lib/iris/tests/unit/ugrid/load/test_ParseUgridOnLoad.py rename to lib/iris/tests/unit/experimental/ugrid/test_ParseUgridOnLoad.py index e47dfe8d50..62961157d8 100644 --- a/lib/iris/tests/unit/ugrid/load/test_ParseUgridOnLoad.py +++ b/lib/iris/tests/unit/experimental/ugrid/test_ParseUgridOnLoad.py @@ -2,7 +2,7 @@ # # This file is part of Iris and is released under the BSD license. # See LICENSE in the root of the repository for full licensing details. -"""Unit tests for the :class:`iris.ugrid.load.ParseUgridOnLoad` class. +"""Unit tests for the :class:`iris.experimental.ugrid.ParseUgridOnLoad` class. TODO: remove this module when ParseUGridOnLoad itself is removed. @@ -11,7 +11,7 @@ import pytest from iris._deprecation import IrisDeprecation -from iris.ugrid.load import PARSE_UGRID_ON_LOAD, ParseUGridOnLoad +from iris.experimental.ugrid import PARSE_UGRID_ON_LOAD, ParseUGridOnLoad def test_creation(): diff --git a/lib/iris/tests/unit/fileformats/cf/test_CFGroup.py b/lib/iris/tests/unit/fileformats/cf/test_CFGroup.py index e1b4b7a7cd..25f64319af 100644 --- a/lib/iris/tests/unit/fileformats/cf/test_CFGroup.py +++ b/lib/iris/tests/unit/fileformats/cf/test_CFGroup.py @@ -11,6 +11,9 @@ CFCoordinateVariable, CFDataVariable, CFGroup, + CFUGridAuxiliaryCoordinateVariable, + CFUGridConnectivityVariable, + CFUGridMeshVariable, ) # Import iris.tests first so that some things can be initialised before @@ -42,3 +45,67 @@ def test_non_data_names(self): expected_names = [var.cf_name for var in (aux_var, coord_var, coord_var2)] expected = set(expected_names) self.assertEqual(expected, self.cf_group.non_data_variable_names) + + +class Ugrid(tests.IrisTest): + """Separate class to test UGRID functionality.""" + + def setUp(self): + self.cf_group = CFGroup() + + def test_inherited(self): + coord_var = MagicMock(spec=CFCoordinateVariable, cf_name="coord_var") + self.cf_group[coord_var.cf_name] = coord_var + self.assertEqual(coord_var, self.cf_group.coordinates[coord_var.cf_name]) + + def test_connectivities(self): + conn_var = MagicMock(spec=CFUGridConnectivityVariable, cf_name="conn_var") + self.cf_group[conn_var.cf_name] = conn_var + self.assertEqual(conn_var, self.cf_group.connectivities[conn_var.cf_name]) + + def test_ugrid_coords(self): + coord_var = MagicMock( + spec=CFUGridAuxiliaryCoordinateVariable, cf_name="coord_var" + ) + self.cf_group[coord_var.cf_name] = coord_var + self.assertEqual(coord_var, self.cf_group.ugrid_coords[coord_var.cf_name]) + + def test_meshes(self): + mesh_var = MagicMock(spec=CFUGridMeshVariable, cf_name="mesh_var") + self.cf_group[mesh_var.cf_name] = mesh_var + self.assertEqual(mesh_var, self.cf_group.meshes[mesh_var.cf_name]) + + def test_non_data_names(self): + data_var = MagicMock(spec=CFDataVariable, cf_name="data_var") + coord_var = MagicMock(spec=CFCoordinateVariable, cf_name="coord_var") + conn_var = MagicMock(spec=CFUGridConnectivityVariable, cf_name="conn_var") + ugrid_coord_var = MagicMock( + spec=CFUGridAuxiliaryCoordinateVariable, cf_name="ugrid_coord_var" + ) + mesh_var = MagicMock(spec=CFUGridMeshVariable, cf_name="mesh_var") + mesh_var2 = MagicMock(spec=CFUGridMeshVariable, cf_name="mesh_var2") + duplicate_name_var = MagicMock(spec=CFUGridMeshVariable, cf_name="coord_var") + + for var in ( + data_var, + coord_var, + conn_var, + ugrid_coord_var, + mesh_var, + mesh_var2, + duplicate_name_var, + ): + self.cf_group[var.cf_name] = var + + expected_names = [ + var.cf_name + for var in ( + coord_var, + conn_var, + ugrid_coord_var, + mesh_var, + mesh_var2, + ) + ] + expected = set(expected_names) + self.assertEqual(expected, self.cf_group.non_data_variable_names) diff --git a/lib/iris/tests/unit/fileformats/cf/test_CFReader.py b/lib/iris/tests/unit/fileformats/cf/test_CFReader.py index 80338ea71e..12c1510413 100644 --- a/lib/iris/tests/unit/fileformats/cf/test_CFReader.py +++ b/lib/iris/tests/unit/fileformats/cf/test_CFReader.py @@ -12,7 +12,15 @@ import numpy as np -from iris.fileformats.cf import CFReader +from iris.fileformats.cf import ( + CFCoordinateVariable, + CFDataVariable, + CFGroup, + CFReader, + CFUGridAuxiliaryCoordinateVariable, + CFUGridConnectivityVariable, + CFUGridMeshVariable, +) def netcdf_variable( @@ -35,6 +43,12 @@ def netcdf_variable( ndim = len(dimensions) else: dimensions = [] + + ugrid_identities = ( + CFUGridAuxiliaryCoordinateVariable.cf_identities + + CFUGridConnectivityVariable.cf_identities + + [CFUGridMeshVariable.cf_identity] + ) ncvar = mock.Mock( name=name, dimensions=dimensions, @@ -49,6 +63,7 @@ def netcdf_variable( grid_mapping=grid_mapping, cell_measures=cell_measures, standard_name=standard_name, + **{name: None for name in ugrid_identities}, ) return ncvar @@ -350,5 +365,84 @@ def test_promoted_auxiliary_ignore(self): self.assertEqual(warn.call_count, 2) +class Test_build_cf_groups__ugrid(tests.IrisTest): + @classmethod + def setUpClass(cls): + # Replicating syntax from test_CFReader.Test_build_cf_groups__formula_terms. + cls.mesh = netcdf_variable("mesh", "", int) + cls.node_x = netcdf_variable("node_x", "node", float) + cls.node_y = netcdf_variable("node_y", "node", float) + cls.face_x = netcdf_variable("face_x", "face", float) + cls.face_y = netcdf_variable("face_y", "face", float) + cls.face_nodes = netcdf_variable("face_nodes", "face vertex", int) + cls.levels = netcdf_variable("levels", "levels", int) + cls.data = netcdf_variable( + "data", "levels face", float, coordinates="face_x face_y" + ) + + # Add necessary attributes for mesh recognition. + cls.mesh.cf_role = "mesh_topology" + cls.mesh.node_coordinates = "node_x node_y" + cls.mesh.face_coordinates = "face_x face_y" + cls.mesh.face_node_connectivity = "face_nodes" + cls.face_nodes.cf_role = "face_node_connectivity" + cls.data.mesh = "mesh" + + cls.variables = dict( + mesh=cls.mesh, + node_x=cls.node_x, + node_y=cls.node_y, + face_x=cls.face_x, + face_y=cls.face_y, + face_nodes=cls.face_nodes, + levels=cls.levels, + data=cls.data, + ) + ncattrs = mock.Mock(return_value=[]) + cls.dataset = mock.Mock( + file_format="NetCDF4", variables=cls.variables, ncattrs=ncattrs + ) + + def setUp(self): + # Restrict the CFReader functionality to only performing + # translations and building first level cf-groups for variables. + self.patch("iris.fileformats.cf.CFReader._reset") + self.patch( + "iris.fileformats.netcdf._thread_safe_nc.DatasetWrapper", + return_value=self.dataset, + ) + cf_reader = CFReader("dummy") + self.cf_group = cf_reader.cf_group + + def test_inherited(self): + for expected_var, collection in ( + [CFCoordinateVariable("levels", self.levels), "coordinates"], + [CFDataVariable("data", self.data), "data_variables"], + ): + expected = {expected_var.cf_name: expected_var} + self.assertDictEqual(expected, getattr(self.cf_group, collection)) + + def test_connectivities(self): + expected_var = CFUGridConnectivityVariable("face_nodes", self.face_nodes) + expected = {expected_var.cf_name: expected_var} + self.assertDictEqual(expected, self.cf_group.connectivities) + + def test_mesh(self): + expected_var = CFUGridMeshVariable("mesh", self.mesh) + expected = {expected_var.cf_name: expected_var} + self.assertDictEqual(expected, self.cf_group.meshes) + + def test_ugrid_coords(self): + names = [f"{loc}_{ax}" for loc in ("node", "face") for ax in ("x", "y")] + expected = { + name: CFUGridAuxiliaryCoordinateVariable(name, getattr(self, name)) + for name in names + } + self.assertDictEqual(expected, self.cf_group.ugrid_coords) + + def test_is_cf_ugrid_group(self): + self.assertIsInstance(self.cf_group, CFGroup) + + if __name__ == "__main__": tests.main() diff --git a/lib/iris/tests/unit/ugrid/cf/test_CFUGridAuxiliaryCoordinateVariable.py b/lib/iris/tests/unit/fileformats/cf/test_CFUGridAuxiliaryCoordinateVariable.py similarity index 93% rename from lib/iris/tests/unit/ugrid/cf/test_CFUGridAuxiliaryCoordinateVariable.py rename to lib/iris/tests/unit/fileformats/cf/test_CFUGridAuxiliaryCoordinateVariable.py index abd4442d3b..d056de4aff 100644 --- a/lib/iris/tests/unit/ugrid/cf/test_CFUGridAuxiliaryCoordinateVariable.py +++ b/lib/iris/tests/unit/fileformats/cf/test_CFUGridAuxiliaryCoordinateVariable.py @@ -2,13 +2,7 @@ # # This file is part of Iris and is released under the BSD license. # See LICENSE in the root of the repository for full licensing details. -"""Unit tests for the :class:`iris.ugrid.cf.CFUGridAuxiliaryCoordinateVariable` class. - -todo: fold these tests into cf tests when iris.ugrid is folded into - standard behaviour. -TODO: complete iris.ugrid replacement - -""" +"""Unit tests for :class:`iris.fileformats.cf.CFUGridAuxiliaryCoordinateVariable`.""" # Import iris.tests first so that some things can be initialised before # importing anything else. @@ -20,16 +14,14 @@ import numpy as np import pytest -from iris.tests.unit.ugrid.cf.test_CFUGridReader import ( - netcdf_ugrid_variable, -) -from iris.ugrid.cf import CFUGridAuxiliaryCoordinateVariable +from iris.fileformats.cf import CFUGridAuxiliaryCoordinateVariable +from iris.tests.unit.fileformats.cf.test_CFReader import netcdf_variable import iris.warnings def named_variable(name): # Don't need to worry about dimensions or dtype for these tests. - return netcdf_ugrid_variable(name, "", int) + return netcdf_variable(name, "", int) class TestIdentify(tests.IrisTest): @@ -130,7 +122,7 @@ def test_string_type_ignored(self): ref_source = named_variable("ref_source") setattr(ref_source, self.cf_identities[0], subject_name) vars_all = { - subject_name: netcdf_ugrid_variable(subject_name, "", np.bytes_), + subject_name: netcdf_variable(subject_name, "", np.bytes_), "ref_not_subject": named_variable("ref_not_subject"), "ref_source": ref_source, } @@ -221,7 +213,7 @@ def operation(warn: bool): # String variable warning. warn_regex = r".*is a CF-netCDF label variable.*" - vars_all[subject_name] = netcdf_ugrid_variable(subject_name, "", np.bytes_) + vars_all[subject_name] = netcdf_variable(subject_name, "", np.bytes_) with pytest.warns(iris.warnings.IrisCfLabelVarWarning, match=warn_regex): operation(warn=True) with pytest.warns() as record: diff --git a/lib/iris/tests/unit/ugrid/cf/test_CFUGridConnectivityVariable.py b/lib/iris/tests/unit/fileformats/cf/test_CFUGridConnectivityVariable.py similarity index 92% rename from lib/iris/tests/unit/ugrid/cf/test_CFUGridConnectivityVariable.py rename to lib/iris/tests/unit/fileformats/cf/test_CFUGridConnectivityVariable.py index 743410a849..573e6f799f 100644 --- a/lib/iris/tests/unit/ugrid/cf/test_CFUGridConnectivityVariable.py +++ b/lib/iris/tests/unit/fileformats/cf/test_CFUGridConnectivityVariable.py @@ -2,13 +2,7 @@ # # This file is part of Iris and is released under the BSD license. # See LICENSE in the root of the repository for full licensing details. -"""Unit tests for the :class:`iris.ugrid.cf.CFUGridConnectivityVariable` class. - -todo: fold these tests into cf tests when iris.ugrid is folded into - standard behaviour. -TODO: complete iris.ugrid replacement - -""" +"""Unit tests for :class:`iris.fileformats.cf.CFUGridConnectivityVariable`.""" # Import iris.tests first so that some things can be initialised before # importing anything else. @@ -20,17 +14,15 @@ import numpy as np import pytest -from iris.tests.unit.ugrid.cf.test_CFUGridReader import ( - netcdf_ugrid_variable, -) -from iris.ugrid.cf import CFUGridConnectivityVariable -from iris.ugrid.mesh import Connectivity +from iris.fileformats.cf import CFUGridConnectivityVariable +from iris.mesh import Connectivity +from iris.tests.unit.fileformats.cf.test_CFReader import netcdf_variable import iris.warnings def named_variable(name): # Don't need to worry about dimensions or dtype for these tests. - return netcdf_ugrid_variable(name, "", int) + return netcdf_variable(name, "", int) class TestIdentify(tests.IrisTest): @@ -119,7 +111,7 @@ def test_string_type_ignored(self): ref_source = named_variable("ref_source") setattr(ref_source, Connectivity.UGRID_CF_ROLES[0], subject_name) vars_all = { - subject_name: netcdf_ugrid_variable(subject_name, "", np.bytes_), + subject_name: netcdf_variable(subject_name, "", np.bytes_), "ref_not_subject": named_variable("ref_not_subject"), "ref_source": ref_source, } @@ -204,7 +196,7 @@ def operation(warn: bool): # String variable warning. warn_regex = r".*is a CF-netCDF label variable.*" - vars_all[subject_name] = netcdf_ugrid_variable(subject_name, "", np.bytes_) + vars_all[subject_name] = netcdf_variable(subject_name, "", np.bytes_) with pytest.warns(iris.warnings.IrisCfLabelVarWarning, match=warn_regex): operation(warn=True) with pytest.warns() as record: diff --git a/lib/iris/tests/unit/ugrid/cf/test_CFUGridMeshVariable.py b/lib/iris/tests/unit/fileformats/cf/test_CFUGridMeshVariable.py similarity index 94% rename from lib/iris/tests/unit/ugrid/cf/test_CFUGridMeshVariable.py rename to lib/iris/tests/unit/fileformats/cf/test_CFUGridMeshVariable.py index f93c1e89a1..5205c6a018 100644 --- a/lib/iris/tests/unit/ugrid/cf/test_CFUGridMeshVariable.py +++ b/lib/iris/tests/unit/fileformats/cf/test_CFUGridMeshVariable.py @@ -2,13 +2,7 @@ # # This file is part of Iris and is released under the BSD license. # See LICENSE in the root of the repository for full licensing details. -"""Unit tests for the :class:`iris.ugrid.cf.CFUGridMeshVariable` class. - -todo: fold these tests into cf tests when ugrid is folded into - standard behaviour. -TODO: complete iris.ugrid replacement - -""" +"""Unit tests for :class:`iris.fileformats.cf.CFUGridMeshVariable`.""" # Import iris.tests first so that some things can be initialised before # importing anything else. @@ -20,16 +14,14 @@ import numpy as np import pytest -from iris.tests.unit.ugrid.cf.test_CFUGridReader import ( - netcdf_ugrid_variable, -) -from iris.ugrid.cf import CFUGridMeshVariable +from iris.fileformats.cf import CFUGridMeshVariable +from iris.tests.unit.fileformats.cf.test_CFReader import netcdf_variable import iris.warnings def named_variable(name): # Don't need to worry about dimensions or dtype for these tests. - return netcdf_ugrid_variable(name, "", int) + return netcdf_variable(name, "", int) class TestIdentify(tests.IrisTest): @@ -166,7 +158,7 @@ def test_string_type_ignored(self): ref_source = named_variable("ref_source") setattr(ref_source, self.cf_identity, subject_name) vars_all = { - subject_name: netcdf_ugrid_variable(subject_name, "", np.bytes_), + subject_name: netcdf_variable(subject_name, "", np.bytes_), "ref_not_subject": named_variable("ref_not_subject"), "ref_source": ref_source, } @@ -251,7 +243,7 @@ def operation(warn: bool): # String variable warning. warn_regex = r".*is a CF-netCDF label variable.*" - vars_all[subject_name] = netcdf_ugrid_variable(subject_name, "", np.bytes_) + vars_all[subject_name] = netcdf_variable(subject_name, "", np.bytes_) with pytest.warns(iris.warnings.IrisCfLabelVarWarning, match=warn_regex): operation(warn=True) with pytest.warns() as record: diff --git a/lib/iris/tests/unit/fileformats/netcdf/loader/test_load_cubes.py b/lib/iris/tests/unit/fileformats/netcdf/loader/test_load_cubes.py index c90f710110..09ee679adf 100644 --- a/lib/iris/tests/unit/fileformats/netcdf/loader/test_load_cubes.py +++ b/lib/iris/tests/unit/fileformats/netcdf/loader/test_load_cubes.py @@ -23,8 +23,8 @@ from iris.coords import AncillaryVariable, CellMeasure from iris.fileformats.netcdf import logger from iris.fileformats.netcdf.loader import load_cubes +from iris.mesh import MeshCoord from iris.tests.stock.netcdf import ncgen_from_cdl -from iris.ugrid.mesh import MeshCoord def setUpModule(): diff --git a/lib/iris/tests/unit/fileformats/netcdf/loader/ugrid_load/__init__.py b/lib/iris/tests/unit/fileformats/netcdf/loader/ugrid_load/__init__.py new file mode 100644 index 0000000000..993d106ba3 --- /dev/null +++ b/lib/iris/tests/unit/fileformats/netcdf/loader/ugrid_load/__init__.py @@ -0,0 +1,5 @@ +# Copyright Iris contributors +# +# This file is part of Iris and is released under the BSD license. +# See LICENSE in the root of the repository for full licensing details. +"""Unit tests for the :mod:`iris.fileformats.netcdf.ugrid_load` package.""" diff --git a/lib/iris/tests/unit/ugrid/load/test_load_mesh.py b/lib/iris/tests/unit/fileformats/netcdf/loader/ugrid_load/test_load_mesh.py similarity index 86% rename from lib/iris/tests/unit/ugrid/load/test_load_mesh.py rename to lib/iris/tests/unit/fileformats/netcdf/loader/ugrid_load/test_load_mesh.py index 5f2308c68e..0e618c7d55 100644 --- a/lib/iris/tests/unit/ugrid/load/test_load_mesh.py +++ b/lib/iris/tests/unit/fileformats/netcdf/loader/ugrid_load/test_load_mesh.py @@ -2,20 +2,21 @@ # # This file is part of Iris and is released under the BSD license. # See LICENSE in the root of the repository for full licensing details. -"""Unit tests for the :func:`iris.ugrid.load.load_mesh` function.""" +"""Unit tests for the :func:`iris.mesh.load_mesh` function.""" # Import iris.tests first so that some things can be initialised before # importing anything else. import iris.tests as tests # isort:skip -from iris.ugrid.load import load_mesh +from iris.fileformats.netcdf.ugrid_load import load_mesh class Tests(tests.IrisTest): # All 'real' tests have been done for load_meshes(). Here we just check # that load_mesh() works with load_meshes() correctly, using mocking. def setUp(self): - self.load_meshes_mock = self.patch("iris.ugrid.load.load_meshes") + tgt = "iris.fileformats.netcdf.ugrid_load.load_meshes" + self.load_meshes_mock = self.patch(tgt) # The expected return from load_meshes - a dict of files, each with # a list of meshes. self.load_meshes_mock.return_value = {"file": ["mesh"]} diff --git a/lib/iris/tests/unit/ugrid/load/test_load_meshes.py b/lib/iris/tests/unit/fileformats/netcdf/loader/ugrid_load/test_load_meshes.py similarity index 98% rename from lib/iris/tests/unit/ugrid/load/test_load_meshes.py rename to lib/iris/tests/unit/fileformats/netcdf/loader/ugrid_load/test_load_meshes.py index 3847514a02..424c321098 100644 --- a/lib/iris/tests/unit/ugrid/load/test_load_meshes.py +++ b/lib/iris/tests/unit/fileformats/netcdf/loader/ugrid_load/test_load_meshes.py @@ -2,7 +2,7 @@ # # This file is part of Iris and is released under the BSD license. # See LICENSE in the root of the repository for full licensing details. -"""Unit tests for the :func:`iris.ugrid.load.load_meshes` function.""" +"""Unit tests for the :func:`iris.mesh.load_meshes` function.""" # Import iris.tests first so that some things can be initialised before # importing anything else. @@ -13,8 +13,8 @@ import tempfile from uuid import uuid4 +from iris.fileformats.netcdf.ugrid_load import load_meshes, logger from iris.tests.stock.netcdf import ncgen_from_cdl -from iris.ugrid.load import load_meshes, logger def setUpModule(): diff --git a/lib/iris/tests/unit/ugrid/load/test_meshload_checks.py b/lib/iris/tests/unit/fileformats/netcdf/loader/ugrid_load/test_meshload_checks.py similarity index 100% rename from lib/iris/tests/unit/ugrid/load/test_meshload_checks.py rename to lib/iris/tests/unit/fileformats/netcdf/loader/ugrid_load/test_meshload_checks.py diff --git a/lib/iris/tests/unit/fileformats/netcdf/saver/test_Saver__ugrid.py b/lib/iris/tests/unit/fileformats/netcdf/saver/test_Saver__ugrid.py index c8b422c7b3..7508376840 100644 --- a/lib/iris/tests/unit/fileformats/netcdf/saver/test_Saver__ugrid.py +++ b/lib/iris/tests/unit/fileformats/netcdf/saver/test_Saver__ugrid.py @@ -23,9 +23,8 @@ from iris.coords import AuxCoord from iris.cube import Cube, CubeList from iris.fileformats.netcdf import _thread_safe_nc +from iris.mesh import Connectivity, MeshXY, save_mesh from iris.tests.stock import realistic_4d -from iris.ugrid.mesh import Connectivity, MeshXY -from iris.ugrid.save import save_mesh XY_LOCS = ("x", "y") XY_NAMES = ("longitude", "latitude") @@ -196,7 +195,7 @@ def make_cube(mesh=None, location="face", **kwargs): Parameters ---------- - mesh : :class:`iris.ugrid.mesh.MeshXY` or None, optional + mesh : :class:`iris.mesh.MeshXY` or None, optional If None, use 'default_mesh()' location : str, optional, default="face" Which mesh element to map the cube to. diff --git a/lib/iris/tests/unit/ugrid/utils/__init__.py b/lib/iris/tests/unit/mesh/__init__.py similarity index 78% rename from lib/iris/tests/unit/ugrid/utils/__init__.py rename to lib/iris/tests/unit/mesh/__init__.py index 7bc5f68717..1305bda078 100644 --- a/lib/iris/tests/unit/ugrid/utils/__init__.py +++ b/lib/iris/tests/unit/mesh/__init__.py @@ -2,6 +2,6 @@ # # This file is part of Iris and is released under the BSD license. # See LICENSE in the root of the repository for full licensing details. -"""Unit tests for the :mod:`iris.ugrid.utils` package.""" +"""Unit tests for the :mod:`iris.mesh` package.""" from __future__ import annotations diff --git a/lib/iris/tests/unit/ugrid/cf/__init__.py b/lib/iris/tests/unit/mesh/components/__init__.py similarity index 73% rename from lib/iris/tests/unit/ugrid/cf/__init__.py rename to lib/iris/tests/unit/mesh/components/__init__.py index dc57b9d980..cc0effb1f6 100644 --- a/lib/iris/tests/unit/ugrid/cf/__init__.py +++ b/lib/iris/tests/unit/mesh/components/__init__.py @@ -2,4 +2,4 @@ # # This file is part of Iris and is released under the BSD license. # See LICENSE in the root of the repository for full licensing details. -"""Unit tests for the :mod:`iris.ugrid.cf` package.""" +"""Unit tests for the :mod:`iris.mesh.components` package.""" diff --git a/lib/iris/tests/unit/ugrid/mesh/test_Connectivity.py b/lib/iris/tests/unit/mesh/components/test_Connectivity.py similarity index 99% rename from lib/iris/tests/unit/ugrid/mesh/test_Connectivity.py rename to lib/iris/tests/unit/mesh/components/test_Connectivity.py index 507176a943..de8d7de3d7 100644 --- a/lib/iris/tests/unit/ugrid/mesh/test_Connectivity.py +++ b/lib/iris/tests/unit/mesh/components/test_Connectivity.py @@ -2,7 +2,7 @@ # # This file is part of Iris and is released under the BSD license. # See LICENSE in the root of the repository for full licensing details. -"""Unit tests for the :class:`iris.ugrid.mesh.Connectivity` class.""" +"""Unit tests for the :class:`iris.mesh.Connectivity` class.""" # Import iris.tests first so that some things can be initialised before # importing anything else. @@ -16,7 +16,7 @@ from packaging import version from iris._lazy_data import as_lazy_data, is_lazy_data -from iris.ugrid.mesh import Connectivity +from iris.mesh import Connectivity class TestStandard(tests.IrisTest): diff --git a/lib/iris/tests/unit/ugrid/mesh/test_MeshCoord.py b/lib/iris/tests/unit/mesh/components/test_MeshCoord.py similarity index 99% rename from lib/iris/tests/unit/ugrid/mesh/test_MeshCoord.py rename to lib/iris/tests/unit/mesh/components/test_MeshCoord.py index 38d48b8be8..0acec1985d 100644 --- a/lib/iris/tests/unit/ugrid/mesh/test_MeshCoord.py +++ b/lib/iris/tests/unit/mesh/components/test_MeshCoord.py @@ -2,7 +2,7 @@ # # This file is part of Iris and is released under the BSD license. # See LICENSE in the root of the repository for full licensing details. -"""Unit tests for the :class:`iris.ugrid.mesh.MeshCoord`.""" +"""Unit tests for the :class:`iris.mesh.MeshCoord`.""" # Import iris.tests first so that some things can be initialised before # importing anything else. @@ -21,9 +21,9 @@ from iris.common.metadata import BaseMetadata, CoordMetadata from iris.coords import AuxCoord, Coord from iris.cube import Cube +from iris.mesh import Connectivity, MeshCoord, MeshXY import iris.tests.stock.mesh from iris.tests.stock.mesh import sample_mesh, sample_meshcoord -from iris.ugrid.mesh import Connectivity, MeshCoord, MeshXY from iris.warnings import IrisVagueMetadataWarning diff --git a/lib/iris/tests/unit/ugrid/mesh/test_Mesh.py b/lib/iris/tests/unit/mesh/components/test_MeshXY.py similarity index 92% rename from lib/iris/tests/unit/ugrid/mesh/test_Mesh.py rename to lib/iris/tests/unit/mesh/components/test_MeshXY.py index 4b26c7b064..c1977633e2 100644 --- a/lib/iris/tests/unit/ugrid/mesh/test_Mesh.py +++ b/lib/iris/tests/unit/mesh/components/test_MeshXY.py @@ -2,7 +2,7 @@ # # This file is part of Iris and is released under the BSD license. # See LICENSE in the root of the repository for full licensing details. -"""Unit tests for the :class:`mesh` class.""" +"""Unit tests for the :class:`iris.mesh.MeshXY` class.""" # Import iris.tests first so that some things can be initialised before # importing anything else. @@ -10,10 +10,11 @@ import numpy as np +from iris.common.metadata import MeshMetadata from iris.coords import AuxCoord from iris.exceptions import ConnectivityNotFoundError, CoordinateNotFoundError -from iris.ugrid import mesh, metadata -from iris.ugrid.mesh import logger +from iris.mesh import components +from iris.mesh.components import logger class TestMeshCommon(tests.IrisTest): @@ -40,22 +41,28 @@ def setUpClass(cls): cls.FACE_LON = AuxCoord([0.5], standard_name="longitude", var_name="face_lon") cls.FACE_LAT = AuxCoord([0.5], standard_name="latitude", var_name="face_lat") - cls.EDGE_NODE = mesh.Connectivity( + cls.EDGE_NODE = components.Connectivity( [[0, 1], [1, 2], [2, 0]], cf_role="edge_node_connectivity", long_name="long_name", var_name="var_name", attributes={"test": 1}, ) - cls.FACE_NODE = mesh.Connectivity([[0, 1, 2]], cf_role="face_node_connectivity") - cls.FACE_EDGE = mesh.Connectivity([[0, 1, 2]], cf_role="face_edge_connectivity") + cls.FACE_NODE = components.Connectivity( + [[0, 1, 2]], cf_role="face_node_connectivity" + ) + cls.FACE_EDGE = components.Connectivity( + [[0, 1, 2]], cf_role="face_edge_connectivity" + ) # (Actually meaningless:) - cls.FACE_FACE = mesh.Connectivity([[0, 0, 0]], cf_role="face_face_connectivity") + cls.FACE_FACE = components.Connectivity( + [[0, 0, 0]], cf_role="face_face_connectivity" + ) # (Actually meaningless:) - cls.EDGE_FACE = mesh.Connectivity( + cls.EDGE_FACE = components.Connectivity( [[0, 0], [0, 0], [0, 0]], cf_role="edge_face_connectivity" ) - cls.BOUNDARY_NODE = mesh.Connectivity( + cls.BOUNDARY_NODE = components.Connectivity( [[0, 1], [1, 2], [2, 0]], cf_role="boundary_node_connectivity" ) @@ -78,12 +85,12 @@ def setUpClass(cls): "edge_dimension": "EdgeDim", "edge_coords_and_axes": ((cls.EDGE_LON, "x"), (cls.EDGE_LAT, "y")), } - cls.mesh = mesh.MeshXY(**cls.kwargs) + cls.mesh = components.MeshXY(**cls.kwargs) def test__metadata_manager(self): self.assertEqual( self.mesh._metadata_manager.cls.__name__, - metadata.MeshMetadata.__name__, + MeshMetadata.__name__, ) def test___getstate__(self): @@ -127,13 +134,13 @@ def test___eq__(self): # The dimension names do not participate in equality. equivalent_kwargs = self.kwargs.copy() equivalent_kwargs["node_dimension"] = "something_else" - equivalent = mesh.MeshXY(**equivalent_kwargs) + equivalent = components.MeshXY(**equivalent_kwargs) self.assertEqual(equivalent, self.mesh) def test_different(self): different_kwargs = self.kwargs.copy() different_kwargs["long_name"] = "new_name" - different = mesh.MeshXY(**different_kwargs) + different = components.MeshXY(**different_kwargs) self.assertNotEqual(different, self.mesh) different_kwargs = self.kwargs.copy() @@ -141,22 +148,22 @@ def test_different(self): new_lat = ncaa[1][0].copy(points=ncaa[1][0].points + 1) new_ncaa = (ncaa[0], (new_lat, "y")) different_kwargs["node_coords_and_axes"] = new_ncaa - different = mesh.MeshXY(**different_kwargs) + different = components.MeshXY(**different_kwargs) self.assertNotEqual(different, self.mesh) different_kwargs = self.kwargs.copy() conns = self.kwargs["connectivities"] new_conn = conns[0].copy(conns[0].indices + 1) different_kwargs["connectivities"] = new_conn - different = mesh.MeshXY(**different_kwargs) + different = components.MeshXY(**different_kwargs) self.assertNotEqual(different, self.mesh) def test_all_connectivities(self): - expected = mesh.Mesh1DConnectivities(self.EDGE_NODE) + expected = components.Mesh1DConnectivities(self.EDGE_NODE) self.assertEqual(expected, self.mesh.all_connectivities) def test_all_coords(self): - expected = mesh.Mesh1DCoords( + expected = components.Mesh1DCoords( self.NODE_LON, self.NODE_LAT, self.EDGE_LON, self.EDGE_LAT ) self.assertEqual(expected, self.mesh.all_coords) @@ -181,7 +188,9 @@ def test_connectivities(self): {"cf_role": "edge_node_connectivity"}, ) - fake_connectivity = tests.mock.Mock(__class__=mesh.Connectivity, cf_role="fake") + fake_connectivity = tests.mock.Mock( + __class__=components.Connectivity, cf_role="fake" + ) negative_kwargs = ( {"item": fake_connectivity}, {"item": "foo"}, @@ -295,7 +304,7 @@ def test_edge_dimension(self): self.assertEqual(self.kwargs["edge_dimension"], self.mesh.edge_dimension) def test_edge_coords(self): - expected = mesh.MeshEdgeCoords(self.EDGE_LON, self.EDGE_LAT) + expected = components.MeshEdgeCoords(self.EDGE_LON, self.EDGE_LAT) self.assertEqual(expected, self.mesh.edge_coords) def test_edge_face(self): @@ -325,7 +334,7 @@ def test_face_node(self): _ = self.mesh.face_node_connectivity def test_node_coords(self): - expected = mesh.MeshNodeCoords(self.NODE_LON, self.NODE_LAT) + expected = components.MeshNodeCoords(self.NODE_LON, self.NODE_LAT) self.assertEqual(expected, self.mesh.node_coords) def test_node_dimension(self): @@ -360,7 +369,7 @@ def setUpClass(cls): (cls.FACE_LON, "x"), (cls.FACE_LAT, "y"), ) - cls.mesh = mesh.MeshXY(**cls.kwargs) + cls.mesh = components.MeshXY(**cls.kwargs) def test___repr__(self): expected = "" @@ -417,7 +426,7 @@ def test___str__(self): def test___str__noedgecoords(self): mesh_kwargs = self.kwargs.copy() del mesh_kwargs["edge_coords_and_axes"] - alt_mesh = mesh.MeshXY(**mesh_kwargs) + alt_mesh = components.MeshXY(**mesh_kwargs) expected = [ "MeshXY : 'my_topology_mesh'", " topology_dimension: 2", @@ -462,7 +471,7 @@ def test___str__noedgecoords(self): self.assertEqual(expected, str(alt_mesh).split("\n")) def test_all_connectivities(self): - expected = mesh.Mesh2DConnectivities( + expected = components.Mesh2DConnectivities( self.FACE_NODE, self.EDGE_NODE, self.FACE_EDGE, @@ -473,7 +482,7 @@ def test_all_connectivities(self): self.assertEqual(expected, self.mesh.all_connectivities) def test_all_coords(self): - expected = mesh.Mesh2DCoords( + expected = components.Mesh2DCoords( self.NODE_LON, self.NODE_LAT, self.EDGE_LON, @@ -583,7 +592,7 @@ def test_edge_face(self): self.assertEqual(self.EDGE_FACE, self.mesh.edge_face_connectivity) def test_face_coords(self): - expected = mesh.MeshFaceCoords(self.FACE_LON, self.FACE_LAT) + expected = components.MeshFaceCoords(self.FACE_LON, self.FACE_LAT) self.assertEqual(expected, self.mesh.face_coords) def test_face_dimension(self): @@ -624,7 +633,7 @@ def setUp(self): (self.EDGE_LAT, "y"), ), } - self.mesh = mesh.MeshXY(**self.kwargs) + self.mesh = components.MeshXY(**self.kwargs) def test___repr__basic(self): expected = "" @@ -667,7 +676,7 @@ def test___str__units_stdname(self): mesh_kwargs = self.kwargs.copy() mesh_kwargs["standard_name"] = "height" # Odd choice ! mesh_kwargs["units"] = "m" - alt_mesh = mesh.MeshXY(**mesh_kwargs) + alt_mesh = components.MeshXY(**mesh_kwargs) result = str(alt_mesh) # We expect these to appear at the end. expected = "\n".join( @@ -690,7 +699,7 @@ class TestOperations1D(TestMeshCommon): # Tests that cannot reuse an existing MeshXY instance, instead need a new # one each time. def setUp(self): - self.mesh = mesh.MeshXY( + self.mesh = components.MeshXY( topology_dimension=1, node_coords_and_axes=((self.NODE_LON, "x"), (self.NODE_LAT, "y")), connectivities=self.EDGE_NODE, @@ -740,7 +749,7 @@ def test_add_connectivities(self): edge_node = self.new_connectivity(self.EDGE_NODE, new_len) self.mesh.add_connectivities(edge_node) self.assertEqual( - mesh.Mesh1DConnectivities(edge_node), + components.Mesh1DConnectivities(edge_node), self.mesh.all_connectivities, ) @@ -771,7 +780,7 @@ def test_add_coords(self): edge_kwargs = {"edge_x": self.EDGE_LON, "edge_y": self.EDGE_LAT} self.mesh.add_coords(**edge_kwargs) self.assertEqual( - mesh.MeshEdgeCoords(**edge_kwargs), + components.MeshEdgeCoords(**edge_kwargs), self.mesh.edge_coords, ) @@ -788,11 +797,11 @@ def test_add_coords(self): } self.mesh.add_coords(**node_kwargs, **edge_kwargs) self.assertEqual( - mesh.MeshNodeCoords(**node_kwargs), + components.MeshNodeCoords(**node_kwargs), self.mesh.node_coords, ) self.assertEqual( - mesh.MeshEdgeCoords(**edge_kwargs), + components.MeshEdgeCoords(**edge_kwargs), self.mesh.edge_coords, ) @@ -833,17 +842,17 @@ def test_add_coords_invalid(self): def test_add_coords_single(self): # ADD coord. edge_x = self.EDGE_LON - expected = mesh.MeshEdgeCoords(edge_x=edge_x, edge_y=None) + expected = components.MeshEdgeCoords(edge_x=edge_x, edge_y=None) self.mesh.add_coords(edge_x=edge_x) self.assertEqual(expected, self.mesh.edge_coords) # REPLACE coords. node_x = self.new_coord(self.NODE_LON) edge_x = self.new_coord(self.EDGE_LON) - expected_nodes = mesh.MeshNodeCoords( + expected_nodes = components.MeshNodeCoords( node_x=node_x, node_y=self.mesh.node_coords.node_y ) - expected_edges = mesh.MeshEdgeCoords(edge_x=edge_x, edge_y=None) + expected_edges = components.MeshEdgeCoords(edge_x=edge_x, edge_y=None) self.mesh.add_coords(node_x=node_x, edge_x=edge_x) self.assertEqual(expected_nodes, self.mesh.node_coords) self.assertEqual(expected_edges, self.mesh.edge_coords) @@ -867,14 +876,14 @@ def test_add_coords_single_face(self): def test_dimension_names(self): # Test defaults. - default = mesh.Mesh1DNames("Mesh1d_node", "Mesh1d_edge") + default = components.Mesh1DNames("Mesh1d_node", "Mesh1d_edge") self.assertEqual(default, self.mesh.dimension_names()) log_regex = r"Not setting face_dimension.*" with self.assertLogs(logger, level="DEBUG", msg_regex=log_regex): self.mesh.dimension_names("foo", "bar", "baz") self.assertEqual( - mesh.Mesh1DNames("foo", "bar"), + components.Mesh1DNames("foo", "bar"), self.mesh.dimension_names(), ) @@ -918,7 +927,9 @@ def test_remove_connectivities(self): {"contains_edge": True, "contains_node": True}, ) - fake_connectivity = tests.mock.Mock(__class__=mesh.Connectivity, cf_role="fake") + fake_connectivity = tests.mock.Mock( + __class__=components.Connectivity, cf_role="fake" + ) negative_kwargs = ( {"item": fake_connectivity}, {"item": "foo"}, @@ -995,7 +1006,7 @@ def test_to_MeshCoord(self): location = "node" axis = "x" result = self.mesh.to_MeshCoord(location, axis) - self.assertIsInstance(result, mesh.MeshCoord) + self.assertIsInstance(result, components.MeshCoord) self.assertEqual(location, result.location) self.assertEqual(axis, result.axis) @@ -1012,7 +1023,7 @@ def test_to_MeshCoords(self): self.assertEqual(len(self.mesh.AXES), len(result)) for ix, axis in enumerate(self.mesh.AXES): coord = result[ix] - self.assertIsInstance(coord, mesh.MeshCoord) + self.assertIsInstance(coord, components.MeshCoord) self.assertEqual(location, coord.location) self.assertEqual(axis, coord.axis) @@ -1024,7 +1035,7 @@ def test_to_MeshCoords_face(self): class TestOperations2D(TestOperations1D): # Additional/specialised tests for topology_dimension=2. def setUp(self): - self.mesh = mesh.MeshXY( + self.mesh = components.MeshXY( topology_dimension=2, node_coords_and_axes=((self.NODE_LON, "x"), (self.NODE_LAT, "y")), connectivities=(self.FACE_NODE), @@ -1039,7 +1050,7 @@ def test_add_connectivities(self): "edge_face": self.EDGE_FACE, "boundary_node": self.BOUNDARY_NODE, } - expected = mesh.Mesh2DConnectivities( + expected = components.Mesh2DConnectivities( face_node=self.mesh.face_node_connectivity, **kwargs ) self.mesh.add_connectivities(*kwargs.values()) @@ -1053,7 +1064,7 @@ def test_add_connectivities(self): kwargs = {k: self.new_connectivity(v, new_len) for k, v in kwargs.items()} self.mesh.add_connectivities(*kwargs.values()) self.assertEqual( - mesh.Mesh2DConnectivities(**kwargs), + components.Mesh2DConnectivities(**kwargs), self.mesh.all_connectivities, ) @@ -1081,7 +1092,7 @@ def test_add_connectivities_inconsistent(self): ) def test_add_connectivities_invalid(self): - fake_cf_role = tests.mock.Mock(__class__=mesh.Connectivity, cf_role="foo") + fake_cf_role = tests.mock.Mock(__class__=components.Connectivity, cf_role="foo") log_regex = r"Not adding connectivity.*" with self.assertLogs(logger, level="DEBUG", msg_regex=log_regex): self.mesh.add_connectivities(fake_cf_role) @@ -1091,7 +1102,7 @@ def test_add_coords_face(self): kwargs = {"face_x": self.FACE_LON, "face_y": self.FACE_LAT} self.mesh.add_coords(**kwargs) self.assertEqual( - mesh.MeshFaceCoords(**kwargs), + components.MeshFaceCoords(**kwargs), self.mesh.face_coords, ) @@ -1104,20 +1115,20 @@ def test_add_coords_face(self): } self.mesh.add_coords(**kwargs) self.assertEqual( - mesh.MeshFaceCoords(**kwargs), + components.MeshFaceCoords(**kwargs), self.mesh.face_coords, ) def test_add_coords_single_face(self): # ADD coord. face_x = self.FACE_LON - expected = mesh.MeshFaceCoords(face_x=face_x, face_y=None) + expected = components.MeshFaceCoords(face_x=face_x, face_y=None) self.mesh.add_coords(face_x=face_x) self.assertEqual(expected, self.mesh.face_coords) # REPLACE coord. face_x = self.new_coord(self.FACE_LON) - expected = mesh.MeshFaceCoords(face_x=face_x, face_y=None) + expected = components.MeshFaceCoords(face_x=face_x, face_y=None) self.mesh.add_coords(face_x=face_x) self.assertEqual(expected, self.mesh.face_coords) @@ -1132,12 +1143,12 @@ def test_add_coords_single_face(self): def test_dimension_names(self): # Test defaults. - default = mesh.Mesh2DNames("Mesh2d_node", "Mesh2d_edge", "Mesh2d_face") + default = components.Mesh2DNames("Mesh2d_node", "Mesh2d_edge", "Mesh2d_face") self.assertEqual(default, self.mesh.dimension_names()) self.mesh.dimension_names("foo", "bar", "baz") self.assertEqual( - mesh.Mesh2DNames("foo", "bar", "baz"), + components.Mesh2DNames("foo", "bar", "baz"), self.mesh.dimension_names(), ) @@ -1179,7 +1190,7 @@ def test_to_MeshCoord_face(self): location = "face" axis = "x" result = self.mesh.to_MeshCoord(location, axis) - self.assertIsInstance(result, mesh.MeshCoord) + self.assertIsInstance(result, components.MeshCoord) self.assertEqual(location, result.location) self.assertEqual(axis, result.axis) @@ -1190,7 +1201,7 @@ def test_to_MeshCoords_face(self): self.assertEqual(len(self.mesh.AXES), len(result)) for ix, axis in enumerate(self.mesh.AXES): coord = result[ix] - self.assertIsInstance(coord, mesh.MeshCoord) + self.assertIsInstance(coord, components.MeshCoord) self.assertEqual(location, coord.location) self.assertEqual(axis, coord.axis) @@ -1208,7 +1219,7 @@ def test_invalid_topology(self): self.assertRaisesRegex( ValueError, "Expected 'topology_dimension'.*", - mesh.MeshXY, + components.MeshXY, **kwargs, ) @@ -1220,7 +1231,7 @@ def test_invalid_axes(self): self.assertRaisesRegex( ValueError, "Invalid axis specified for node.*", - mesh.MeshXY, + components.MeshXY, node_coords_and_axes=( (self.NODE_LON, "foo"), (self.NODE_LAT, "y"), @@ -1234,14 +1245,14 @@ def test_invalid_axes(self): self.assertRaisesRegex( ValueError, "Invalid axis specified for edge.*", - mesh.MeshXY, + components.MeshXY, edge_coords_and_axes=((self.EDGE_LON, "foo"),), **kwargs, ) self.assertRaisesRegex( ValueError, "Invalid axis specified for face.*", - mesh.MeshXY, + components.MeshXY, face_coords_and_axes=((self.FACE_LON, "foo"),), **kwargs, ) @@ -1261,7 +1272,7 @@ def test_minimum_connectivities(self): self.assertRaisesRegex( ValueError, ".*requires a edge_node_connectivity.*", - mesh.MeshXY, + components.MeshXY, **kwargs, ) @@ -1275,7 +1286,7 @@ def test_minimum_coords(self): self.assertRaisesRegex( ValueError, ".*is a required coordinate.*", - mesh.MeshXY, + components.MeshXY, **kwargs, ) diff --git a/lib/iris/tests/unit/ugrid/mesh/test_Mesh__from_coords.py b/lib/iris/tests/unit/mesh/components/test_MeshXY__from_coords.py similarity index 98% rename from lib/iris/tests/unit/ugrid/mesh/test_Mesh__from_coords.py rename to lib/iris/tests/unit/mesh/components/test_MeshXY__from_coords.py index 3043ae81b5..8114fbe92e 100644 --- a/lib/iris/tests/unit/ugrid/mesh/test_Mesh__from_coords.py +++ b/lib/iris/tests/unit/mesh/components/test_MeshXY__from_coords.py @@ -2,7 +2,7 @@ # # This file is part of Iris and is released under the BSD license. # See LICENSE in the root of the repository for full licensing details. -"""Unit tests for the :meth:`iris.ugrid.mesh.MeshXY.from_coords`.""" +"""Unit tests for the :meth:`iris.mesh.MeshXY.from_coords`.""" # Import iris.tests first so that some things can be initialised before # importing anything else. @@ -11,9 +11,8 @@ import numpy as np from iris.coords import AuxCoord, DimCoord +from iris.mesh import Connectivity, MeshXY, logger from iris.tests.stock import simple_2d_w_multidim_coords -from iris.ugrid import logger -from iris.ugrid.mesh import Connectivity, MeshXY class Test1Dim(tests.IrisTest): diff --git a/benchmarks/benchmarks/experimental/ugrid/__init__.py b/lib/iris/tests/unit/mesh/utils/__init__.py similarity index 75% rename from benchmarks/benchmarks/experimental/ugrid/__init__.py rename to lib/iris/tests/unit/mesh/utils/__init__.py index 4976054178..5a252f1529 100644 --- a/benchmarks/benchmarks/experimental/ugrid/__init__.py +++ b/lib/iris/tests/unit/mesh/utils/__init__.py @@ -2,4 +2,4 @@ # # This file is part of Iris and is released under the BSD license. # See LICENSE in the root of the repository for full licensing details. -"""Benchmark tests for the experimental.ugrid module.""" +"""Unit tests for the :mod:`iris.mesh.utils` package.""" diff --git a/lib/iris/tests/unit/ugrid/utils/test_recombine_submeshes.py b/lib/iris/tests/unit/mesh/utils/test_recombine_submeshes.py similarity index 99% rename from lib/iris/tests/unit/ugrid/utils/test_recombine_submeshes.py rename to lib/iris/tests/unit/mesh/utils/test_recombine_submeshes.py index 2617000a0e..5323dd5883 100644 --- a/lib/iris/tests/unit/ugrid/utils/test_recombine_submeshes.py +++ b/lib/iris/tests/unit/mesh/utils/test_recombine_submeshes.py @@ -2,7 +2,7 @@ # # This file is part of Iris and is released under the BSD license. # See LICENSE in the root of the repository for full licensing details. -"""Unit tests for :func:`iris.ugrid.utils.recombine_submeshes`.""" +"""Unit tests for :func:`iris.mesh.utils.recombine_submeshes`.""" # Import iris.tests first so that some things can be initialised before # importing anything else. @@ -13,8 +13,8 @@ from iris.coords import AuxCoord from iris.cube import CubeList +from iris.mesh.utils import recombine_submeshes from iris.tests.stock.mesh import sample_mesh, sample_mesh_cube -from iris.ugrid.utils import recombine_submeshes def common_test_setup(self, shape_3d=(0, 2), data_chunks=None): diff --git a/lib/iris/tests/unit/tests/stock/test_netcdf.py b/lib/iris/tests/unit/tests/stock/test_netcdf.py index 521f03e053..cccb93d28b 100644 --- a/lib/iris/tests/unit/tests/stock/test_netcdf.py +++ b/lib/iris/tests/unit/tests/stock/test_netcdf.py @@ -12,8 +12,8 @@ # Import iris.tests first so that some things can be initialised before # importing anything else. import iris.tests as tests # isort:skip +from iris.mesh import MeshCoord, MeshXY from iris.tests.stock import netcdf -from iris.ugrid.mesh import MeshCoord, MeshXY class XIOSFileMixin(tests.IrisTest): diff --git a/lib/iris/tests/unit/ugrid/cf/test_CFUGridGroup.py b/lib/iris/tests/unit/ugrid/cf/test_CFUGridGroup.py deleted file mode 100644 index 0226ec76e2..0000000000 --- a/lib/iris/tests/unit/ugrid/cf/test_CFUGridGroup.py +++ /dev/null @@ -1,87 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the BSD license. -# See LICENSE in the root of the repository for full licensing details. -"""Unit tests for the :class:`iris.ugrid.cf.CFUGridGroup` class. - -todo: fold these tests into cf tests when iris.ugrid is folded into - standard behaviour. -TODO: complete iris.ugrid replacement - -""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from unittest.mock import MagicMock - -from iris.fileformats.cf import CFCoordinateVariable, CFDataVariable -from iris.ugrid.cf import ( - CFUGridAuxiliaryCoordinateVariable, - CFUGridConnectivityVariable, - CFUGridGroup, - CFUGridMeshVariable, -) - - -class Tests(tests.IrisTest): - def setUp(self): - self.cf_group = CFUGridGroup() - - def test_inherited(self): - coord_var = MagicMock(spec=CFCoordinateVariable, cf_name="coord_var") - self.cf_group[coord_var.cf_name] = coord_var - self.assertEqual(coord_var, self.cf_group.coordinates[coord_var.cf_name]) - - def test_connectivities(self): - conn_var = MagicMock(spec=CFUGridConnectivityVariable, cf_name="conn_var") - self.cf_group[conn_var.cf_name] = conn_var - self.assertEqual(conn_var, self.cf_group.connectivities[conn_var.cf_name]) - - def test_ugrid_coords(self): - coord_var = MagicMock( - spec=CFUGridAuxiliaryCoordinateVariable, cf_name="coord_var" - ) - self.cf_group[coord_var.cf_name] = coord_var - self.assertEqual(coord_var, self.cf_group.ugrid_coords[coord_var.cf_name]) - - def test_meshes(self): - mesh_var = MagicMock(spec=CFUGridMeshVariable, cf_name="mesh_var") - self.cf_group[mesh_var.cf_name] = mesh_var - self.assertEqual(mesh_var, self.cf_group.meshes[mesh_var.cf_name]) - - def test_non_data_names(self): - data_var = MagicMock(spec=CFDataVariable, cf_name="data_var") - coord_var = MagicMock(spec=CFCoordinateVariable, cf_name="coord_var") - conn_var = MagicMock(spec=CFUGridConnectivityVariable, cf_name="conn_var") - ugrid_coord_var = MagicMock( - spec=CFUGridAuxiliaryCoordinateVariable, cf_name="ugrid_coord_var" - ) - mesh_var = MagicMock(spec=CFUGridMeshVariable, cf_name="mesh_var") - mesh_var2 = MagicMock(spec=CFUGridMeshVariable, cf_name="mesh_var2") - duplicate_name_var = MagicMock(spec=CFUGridMeshVariable, cf_name="coord_var") - - for var in ( - data_var, - coord_var, - conn_var, - ugrid_coord_var, - mesh_var, - mesh_var2, - duplicate_name_var, - ): - self.cf_group[var.cf_name] = var - - expected_names = [ - var.cf_name - for var in ( - coord_var, - conn_var, - ugrid_coord_var, - mesh_var, - mesh_var2, - ) - ] - expected = set(expected_names) - self.assertEqual(expected, self.cf_group.non_data_variable_names) diff --git a/lib/iris/tests/unit/ugrid/cf/test_CFUGridReader.py b/lib/iris/tests/unit/ugrid/cf/test_CFUGridReader.py deleted file mode 100644 index 5f36958e9a..0000000000 --- a/lib/iris/tests/unit/ugrid/cf/test_CFUGridReader.py +++ /dev/null @@ -1,129 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the BSD license. -# See LICENSE in the root of the repository for full licensing details. -"""Unit tests for the :class:`iris.ugrid.cf.CFUGridGroup` class. - -todo: fold these tests into cf tests when iris.ugrid is folded into - standard behaviour. -TODO: complete iris.ugrid replacement - -""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from unittest import mock - -from iris.fileformats.cf import CFCoordinateVariable, CFDataVariable -from iris.tests.unit.fileformats.cf.test_CFReader import netcdf_variable -from iris.ugrid.cf import ( - CFUGridAuxiliaryCoordinateVariable, - CFUGridConnectivityVariable, - CFUGridGroup, - CFUGridMeshVariable, - CFUGridReader, -) - - -def netcdf_ugrid_variable( - name, - dimensions, - dtype, - coordinates=None, -): - ncvar = netcdf_variable( - name=name, dimensions=dimensions, dtype=dtype, coordinates=coordinates - ) - - # Fill in all the extra UGRID attributes to prevent problems with getattr - # and Mock. Any attribute can be replaced in downstream setUp if present. - ugrid_attrs = ( - CFUGridAuxiliaryCoordinateVariable.cf_identities - + CFUGridConnectivityVariable.cf_identities - + [CFUGridMeshVariable.cf_identity] - ) - for attr in ugrid_attrs: - setattr(ncvar, attr, None) - - return ncvar - - -class Test_build_cf_groups(tests.IrisTest): - @classmethod - def setUpClass(cls): - # Replicating syntax from test_CFReader.Test_build_cf_groups__formula_terms. - cls.mesh = netcdf_ugrid_variable("mesh", "", int) - cls.node_x = netcdf_ugrid_variable("node_x", "node", float) - cls.node_y = netcdf_ugrid_variable("node_y", "node", float) - cls.face_x = netcdf_ugrid_variable("face_x", "face", float) - cls.face_y = netcdf_ugrid_variable("face_y", "face", float) - cls.face_nodes = netcdf_ugrid_variable("face_nodes", "face vertex", int) - cls.levels = netcdf_ugrid_variable("levels", "levels", int) - cls.data = netcdf_ugrid_variable( - "data", "levels face", float, coordinates="face_x face_y" - ) - - # Add necessary attributes for mesh recognition. - cls.mesh.cf_role = "mesh_topology" - cls.mesh.node_coordinates = "node_x node_y" - cls.mesh.face_coordinates = "face_x face_y" - cls.mesh.face_node_connectivity = "face_nodes" - cls.face_nodes.cf_role = "face_node_connectivity" - cls.data.mesh = "mesh" - - cls.variables = dict( - mesh=cls.mesh, - node_x=cls.node_x, - node_y=cls.node_y, - face_x=cls.face_x, - face_y=cls.face_y, - face_nodes=cls.face_nodes, - levels=cls.levels, - data=cls.data, - ) - ncattrs = mock.Mock(return_value=[]) - cls.dataset = mock.Mock( - file_format="NetCDF4", variables=cls.variables, ncattrs=ncattrs - ) - - def setUp(self): - # Restrict the CFUGridReader functionality to only performing - # translations and building first level cf-groups for variables. - self.patch("iris.ugrid.cf.CFUGridReader._reset") - self.patch( - "iris.fileformats.netcdf._thread_safe_nc.DatasetWrapper", - return_value=self.dataset, - ) - cf_reader = CFUGridReader("dummy") - self.cf_group = cf_reader.cf_group - - def test_inherited(self): - for expected_var, collection in ( - [CFCoordinateVariable("levels", self.levels), "coordinates"], - [CFDataVariable("data", self.data), "data_variables"], - ): - expected = {expected_var.cf_name: expected_var} - self.assertDictEqual(expected, getattr(self.cf_group, collection)) - - def test_connectivities(self): - expected_var = CFUGridConnectivityVariable("face_nodes", self.face_nodes) - expected = {expected_var.cf_name: expected_var} - self.assertDictEqual(expected, self.cf_group.connectivities) - - def test_mesh(self): - expected_var = CFUGridMeshVariable("mesh", self.mesh) - expected = {expected_var.cf_name: expected_var} - self.assertDictEqual(expected, self.cf_group.meshes) - - def test_ugrid_coords(self): - names = [f"{loc}_{ax}" for loc in ("node", "face") for ax in ("x", "y")] - expected = { - name: CFUGridAuxiliaryCoordinateVariable(name, getattr(self, name)) - for name in names - } - self.assertDictEqual(expected, self.cf_group.ugrid_coords) - - def test_is_cf_ugrid_group(self): - self.assertIsInstance(self.cf_group, CFUGridGroup) diff --git a/lib/iris/tests/unit/ugrid/load/__init__.py b/lib/iris/tests/unit/ugrid/load/__init__.py deleted file mode 100644 index b3552cc441..0000000000 --- a/lib/iris/tests/unit/ugrid/load/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the BSD license. -# See LICENSE in the root of the repository for full licensing details. -"""Unit tests for the :mod:`iris.ugrid.load` package.""" diff --git a/lib/iris/tests/unit/ugrid/mesh/__init__.py b/lib/iris/tests/unit/ugrid/mesh/__init__.py deleted file mode 100644 index f41d747a68..0000000000 --- a/lib/iris/tests/unit/ugrid/mesh/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the BSD license. -# See LICENSE in the root of the repository for full licensing details. -"""Unit tests for the :mod:`iris.ugrid.mesh` package.""" diff --git a/lib/iris/tests/unit/ugrid/metadata/__init__.py b/lib/iris/tests/unit/ugrid/metadata/__init__.py deleted file mode 100644 index e1ca32ec43..0000000000 --- a/lib/iris/tests/unit/ugrid/metadata/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the BSD license. -# See LICENSE in the root of the repository for full licensing details. -"""Unit tests for the :mod:`iris.ugrid.metadata` package.""" - -from __future__ import annotations diff --git a/lib/iris/ugrid/cf.py b/lib/iris/ugrid/cf.py deleted file mode 100644 index 27856b5d52..0000000000 --- a/lib/iris/ugrid/cf.py +++ /dev/null @@ -1,292 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the BSD license. -# See LICENSE in the root of the repository for full licensing details. - -"""Extensions to Iris' CF variable representation to represent CF UGrid variables. - -Eventual destination: :mod:`iris.fileformats.cf`. - -""" - -import warnings - -from ..fileformats import cf -from ..warnings import IrisCfLabelVarWarning, IrisCfMissingVarWarning -from .mesh import Connectivity - - -class CFUGridConnectivityVariable(cf.CFVariable): - """A CF_UGRID connectivity variable. - - A CF_UGRID connectivity variable points to an index variable identifying - for every element (edge/face/volume) the indices of its corner nodes. The - connectivity array will thus be a matrix of size n-elements x n-corners. - For the indexing one may use either 0- or 1-based indexing; the convention - used should be specified using a ``start_index`` attribute to the index - variable. - - For face elements: the corner nodes should be specified in anticlockwise - direction as viewed from above. For volume elements: use the - additional attribute ``volume_shape_type`` which points to a flag variable - that specifies for every volume its shape. - - Identified by a CF-netCDF variable attribute equal to any one of the values - in :attr:`~iris.ugrid.mesh.Connectivity.UGRID_CF_ROLES`. - - .. seealso:: - - The UGRID Conventions, https://ugrid-conventions.github.io/ugrid-conventions/ - - """ - - cf_identity = NotImplemented - cf_identities = Connectivity.UGRID_CF_ROLES - - @classmethod - def identify(cls, variables, ignore=None, target=None, warn=True): - result = {} - ignore, target = cls._identify_common(variables, ignore, target) - - # Identify all CF-UGRID connectivity variables. - for nc_var_name, nc_var in target.items(): - # Check for connectivity variable references, iterating through - # the valid cf roles. - for identity in cls.cf_identities: - nc_var_att = getattr(nc_var, identity, None) - - if nc_var_att is not None: - # UGRID only allows for one of each connectivity cf role. - name = nc_var_att.strip() - if name not in ignore: - if name not in variables: - message = ( - f"Missing CF-UGRID connectivity variable " - f"{name}, referenced by netCDF variable " - f"{nc_var_name}" - ) - if warn: - warnings.warn(message, category=IrisCfMissingVarWarning) - else: - # Restrict to non-string type i.e. not a - # CFLabelVariable. - if not cf._is_str_dtype(variables[name]): - result[name] = CFUGridConnectivityVariable( - name, variables[name] - ) - else: - message = ( - f"Ignoring variable {name}, identified " - f"as a CF-UGRID connectivity - is a " - f"CF-netCDF label variable." - ) - if warn: - warnings.warn( - message, category=IrisCfLabelVarWarning - ) - - return result - - -class CFUGridAuxiliaryCoordinateVariable(cf.CFVariable): - """A CF-UGRID auxiliary coordinate variable. - - A CF-UGRID auxiliary coordinate variable is a CF-netCDF auxiliary - coordinate variable representing the element (node/edge/face/volume) - locations (latitude, longitude or other spatial coordinates, and optional - elevation or other coordinates). These auxiliary coordinate variables will - have length n-elements. - - For elements other than nodes, these auxiliary coordinate variables may - have in turn a ``bounds`` attribute that specifies the bounding coordinates - of the element (thereby duplicating the data in the ``node_coordinates`` - variables). - - Identified by the CF-netCDF variable attribute - ``node_``/``edge_``/``face_``/``volume_coordinates``. - - .. seealso:: - - The UGRID Conventions, https://ugrid-conventions.github.io/ugrid-conventions/ - - """ - - cf_identity = NotImplemented - cf_identities = [ - "node_coordinates", - "edge_coordinates", - "face_coordinates", - "volume_coordinates", - ] - - @classmethod - def identify(cls, variables, ignore=None, target=None, warn=True): - result = {} - ignore, target = cls._identify_common(variables, ignore, target) - - # Identify any CF-UGRID-relevant auxiliary coordinate variables. - for nc_var_name, nc_var in target.items(): - # Check for UGRID auxiliary coordinate variable references. - for identity in cls.cf_identities: - nc_var_att = getattr(nc_var, identity, None) - - if nc_var_att is not None: - for name in nc_var_att.split(): - if name not in ignore: - if name not in variables: - message = ( - f"Missing CF-netCDF auxiliary coordinate " - f"variable {name}, referenced by netCDF " - f"variable {nc_var_name}" - ) - if warn: - warnings.warn( - message, - category=IrisCfMissingVarWarning, - ) - else: - # Restrict to non-string type i.e. not a - # CFLabelVariable. - if not cf._is_str_dtype(variables[name]): - result[name] = CFUGridAuxiliaryCoordinateVariable( - name, variables[name] - ) - else: - message = ( - f"Ignoring variable {name}, " - f"identified as a CF-netCDF " - f"auxiliary coordinate - is a " - f"CF-netCDF label variable." - ) - if warn: - warnings.warn( - message, - category=IrisCfLabelVarWarning, - ) - - return result - - -class CFUGridMeshVariable(cf.CFVariable): - """A CF-UGRID mesh variable is a dummy variable for storing topology information as attributes. - - A CF-UGRID mesh variable is a dummy variable for storing topology - information as attributes. The mesh variable has the ``cf_role`` - 'mesh_topology'. - - The UGRID conventions describe define the mesh topology as the - interconnection of various geometrical elements of the mesh. The pure - interconnectivity is independent of georeferencing the individual - geometrical elements, but for the practical applications for which the - UGRID CF extension is defined, coordinate data will always be added. - - Identified by the CF-netCDF variable attribute 'mesh'. - - .. seealso:: - - The UGRID Conventions, https://ugrid-conventions.github.io/ugrid-conventions/ - - """ - - cf_identity = "mesh" - - @classmethod - def identify(cls, variables, ignore=None, target=None, warn=True): - result = {} - ignore, target = cls._identify_common(variables, ignore, target) - - # Identify all CF-UGRID mesh variables. - all_vars = target == variables - for nc_var_name, nc_var in target.items(): - if all_vars: - # SPECIAL BEHAVIOUR FOR MESH VARIABLES. - # We are looking for all mesh variables. Check if THIS variable - # is a mesh using its own attributes. - if getattr(nc_var, "cf_role", "") == "mesh_topology": - result[nc_var_name] = CFUGridMeshVariable(nc_var_name, nc_var) - - # Check for mesh variable references. - nc_var_att = getattr(nc_var, cls.cf_identity, None) - - if nc_var_att is not None: - # UGRID only allows for 1 mesh per variable. - name = nc_var_att.strip() - if name not in ignore: - if name not in variables: - message = ( - f"Missing CF-UGRID mesh variable {name}, " - f"referenced by netCDF variable {nc_var_name}" - ) - if warn: - warnings.warn(message, category=IrisCfMissingVarWarning) - else: - # Restrict to non-string type i.e. not a - # CFLabelVariable. - if not cf._is_str_dtype(variables[name]): - result[name] = CFUGridMeshVariable(name, variables[name]) - else: - message = ( - f"Ignoring variable {name}, identified as a " - f"CF-UGRID mesh - is a CF-netCDF label " - f"variable." - ) - if warn: - warnings.warn(message, category=IrisCfLabelVarWarning) - - return result - - -class CFUGridGroup(cf.CFGroup): - """Represents a collection of CF Metadata Conventions variables and netCDF global attributes. - - Represents a collection of 'NetCDF Climate and Forecast (CF) Metadata - Conventions' variables and netCDF global attributes. - - Specialisation of :class:`~iris.fileformats.cf.CFGroup` that includes extra - collections for CF-UGRID-specific variable types. - - """ - - @property - def connectivities(self): - """Collection of CF-UGRID connectivity variables.""" - return self._cf_getter(CFUGridConnectivityVariable) - - @property - def ugrid_coords(self): - """Collection of CF-UGRID-relevant auxiliary coordinate variables.""" - return self._cf_getter(CFUGridAuxiliaryCoordinateVariable) - - @property - def meshes(self): - """Collection of CF-UGRID mesh variables.""" - return self._cf_getter(CFUGridMeshVariable) - - @property - def non_data_variable_names(self): - """:class:`set` of names of the CF-netCDF/CF-UGRID variables that are not the data pay-load.""" - extra_variables = (self.connectivities, self.ugrid_coords, self.meshes) - extra_result = set() - for variable in extra_variables: - extra_result |= set(variable) - return super().non_data_variable_names | extra_result - - -class CFUGridReader(cf.CFReader): - """Allows the contents of a netCDF file to be. - - This class allows the contents of a netCDF file to be interpreted according - to the 'NetCDF Climate and Forecast (CF) Metadata Conventions'. - - Specialisation of :class:`~iris.fileformats.cf.CFReader` that can also - handle CF-UGRID-specific variable types. - - """ - - _variable_types = cf.CFReader._variable_types + ( # type: ignore[assignment] - CFUGridConnectivityVariable, - CFUGridAuxiliaryCoordinateVariable, - CFUGridMeshVariable, - ) - - CFGroup = CFUGridGroup diff --git a/lib/iris/ugrid/metadata.py b/lib/iris/ugrid/metadata.py deleted file mode 100644 index 0165852d5f..0000000000 --- a/lib/iris/ugrid/metadata.py +++ /dev/null @@ -1,398 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the BSD license. -# See LICENSE in the root of the repository for full licensing details. - -"""The common metadata API classes for :mod:`iris.ugrid.mesh`. - -Eventual destination: :mod:`iris.common.metadata`. - -""" - -from functools import wraps - -from ..common import BaseMetadata -from ..common.lenient import _lenient_service as lenient_service -from ..common.metadata import ( - SERVICES, - SERVICES_COMBINE, - SERVICES_DIFFERENCE, - SERVICES_EQUAL, -) - - -class ConnectivityMetadata(BaseMetadata): - """Metadata container for a :class:`~iris.ugrid.mesh.Connectivity`.""" - - # The "location_axis" member is stateful only, and does not participate in - # lenient/strict equivalence. - _members = ("cf_role", "start_index", "location_axis") - - __slots__ = () - - @wraps(BaseMetadata.__eq__, assigned=("__doc__",), updated=()) - @lenient_service - def __eq__(self, other): - return super().__eq__(other) - - def _combine_lenient(self, other): - """Perform lenient combination of metadata members for connectivities. - - Parameters - ---------- - other : ConnectivityMetadata - The other connectivity metadata participating in the lenient - combination. - - Returns - ------- - A list of combined metadata member values. - - """ - - # Perform "strict" combination for "cf_role", "start_index", "location_axis". - def func(field): - left = getattr(self, field) - right = getattr(other, field) - return left if left == right else None - - # Note that, we use "_members" not "_fields". - values = [func(field) for field in ConnectivityMetadata._members] - # Perform lenient combination of the other parent members. - result = super()._combine_lenient(other) - result.extend(values) - - return result - - def _compare_lenient(self, other): - """Perform lenient equality of metadata members for connectivities. - - Parameters - ---------- - other : ConnectivityMetadata - The other connectivity metadata participating in the lenient - comparison. - - Returns - ------- - bool - - """ - # Perform "strict" comparison for "cf_role", "start_index". - # The "location_axis" member is not part of lenient equivalence. - members = filter( - lambda member: member != "location_axis", - ConnectivityMetadata._members, - ) - result = all( - [getattr(self, field) == getattr(other, field) for field in members] - ) - if result: - # Perform lenient comparison of the other parent members. - result = super()._compare_lenient(other) - - return result - - def _difference_lenient(self, other): - """Perform lenient difference of metadata members for connectivities. - - Parameters - ---------- - other : ConnectivityMetadata - The other connectivity metadata participating in the lenient - difference. - - Returns - ------- - A list of difference metadata member values. - - """ - - # Perform "strict" difference for "cf_role", "start_index", "location_axis". - def func(field): - left = getattr(self, field) - right = getattr(other, field) - return None if left == right else (left, right) - - # Note that, we use "_members" not "_fields". - values = [func(field) for field in ConnectivityMetadata._members] - # Perform lenient difference of the other parent members. - result = super()._difference_lenient(other) - result.extend(values) - - return result - - @wraps(BaseMetadata.combine, assigned=("__doc__",), updated=()) - @lenient_service - def combine(self, other, lenient=None): - return super().combine(other, lenient=lenient) - - @wraps(BaseMetadata.difference, assigned=("__doc__",), updated=()) - @lenient_service - def difference(self, other, lenient=None): - return super().difference(other, lenient=lenient) - - @wraps(BaseMetadata.equal, assigned=("__doc__",), updated=()) - @lenient_service - def equal(self, other, lenient=None): - return super().equal(other, lenient=lenient) - - -class MeshMetadata(BaseMetadata): - """Metadata container for a :class:`~iris.ugrid.mesh.MeshXY`.""" - - # The node_dimension", "edge_dimension" and "face_dimension" members are - # stateful only; they not participate in lenient/strict equivalence. - _members = ( - "topology_dimension", - "node_dimension", - "edge_dimension", - "face_dimension", - ) - - __slots__ = () - - @wraps(BaseMetadata.__eq__, assigned=("__doc__",), updated=()) - @lenient_service - def __eq__(self, other): - return super().__eq__(other) - - def _combine_lenient(self, other): - """Perform lenient combination of metadata members for meshes. - - Parameters - ---------- - other : MeshMetadata - The other mesh metadata participating in the lenient - combination. - - Returns - ------- - A list of combined metadata member values. - - """ - - # Perform "strict" combination for "topology_dimension", - # "node_dimension", "edge_dimension" and "face_dimension". - def func(field): - left = getattr(self, field) - right = getattr(other, field) - return left if left == right else None - - # Note that, we use "_members" not "_fields". - values = [func(field) for field in MeshMetadata._members] - # Perform lenient combination of the other parent members. - result = super()._combine_lenient(other) - result.extend(values) - - return result - - def _compare_lenient(self, other): - """Perform lenient equality of metadata members for meshes. - - Parameters - ---------- - other : MeshMetadata - The other mesh metadata participating in the lenient - comparison. - - Returns - ------- - bool - - """ - # Perform "strict" comparison for "topology_dimension". - # "node_dimension", "edge_dimension" and "face_dimension" are not part - # of lenient equivalence at all. - result = self.topology_dimension == other.topology_dimension - if result: - # Perform lenient comparison of the other parent members. - result = super()._compare_lenient(other) - - return result - - def _difference_lenient(self, other): - """Perform lenient difference of metadata members for meshes. - - Parameters - ---------- - other : MeshMetadata - The other mesh metadata participating in the lenient - difference. - - Returns - ------- - A list of difference metadata member values. - - """ - - # Perform "strict" difference for "topology_dimension", - # "node_dimension", "edge_dimension" and "face_dimension". - def func(field): - left = getattr(self, field) - right = getattr(other, field) - return None if left == right else (left, right) - - # Note that, we use "_members" not "_fields". - values = [func(field) for field in MeshMetadata._members] - # Perform lenient difference of the other parent members. - result = super()._difference_lenient(other) - result.extend(values) - - return result - - @wraps(BaseMetadata.combine, assigned=("__doc__",), updated=()) - @lenient_service - def combine(self, other, lenient=None): - return super().combine(other, lenient=lenient) - - @wraps(BaseMetadata.difference, assigned=("__doc__",), updated=()) - @lenient_service - def difference(self, other, lenient=None): - return super().difference(other, lenient=lenient) - - @wraps(BaseMetadata.equal, assigned=("__doc__",), updated=()) - @lenient_service - def equal(self, other, lenient=None): - return super().equal(other, lenient=lenient) - - -class MeshCoordMetadata(BaseMetadata): - """Metadata container for a :class:`~iris.coords.MeshCoord`.""" - - _members = ("location", "axis") - # NOTE: in future, we may add 'mesh' as part of this metadata, - # as the MeshXY seems part of the 'identity' of a MeshCoord. - # For now we omit it, particularly as we don't yet implement MeshXY.__eq__. - # - # Thus, for now, the MeshCoord class will need to handle 'mesh' explicitly - # in identity / comparison, but in future that may be simplified. - - __slots__ = () - - @wraps(BaseMetadata.__eq__, assigned=("__doc__",), updated=()) - @lenient_service - def __eq__(self, other): - return super().__eq__(other) - - def _combine_lenient(self, other): - """Perform lenient combination of metadata members for MeshCoord. - - Parameters - ---------- - other : MeshCoordMetadata - The other metadata participating in the lenient combination. - - Returns - ------- - A list of combined metadata member values. - - """ - - # It is actually "strict" : return None except where members are equal. - def func(field): - left = getattr(self, field) - right = getattr(other, field) - return left if left == right else None - - # Note that, we use "_members" not "_fields". - values = [func(field) for field in self._members] - # Perform lenient combination of the other parent members. - result = super()._combine_lenient(other) - result.extend(values) - - return result - - def _compare_lenient(self, other): - """Perform lenient equality of metadata members for MeshCoord. - - Parameters - ---------- - other : MeshCoordMetadata - The other metadata participating in the lenient comparison. - - Returns - ------- - bool - - """ - # Perform "strict" comparison for the MeshCoord specific members - # 'location', 'axis' : for equality, they must all match. - result = all( - [getattr(self, field) == getattr(other, field) for field in self._members] - ) - if result: - # Perform lenient comparison of the other parent members. - result = super()._compare_lenient(other) - - return result - - def _difference_lenient(self, other): - """Perform lenient difference of metadata members for MeshCoord. - - Parameters - ---------- - other : MeshCoordMetadata - The other MeshCoord metadata participating in the lenient - difference. - - Returns - ------- - A list of different metadata member values. - - """ - - # Perform "strict" difference for location / axis. - def func(field): - left = getattr(self, field) - right = getattr(other, field) - return None if left == right else (left, right) - - # Note that, we use "_members" not "_fields". - values = [func(field) for field in self._members] - # Perform lenient difference of the other parent members. - result = super()._difference_lenient(other) - result.extend(values) - - return result - - @wraps(BaseMetadata.combine, assigned=("__doc__",), updated=()) - @lenient_service - def combine(self, other, lenient=None): - return super().combine(other, lenient=lenient) - - @wraps(BaseMetadata.difference, assigned=("__doc__",), updated=()) - @lenient_service - def difference(self, other, lenient=None): - return super().difference(other, lenient=lenient) - - @wraps(BaseMetadata.equal, assigned=("__doc__",), updated=()) - @lenient_service - def equal(self, other, lenient=None): - return super().equal(other, lenient=lenient) - - -# Add our new optional metadata operations into the 'convenience collections' -# of lenient metadata services. -# TODO: when included in 'iris.common.metadata', install each one directly ? -_op_names_and_service_collections = [ - ("combine", SERVICES_COMBINE), - ("difference", SERVICES_DIFFERENCE), - ("__eq__", SERVICES_EQUAL), - ("equal", SERVICES_EQUAL), -] -_metadata_classes = [ConnectivityMetadata, MeshMetadata, MeshCoordMetadata] -for _cls in _metadata_classes: - for _name, _service_collection in _op_names_and_service_collections: - _method = getattr(_cls, _name) - _service_collection.append(_method) - SERVICES.append(_method) - -del ( - _op_names_and_service_collections, - _metadata_classes, - _cls, - _name, - _service_collection, - _method, -) diff --git a/lib/iris/ugrid/save.py b/lib/iris/ugrid/save.py deleted file mode 100644 index 79933cbd08..0000000000 --- a/lib/iris/ugrid/save.py +++ /dev/null @@ -1,53 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the BSD license. -# See LICENSE in the root of the repository for full licensing details. - -"""Extension to Iris' NetCDF saving to allow :class:`~iris.ugrid.mesh.MeshXY` saving in UGRID format. - -Eventual destination: :mod:`iris.fileformats.netcdf`. - -""" - -from collections.abc import Iterable - -from ..fileformats import netcdf - - -def save_mesh(mesh, filename, netcdf_format="NETCDF4"): - """Save mesh(es) to a netCDF file. - - Parameters - ---------- - mesh : :class:`iris.ugrid.MeshXY` or iterable - Mesh(es) to save. - filename : str - Name of the netCDF file to create. - netcdf_format : str, default="NETCDF4" - Underlying netCDF file format, one of 'NETCDF4', 'NETCDF4_CLASSIC', - 'NETCDF3_CLASSIC' or 'NETCDF3_64BIT'. Default is 'NETCDF4' format. - - """ - # TODO: integrate with standard saving API when no longer 'experimental'. - - if isinstance(mesh, Iterable): - meshes = mesh - else: - meshes = [mesh] - - # Initialise Manager for saving - with netcdf.Saver(filename, netcdf_format) as sman: - # Iterate through the list. - for mesh in meshes: - # Get suitable dimension names. - mesh_dimensions, _ = sman._get_dim_names(mesh) - - # Create dimensions. - sman._create_cf_dimensions(cube=None, dimension_names=mesh_dimensions) - - # Create the mesh components. - sman._add_mesh(mesh) - - # Add a conventions attribute. - # TODO: add 'UGRID' to conventions, when this is agreed with CF ? - sman.update_global_attributes(Conventions=netcdf.CF_CONVENTIONS_VERSION) From 83905e930da4ac08af9f7a38ae27506187b1a03f Mon Sep 17 00:00:00 2001 From: Henry Wright <84939917+HGWright@users.noreply.github.com> Date: Fri, 26 Jul 2024 17:05:36 +0100 Subject: [PATCH 06/26] Adding monthly and yearly arguments to `guess_bounds` (#6090) * add monthly and some tests * more monthly tests * fix broken test * updated docstrings and removed comment * adding yearly and tests * add test for both * whatsnew and docstring note --- docs/src/whatsnew/latest.rst | 3 + lib/iris/coords.py | 110 +++++++++++++--- lib/iris/tests/unit/coords/test_Coord.py | 159 +++++++++++++++++++++++ 3 files changed, 251 insertions(+), 21 deletions(-) diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst index e04b832e23..bbef181057 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/latest.rst @@ -45,6 +45,9 @@ This document explains the changes made to Iris for this release grid-mappping syntax -- see : :issue:`3388`. (:issue:`5562`, :pull:`6016`) +#. `@HGWright`_ added the `monthly` and `yearly` options to the + :meth:`~iris.coords.guess_bounds` method. (:issue:`4864`, :pull:`6090`) + 🐛 Bugs Fixed ============= diff --git a/lib/iris/coords.py b/lib/iris/coords.py index a56c13d9af..d2f5b05f89 100644 --- a/lib/iris/coords.py +++ b/lib/iris/coords.py @@ -2195,7 +2195,7 @@ def serialize(x): coord = self.copy(points=points, bounds=bounds) return coord - def _guess_bounds(self, bound_position=0.5): + def _guess_bounds(self, bound_position=0.5, monthly=False, yearly=False): """Return bounds for this coordinate based on its points. Parameters @@ -2203,6 +2203,12 @@ def _guess_bounds(self, bound_position=0.5): bound_position : float, default=0.5 The desired position of the bounds relative to the position of the points. + monthly : bool, default=False + If True, the coordinate must be monthly and bounds are set to the + start and ends of each month. + yearly : bool, default=False + If True, the coordinate must be yearly and bounds are set to the + start and ends of each year. Returns ------- @@ -2225,7 +2231,7 @@ def _guess_bounds(self, bound_position=0.5): if self.ndim != 1: raise iris.exceptions.CoordinateMultiDimError(self) - if self.shape[0] < 2: + if not monthly and self.shape[0] < 2: raise ValueError("Cannot guess bounds for a coordinate of length 1.") if self.has_bounds(): @@ -2234,31 +2240,80 @@ def _guess_bounds(self, bound_position=0.5): "before guessing new ones." ) - if getattr(self, "circular", False): - points = np.empty(self.shape[0] + 2) - points[1:-1] = self.points - direction = 1 if self.points[-1] > self.points[0] else -1 - points[0] = self.points[-1] - (self.units.modulus * direction) - points[-1] = self.points[0] + (self.units.modulus * direction) - diffs = np.diff(points) + if monthly or yearly: + if monthly and yearly: + raise ValueError( + "Cannot guess monthly and yearly bounds simultaneously." + ) + dates = self.units.num2date(self.points) + lower_bounds = [] + upper_bounds = [] + months_and_years = [] + if monthly: + for date in dates: + if date.month == 12: + lyear = date.year + uyear = date.year + 1 + lmonth = 12 + umonth = 1 + else: + lyear = uyear = date.year + lmonth = date.month + umonth = date.month + 1 + date_pair = (date.year, date.month) + if date_pair not in months_and_years: + months_and_years.append(date_pair) + else: + raise ValueError( + "Cannot guess monthly bounds for a coordinate with multiple " + "points in a month." + ) + lower_bounds.append(date.__class__(lyear, lmonth, 1, 0, 0)) + upper_bounds.append(date.__class__(uyear, umonth, 1, 0, 0)) + elif yearly: + for date in dates: + year = date.year + if year not in months_and_years: + months_and_years.append(year) + else: + raise ValueError( + "Cannot guess yearly bounds for a coordinate with multiple " + "points in a year." + ) + lower_bounds.append(date.__class__(date.year, 1, 1, 0, 0)) + upper_bounds.append(date.__class__(date.year + 1, 1, 1, 0, 0)) + bounds = self.units.date2num(np.array([lower_bounds, upper_bounds]).T) + contiguous = np.ma.allclose(bounds[1:, 0], bounds[:-1, 1]) + if not contiguous: + raise ValueError("Cannot guess bounds for a non-contiguous coordinate.") + + # if not monthly or yearly else: - diffs = np.diff(self.points) - diffs = np.insert(diffs, 0, diffs[0]) - diffs = np.append(diffs, diffs[-1]) + if getattr(self, "circular", False): + points = np.empty(self.shape[0] + 2) + points[1:-1] = self.points + direction = 1 if self.points[-1] > self.points[0] else -1 + points[0] = self.points[-1] - (self.units.modulus * direction) + points[-1] = self.points[0] + (self.units.modulus * direction) + diffs = np.diff(points) + else: + diffs = np.diff(self.points) + diffs = np.insert(diffs, 0, diffs[0]) + diffs = np.append(diffs, diffs[-1]) - min_bounds = self.points - diffs[:-1] * bound_position - max_bounds = self.points + diffs[1:] * (1 - bound_position) + min_bounds = self.points - diffs[:-1] * bound_position + max_bounds = self.points + diffs[1:] * (1 - bound_position) - bounds = np.array([min_bounds, max_bounds]).transpose() + bounds = np.array([min_bounds, max_bounds]).transpose() - if self.name() in ("latitude", "grid_latitude") and self.units == "degree": - points = self.points - if (points >= -90).all() and (points <= 90).all(): - np.clip(bounds, -90, 90, out=bounds) + if self.name() in ("latitude", "grid_latitude") and self.units == "degree": + points = self.points + if (points >= -90).all() and (points <= 90).all(): + np.clip(bounds, -90, 90, out=bounds) return bounds - def guess_bounds(self, bound_position=0.5): + def guess_bounds(self, bound_position=0.5, monthly=False, yearly=False): """Add contiguous bounds to a coordinate, calculated from its points. Puts a cell boundary at the specified fraction between each point and @@ -2275,6 +2330,13 @@ def guess_bounds(self, bound_position=0.5): bound_position : float, default=0.5 The desired position of the bounds relative to the position of the points. + monthly : bool, default=False + If True, the coordinate must be monthly and bounds are set to the + start and ends of each month. + yearly : bool, default=False + If True, the coordinate must be yearly and bounds are set to the + start and ends of each year. + Notes ----- @@ -2289,8 +2351,14 @@ def guess_bounds(self, bound_position=0.5): produce unexpected results : In such cases you should assign suitable values directly to the bounds property, instead. + .. note:: + + Monthly and Yearly work differently from the standard case. They + can work for single points but cannot be used together. + + """ - self.bounds = self._guess_bounds(bound_position) + self.bounds = self._guess_bounds(bound_position, monthly, yearly) def intersect(self, other, return_indices=False): """Return a new coordinate from the intersection of two coordinates. diff --git a/lib/iris/tests/unit/coords/test_Coord.py b/lib/iris/tests/unit/coords/test_Coord.py index 3740b17f22..c63261f95c 100644 --- a/lib/iris/tests/unit/coords/test_Coord.py +++ b/lib/iris/tests/unit/coords/test_Coord.py @@ -9,9 +9,11 @@ import iris.tests as tests # isort:skip import collections +from datetime import datetime from unittest import mock import warnings +import cf_units import dask.array as da import numpy as np import pytest @@ -236,6 +238,163 @@ def test_points_inside_bounds_outside_wrong_name_2(self): self.assertArrayEqual(lat.bounds, [[-120, -40], [-40, 35], [35, 105]]) +def test_guess_bounds_monthly_and_yearly(): + units = cf_units.Unit("days since epoch", calendar="gregorian") + points = units.date2num( + [ + datetime(1990, 1, 1), + datetime(1990, 2, 1), + datetime(1990, 3, 1), + ] + ) + coord = iris.coords.AuxCoord(points=points, units=units, standard_name="time") + with pytest.raises( + ValueError, + match="Cannot guess monthly and yearly bounds simultaneously.", + ): + coord.guess_bounds(monthly=True, yearly=True) + + +class Test_Guess_Bounds_Monthly: + def test_monthly_multiple_points_in_month(self): + units = cf_units.Unit("days since epoch", calendar="gregorian") + points = units.date2num( + [ + datetime(1990, 1, 3), + datetime(1990, 1, 28), + datetime(1990, 2, 13), + ] + ) + coord = iris.coords.AuxCoord(points=points, units=units, standard_name="time") + with pytest.raises( + ValueError, + match="Cannot guess monthly bounds for a coordinate with multiple points " + "in a month.", + ): + coord.guess_bounds(monthly=True) + + def test_monthly_non_contiguous(self): + units = cf_units.Unit("days since epoch", calendar="gregorian") + expected = units.date2num( + [ + [datetime(1990, 1, 1), datetime(1990, 2, 1)], + [datetime(1990, 2, 1), datetime(1990, 3, 1)], + [datetime(1990, 5, 1), datetime(1990, 6, 1)], + ] + ) + points = expected.mean(axis=1) + coord = iris.coords.AuxCoord(points=points, units=units, standard_name="time") + with pytest.raises( + ValueError, match="Cannot guess bounds for a non-contiguous coordinate." + ): + coord.guess_bounds(monthly=True) + + def test_monthly_end_of_month(self): + units = cf_units.Unit("days since epoch", calendar="gregorian") + expected = units.date2num( + [ + [datetime(1990, 1, 1), datetime(1990, 2, 1)], + [datetime(1990, 2, 1), datetime(1990, 3, 1)], + [datetime(1990, 3, 1), datetime(1990, 4, 1)], + ] + ) + points = units.date2num( + [ + datetime(1990, 1, 31), + datetime(1990, 2, 28), + datetime(1990, 3, 31), + ] + ) + coord = iris.coords.AuxCoord(points=points, units=units, standard_name="time") + coord.guess_bounds(monthly=True) + dates = units.num2date(coord.bounds) + expected_dates = units.num2date(expected) + np.testing.assert_array_equal(dates, expected_dates) + + def test_monthly_multiple_years(self): + units = cf_units.Unit("days since epoch", calendar="gregorian") + expected = [ + [datetime(1990, 10, 1), datetime(1990, 11, 1)], + [datetime(1990, 11, 1), datetime(1990, 12, 1)], + [datetime(1990, 12, 1), datetime(1991, 1, 1)], + ] + expected_points = units.date2num(expected) + points = expected_points.mean(axis=1) + coord = iris.coords.AuxCoord(points=points, units=units, standard_name="time") + coord.guess_bounds(monthly=True) + dates = units.num2date(coord.bounds) + np.testing.assert_array_equal(dates, expected) + + def test_monthly_single_point(self): + units = cf_units.Unit("days since epoch", calendar="gregorian") + expected = [ + [datetime(1990, 1, 1), datetime(1990, 2, 1)], + ] + expected_points = units.date2num(expected) + points = expected_points.mean(axis=1) + coord = iris.coords.AuxCoord(points=points, units=units, standard_name="time") + coord.guess_bounds(monthly=True) + dates = units.num2date(coord.bounds) + np.testing.assert_array_equal(dates, expected) + + +class Test_Guess_Bounds_Yearly: + def test_yearly_multiple_points_in_year(self): + units = cf_units.Unit("days since epoch", calendar="gregorian") + points = units.date2num( + [ + datetime(1990, 1, 1), + datetime(1990, 2, 1), + datetime(1991, 1, 1), + ] + ) + coord = iris.coords.AuxCoord(points=points, units=units, standard_name="time") + with pytest.raises( + ValueError, + match="Cannot guess yearly bounds for a coordinate with multiple points " + "in a year.", + ): + coord.guess_bounds(yearly=True) + + def test_yearly_non_contiguous(self): + units = cf_units.Unit("days since epoch", calendar="gregorian") + expected = units.date2num( + [ + [datetime(1990, 1, 1), datetime(1990, 1, 1)], + [datetime(1991, 1, 1), datetime(1991, 1, 1)], + [datetime(1994, 1, 1), datetime(1994, 1, 1)], + ] + ) + points = expected.mean(axis=1) + coord = iris.coords.AuxCoord(points=points, units=units, standard_name="time") + with pytest.raises( + ValueError, match="Cannot guess bounds for a non-contiguous coordinate." + ): + coord.guess_bounds(yearly=True) + + def test_yearly_end_of_year(self): + units = cf_units.Unit("days since epoch", calendar="gregorian") + expected = units.date2num( + [ + [datetime(1990, 1, 1), datetime(1991, 1, 1)], + [datetime(1991, 1, 1), datetime(1992, 1, 1)], + [datetime(1992, 1, 1), datetime(1993, 1, 1)], + ] + ) + points = units.date2num( + [ + datetime(1990, 12, 31), + datetime(1991, 12, 31), + datetime(1992, 12, 31), + ] + ) + coord = iris.coords.AuxCoord(points=points, units=units, standard_name="time") + coord.guess_bounds(yearly=True) + dates = units.num2date(coord.bounds) + expected_dates = units.num2date(expected) + np.testing.assert_array_equal(dates, expected_dates) + + class Test_cell(tests.IrisTest): def _mock_coord(self): coord = mock.Mock( From 48f1f4ef0882e0b1a353430ee8aeb264a69d8759 Mon Sep 17 00:00:00 2001 From: stephenworsley <49274989+stephenworsley@users.noreply.github.com> Date: Mon, 29 Jul 2024 15:20:57 +0100 Subject: [PATCH 07/26] Load performance improvement (ignoring UGRID) (#6088) * proof of concept load performance improvement * remove print line * adjust for merge * adjust to tolerant load behaviour, make methods private * fix tests * simplify changes * Update lib/iris/fileformats/cf.py Co-authored-by: Bill Little * address review comment * add whatsnew --------- Co-authored-by: Bill Little --- docs/src/whatsnew/latest.rst | 4 ++++ lib/iris/fileformats/cf.py | 24 +++++++++++++++++++++++ lib/iris/fileformats/netcdf/ugrid_load.py | 12 +++++++----- 3 files changed, 35 insertions(+), 5 deletions(-) diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst index bbef181057..8b7fbf5864 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/latest.rst @@ -98,6 +98,10 @@ This document explains the changes made to Iris for this release subclasses of a common generic :class:`~iris.mesh.components.Mesh` class. (:issue:`6057`, :pull:`6061`, :pull:`6077`) +#. `@pp-mo`_ and `@stephenworsley`_ Turned on UGRID loading by default, effectively removing + the need for and deprecating the :func:`~iris.ugrid.experimental.PARSE_UGRID_ON_LOAD` + context manager. (:pull:`6054`, :pull:`6088`) + 🚀 Performance Enhancements =========================== diff --git a/lib/iris/fileformats/cf.py b/lib/iris/fileformats/cf.py index 556642003a..024bcb6f1d 100644 --- a/lib/iris/fileformats/cf.py +++ b/lib/iris/fileformats/cf.py @@ -1331,6 +1331,11 @@ def __init__(self, file_source, warn=False, monotonic=False): self._check_monotonic = monotonic + self._with_ugrid = True + if not self._has_meshes(): + self._trim_ugrid_variable_types() + self._with_ugrid = False + self._translate() self._build_cf_groups() self._reset() @@ -1348,6 +1353,25 @@ def __exit__(self, exc_type, exc_value, traceback): # When used as a context-manager, **always** close the file on exit. self._close() + def _has_meshes(self): + result = False + for variable in self._dataset.variables.values(): + if hasattr(variable, "mesh") or hasattr(variable, "node_coordinates"): + result = True + break + return result + + def _trim_ugrid_variable_types(self): + self._variable_types = ( + CFAncillaryDataVariable, + CFAuxiliaryCoordinateVariable, + CFBoundaryVariable, + CFClimatologyVariable, + CFGridMappingVariable, + CFLabelVariable, + CFMeasureVariable, + ) + @property def filename(self): """The file that the CFReader is reading.""" diff --git a/lib/iris/fileformats/netcdf/ugrid_load.py b/lib/iris/fileformats/netcdf/ugrid_load.py index 210e112629..0a70567f16 100644 --- a/lib/iris/fileformats/netcdf/ugrid_load.py +++ b/lib/iris/fileformats/netcdf/ugrid_load.py @@ -56,11 +56,13 @@ def _meshes_from_cf(cf_reader): # Mesh instances are shared between file phenomena. # TODO: more sophisticated Mesh sharing between files. # TODO: access external Mesh cache? - mesh_vars = cf_reader.cf_group.meshes - meshes = { - name: _build_mesh(cf_reader, var, cf_reader.filename) - for name, var in mesh_vars.items() - } + meshes = {} + if cf_reader._with_ugrid: + mesh_vars = cf_reader.cf_group.meshes + meshes = { + name: _build_mesh(cf_reader, var, cf_reader.filename) + for name, var in mesh_vars.items() + } return meshes From db77e6eca87219133bf50e507040f2d75dfad37f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 30 Jul 2024 10:47:51 +0100 Subject: [PATCH 08/26] Bump scitools/workflows from 2024.07.5 to 2024.07.6 (#6099) Bumps [scitools/workflows](https://github.com/scitools/workflows) from 2024.07.5 to 2024.07.6. - [Release notes](https://github.com/scitools/workflows/releases) - [Commits](https://github.com/scitools/workflows/compare/2024.07.5...2024.07.6) --- updated-dependencies: - dependency-name: scitools/workflows dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci-manifest.yml | 2 +- .github/workflows/refresh-lockfiles.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci-manifest.yml b/.github/workflows/ci-manifest.yml index 12691f8536..b0c85425af 100644 --- a/.github/workflows/ci-manifest.yml +++ b/.github/workflows/ci-manifest.yml @@ -23,4 +23,4 @@ concurrency: jobs: manifest: name: "check-manifest" - uses: scitools/workflows/.github/workflows/ci-manifest.yml@2024.07.5 + uses: scitools/workflows/.github/workflows/ci-manifest.yml@2024.07.6 diff --git a/.github/workflows/refresh-lockfiles.yml b/.github/workflows/refresh-lockfiles.yml index b0cbe14054..2aa2e82d44 100644 --- a/.github/workflows/refresh-lockfiles.yml +++ b/.github/workflows/refresh-lockfiles.yml @@ -14,5 +14,5 @@ on: jobs: refresh_lockfiles: - uses: scitools/workflows/.github/workflows/refresh-lockfiles.yml@2024.07.5 + uses: scitools/workflows/.github/workflows/refresh-lockfiles.yml@2024.07.6 secrets: inherit From 133570cb8f8c16cce08d361176e782a2d4aec651 Mon Sep 17 00:00:00 2001 From: Henry Wright <84939917+HGWright@users.noreply.github.com> Date: Tue, 30 Jul 2024 10:58:50 +0100 Subject: [PATCH 09/26] Update cf_table v85 (#6100) * update cf_table * add whatsnew * Update docs/src/whatsnew/latest.rst Co-authored-by: Martin Yeo <40734014+trexfeathers@users.noreply.github.com> --------- Co-authored-by: Martin Yeo <40734014+trexfeathers@users.noreply.github.com> --- docs/src/whatsnew/latest.rst | 3 + etc/cf-standard-name-table.xml | 1434 ++++++++++++++++++++++++++------ 2 files changed, 1169 insertions(+), 268 deletions(-) diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst index 8b7fbf5864..4343b02f6e 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/latest.rst @@ -48,6 +48,9 @@ This document explains the changes made to Iris for this release #. `@HGWright`_ added the `monthly` and `yearly` options to the :meth:`~iris.coords.guess_bounds` method. (:issue:`4864`, :pull:`6090`) +#. `@HGWright`_ updated to the latest CF Standard Names Table v85 + (30 July 2024). (:pull:`6100`) + 🐛 Bugs Fixed ============= diff --git a/etc/cf-standard-name-table.xml b/etc/cf-standard-name-table.xml index ef05fde69a..c5405e2dca 100644 --- a/etc/cf-standard-name-table.xml +++ b/etc/cf-standard-name-table.xml @@ -1,10 +1,11 @@ - - 84 - 2024-01-19T15:55:10Z + + 85 + CF-StandardNameTable-85 + 2024-05-21T15:55:10Z + 2024-05-21T15:55:10Z Centre for Environmental Data Analysis support@ceda.ac.uk - 1 @@ -94,21 +95,21 @@ K - The "equivalent potential temperature" is a thermodynamic quantity, with its natural logarithm proportional to the entropy of moist air, that is conserved in a reversible moist adiabatic process. Reference: AMS Glossary http://glossary.ametsoc.org/wiki/Equivalent_potential_temperature. It is the temperature of a parcel of air if all the moisture contained in it were first condensed, releasing latent heat, before moving the parcel dry adiabatically to a standard pressure, typically representative of mean sea level pressure. To specify the standard pressure to which the quantity applies, provide a scalar coordinate variable with standard name reference_pressure. + The "equivalent potential temperature" is a thermodynamic quantity, with its natural logarithm proportional to the entropy of moist air, that is conserved in a reversible moist adiabatic process. Reference: AMS Glossary http://glossary.ametsoc.org/wiki/Equivalent_potential_temperature. It is the temperature of a parcel of air if all the moisture contained in it were first condensed, releasing latent heat, before moving the parcel dry adiabatically to a standard pressure, typically representative of mean sea level pressure. To specify the standard pressure to which the quantity applies, provide a scalar coordinate variable with standard name reference_pressure. It is strongly recommended that a variable with this standard name should have a units_metadata attribute, with one of the values "on-scale" or "difference", whichever is appropriate for the data, because it is essential to know whether the temperature is on-scale (meaning relative to the origin of the scale indicated by the units) or refers to temperature differences (implying that the origin of the temperature scale is irrevelant), in order to convert the units correctly (cf. https://cfconventions.org/cf-conventions/cf-conventions.html#temperature-units). K - The equivalent temperature is the temperature that an air parcel would have if all water vapor were condensed at contstant pressure and the enthalpy released from the vapor used to heat the air. Reference: AMS Glossary http://glossary.ametsoc.org/wiki/Equivalent_temperature. It is the isobaric equivalent temperature and not the adiabatic equivalent temperature, also known as pseudoequivalent temperature, which has the standard name air_pseudo_equivalent_temperature. + The equivalent temperature is the temperature that an air parcel would have if all water vapor were condensed at contstant pressure and the enthalpy released from the vapor used to heat the air. Reference: AMS Glossary http://glossary.ametsoc.org/wiki/Equivalent_temperature. It is the isobaric equivalent temperature and not the adiabatic equivalent temperature, also known as pseudoequivalent temperature, which has the standard name air_pseudo_equivalent_temperature. It is strongly recommended that a variable with this standard name should have a units_metadata attribute, with one of the values "on-scale" or "difference", whichever is appropriate for the data, because it is essential to know whether the temperature is on-scale (meaning relative to the origin of the scale indicated by the units) or refers to temperature differences (implying that the origin of the temperature scale is irrevelant), in order to convert the units correctly (cf. https://cfconventions.org/cf-conventions/cf-conventions.html#temperature-units). K 13 theta - Air potential temperature is the temperature a parcel of air would have if moved dry adiabatically to a standard pressure, typically representative of mean sea level pressure. To specify the standard pressure to which the quantity applies, provide a scalar coordinate variable with standard name reference_pressure. + Air potential temperature is the temperature a parcel of air would have if moved dry adiabatically to a standard pressure, typically representative of mean sea level pressure. To specify the standard pressure to which the quantity applies, provide a scalar coordinate variable with standard name reference_pressure. It is strongly recommended that a variable with this standard name should have a units_metadata attribute, with one of the values "on-scale" or "difference", whichever is appropriate for the data, because it is essential to know whether the temperature is on-scale (meaning relative to the origin of the scale indicated by the units) or refers to temperature differences (implying that the origin of the temperature scale is irrevelant), in order to convert the units correctly (cf. https://cfconventions.org/cf-conventions/cf-conventions.html#temperature-units). @@ -178,56 +179,56 @@ K 14 - The pseudoequivalent potential temperature is the temperature a parcel of air would have if it is expanded by a pseudoadiabatic (irreversible moist-adiabatic) process to zero pressure and afterwards compressed by a dry-adiabatic process to a standard pressure, typically representative of mean sea level pressure. Reference: AMS Glossary http://glossary.ametsoc.org/wiki/Pseudoequivalent_potential_temperature. A pseudoadiabatic process means that the liquid water that condenses is assumed to be removed as soon as it is formed. Reference: AMS Glossary http:/glossary.ametsoc.org/wiki/Pseudoadiabatic_process. To specify the standard pressure to which the quantity applies, provide a scalar coordinate variable with the standard name reference_pressure. + The pseudoequivalent potential temperature is the temperature a parcel of air would have if it is expanded by a pseudoadiabatic (irreversible moist-adiabatic) process to zero pressure and afterwards compressed by a dry-adiabatic process to a standard pressure, typically representative of mean sea level pressure. Reference: AMS Glossary http://glossary.ametsoc.org/wiki/Pseudoequivalent_potential_temperature. A pseudoadiabatic process means that the liquid water that condenses is assumed to be removed as soon as it is formed. Reference: AMS Glossary http:/glossary.ametsoc.org/wiki/Pseudoadiabatic_process. To specify the standard pressure to which the quantity applies, provide a scalar coordinate variable with the standard name reference_pressure. It is strongly recommended that a variable with this standard name should have a units_metadata attribute, with one of the values "on-scale" or "difference", whichever is appropriate for the data, because it is essential to know whether the temperature is on-scale (meaning relative to the origin of the scale indicated by the units) or refers to temperature differences (implying that the origin of the temperature scale is irrevelant), in order to convert the units correctly (cf. https://cfconventions.org/cf-conventions/cf-conventions.html#temperature-units). K - The pseudoequivalent temperature is also known as the adiabatic equivalent temperature. It is the temperature that an air parcel would have after undergoing the following process: dry-adiabatic expansion until saturated; pseudoadiabatic expansion until all moisture is precipitated out; dry-adiabatic compression to the initial pressure. Reference: AMS Glossary http://glossary.ametsoc.org/wiki/Equivalent_temperature. This quantity is distinct from the isobaric equivalent temperature, also known as equivalent temperature, which has the standard name air_equivalent_temperature. + The pseudoequivalent temperature is also known as the adiabatic equivalent temperature. It is the temperature that an air parcel would have after undergoing the following process: dry-adiabatic expansion until saturated; pseudoadiabatic expansion until all moisture is precipitated out; dry-adiabatic compression to the initial pressure. Reference: AMS Glossary http://glossary.ametsoc.org/wiki/Equivalent_temperature. This quantity is distinct from the isobaric equivalent temperature, also known as equivalent temperature, which has the standard name air_equivalent_temperature. It is strongly recommended that a variable with this standard name should have a units_metadata attribute, with one of the values "on-scale" or "difference", whichever is appropriate for the data, because it is essential to know whether the temperature is on-scale (meaning relative to the origin of the scale indicated by the units) or refers to temperature differences (implying that the origin of the temperature scale is irrevelant), in order to convert the units correctly (cf. https://cfconventions.org/cf-conventions/cf-conventions.html#temperature-units). K 11 E130 ta - Air temperature is the bulk temperature of the air, not the surface (skin) temperature. + Air temperature is the bulk temperature of the air, not the surface (skin) temperature. It is strongly recommended that a variable with this standard name should have a units_metadata attribute, with one of the values "on-scale" or "difference", whichever is appropriate for the data, because it is essential to know whether the temperature is on-scale (meaning relative to the origin of the scale indicated by the units) or refers to temperature differences (implying that the origin of the temperature scale is irrevelant), in order to convert the units correctly (cf. https://cfconventions.org/cf-conventions/cf-conventions.html#temperature-units). K 25 - "anomaly" means difference from climatology. Air temperature is the bulk temperature of the air, not the surface (skin) temperature. + The term "anomaly" means difference from climatology. Air temperature is the bulk temperature of the air, not the surface (skin) temperature. It is strongly recommended that a variable with this standard name should have the attribute units_metadata="temperature: difference", meaning that it refers to temperature differences and implying that the origin of the temperature scale is irrelevant, because it is essential to know whether a temperature is on-scale or a difference in order to convert the units correctly (cf. https://cfconventions.org/cf-conventions/cf-conventions.html#temperature-units). K - cloud_top refers to the top of the highest cloud. Air temperature is the bulk temperature of the air, not the surface (skin) temperature. + cloud_top refers to the top of the highest cloud. Air temperature is the bulk temperature of the air, not the surface (skin) temperature. It is strongly recommended that a variable with this standard name should have a units_metadata attribute, with one of the values "on-scale" or "difference", whichever is appropriate for the data, because it is essential to know whether the temperature is on-scale (meaning relative to the origin of the scale indicated by the units) or refers to temperature differences (implying that the origin of the temperature scale is irrevelant), in order to convert the units correctly (cf. https://cfconventions.org/cf-conventions/cf-conventions.html#temperature-units). K - The "effective cloud top defined by infrared radiation" is (approximately) the geometric height above the surface that is one optical depth at infrared wavelengths (in the region of 11 micrometers) below the cloud top that would be detected by visible and lidar techniques. Reference: Minnis, P. et al 2011 CERES Edition-2 Cloud Property Retrievals Using TRMM VIRS and Terra and Aqua MODIS Data x2014; Part I: Algorithms IEEE Transactions on Geoscience and Remote Sensing, 49(11), 4374-4400. doi: http://dx.doi.org/10.1109/TGRS.2011.2144601. + The "effective cloud top defined by infrared radiation" is (approximately) the geometric height above the surface that is one optical depth at infrared wavelengths (in the region of 11 micrometers) below the cloud top that would be detected by visible and lidar techniques. Reference: Minnis, P. et al 2011 CERES Edition-2 Cloud Property Retrievals Using TRMM VIRS and Terra and Aqua MODIS Data x2014; Part I: Algorithms IEEE Transactions on Geoscience and Remote Sensing, 49(11), 4374-4400. doi: http://dx.doi.org/10.1109/TGRS.2011.2144601. It is strongly recommended that a variable with this standard name should have a units_metadata attribute, with one of the values "on-scale" or "difference", whichever is appropriate for the data, because it is essential to know whether the temperature is on-scale (meaning relative to the origin of the scale indicated by the units) or refers to temperature differences (implying that the origin of the temperature scale is irrevelant), in order to convert the units correctly (cf. https://cfconventions.org/cf-conventions/cf-conventions.html#temperature-units). K m-1 19 - Air temperature is the bulk temperature of the air, not the surface (skin) temperature. A lapse rate is the negative derivative of a quantity with respect to increasing height above the surface, or the (positive) derivative with respect to increasing depth. + Air temperature is the bulk temperature of the air, not the surface (skin) temperature. A lapse rate is the negative derivative of a quantity with respect to increasing height above the surface, or the (positive) derivative with respect to increasing depth. It is strongly recommended that a variable with this standard name should have the attribute units_metadata="temperature: difference", meaning that it refers to temperature differences and implying that the origin of the temperature scale is irrelevant, because it is essential to know whether a temperature is on-scale or a difference in order to convert the units correctly (cf. https://cfconventions.org/cf-conventions/cf-conventions.html#temperature-units). K - Air temperature is the bulk temperature of the air, not the surface (skin) temperature. Air temperature excess and deficit are calculated relative to the air temperature threshold. + Air temperature is the bulk temperature of the air, not the surface (skin) temperature. Air temperature excess and deficit are calculated relative to the air temperature threshold. It is strongly recommended that a variable with this standard name should have the attribute units_metadata="temperature: on-scale", meaning that the temperature is relative to the origin of the scale indicated by the units, because it is essential to know whether a temperature is on-scale or a difference in order to convert the units correctly (cf. https://cfconventions.org/cf-conventions/cf-conventions.html#temperature-units). @@ -353,7 +354,7 @@ K - Air temperature is the bulk temperature of the air, not the surface (skin) temperature. The quantity with standard name apparent_air_temperature is the perceived air temperature derived from either a combination of temperature and wind (which has standard name wind_chill_of_air_temperature) or temperature and humidity (which has standard name heat_index_of_air_temperature) for the hour indicated by the time coordinate variable. When the air temperature falls to 283.15 K or below, wind chill is used for the apparent_air_temperature. When the air temperature rises above 299.817 K, the heat index is used for apparent_air_temperature. For temperatures above 283.15 and below 299.817K, the apparent_air_temperature is the ambient air temperature (which has standard name air_temperature). References: https://digital.weather.gov/staticpages/definitions.php; WMO codes registry entry http://codes.wmo.int/grib2/codeflag/4.2/_0-0-21. + Air temperature is the bulk temperature of the air, not the surface (skin) temperature. The quantity with standard name apparent_air_temperature is the perceived air temperature derived from either a combination of temperature and wind (which has standard name wind_chill_of_air_temperature) or temperature and humidity (which has standard name heat_index_of_air_temperature) for the hour indicated by the time coordinate variable. When the air temperature falls to 283.15 K or below, wind chill is used for the apparent_air_temperature. When the air temperature rises above 299.817 K, the heat index is used for apparent_air_temperature. For temperatures above 283.15 and below 299.817K, the apparent_air_temperature is the ambient air temperature (which has standard name air_temperature). References: https://digital.weather.gov/staticpages/definitions.php; WMO codes registry entry http://codes.wmo.int/grib2/codeflag/4.2/_0-0-21. It is strongly recommended that a variable with this standard name should have a units_metadata attribute, with one of the values "on-scale" or "difference", whichever is appropriate for the data, because it is essential to know whether the temperature is on-scale (meaning relative to the origin of the scale indicated by the units) or refers to temperature differences (implying that the origin of the temperature scale is irrevelant), in order to convert the units correctly (cf. https://cfconventions.org/cf-conventions/cf-conventions.html#temperature-units). @@ -2383,21 +2384,21 @@ K - The atmosphere_stability_k_index is an index that indicates the potential of severe convection and is often referred to a simply the k index. The index is derived from the difference in air temperature between 850 and 500 hPa, the dew point temperature at 850 hPa, and the difference between the air temperature and the dew point temperature at 700 hPa. + The atmosphere_stability_k_index is an index that indicates the potential of severe convection and is often referred to as simply the k index. The index is calculated as A + B - C, where A is the difference in air temperature between 850 and 500 hPa, B is the dew point temperature at 850 hPa, and C is the dew point depression (i.e. the amount by which the air temperature exceeds its dew point temperature) at 700 hPa. It is strongly recommended that a variable with this standard name should have a units_metadata attribute, with one of the values "on-scale" or "difference", whichever is appropriate for the data, because it is essential to know whether the temperature is on-scale (meaning relative to the origin of the scale indicated by the units) or refers to temperature differences (implying that the origin of the temperature scale is irrevelant), in order to convert the units correctly (cf. https://cfconventions.org/cf-conventions/cf-conventions.html#temperature-units). K - The atmosphere_stability_showalter_index is an index used to determine convective and thunderstorm potential and is often referred to as simply the showalter index. The index is defined as the temperature difference between a parcel of air lifted from 850 to 500 hPa (wet adiabatically) and the ambient air temperature at 500 hPa. + The atmosphere_stability_showalter_index is an index used to determine convective and thunderstorm potential and is often referred to as simply the showalter index. The index is defined as the temperature difference between a parcel of air lifted from 850 to 500 hPa (wet adiabatically) and the ambient air temperature at 500 hPa. It is strongly recommended that a variable with this standard name should have the attribute units_metadata="temperature: difference", meaning that it refers to temperature differences and implying that the origin of the temperature scale is irrelevant, because it is essential to know whether a temperature is on-scale or a difference in order to convert the units correctly (cf. https://cfconventions.org/cf-conventions/cf-conventions.html#temperature-units). K - The atmosphere_stability_total_totals_index indicates thelikelihood of severe convection and is often referred to as simply thetotal totals index. The index is derived from the difference in airtemperature between 850 and 500 hPa (the vertical totals) and thedifference between the dew point temperature at 850 hPa and the airtemperature at 500 hPa (the cross totals). The vertical totals and crosstotals are summed to obtain the index. + The atmosphere_stability_total_totals_index indicates thelikelihood of severe convection and is often referred to as simply thetotal totals index. The index is derived from the difference in airtemperature between 850 and 500 hPa (the vertical totals) and thedifference between the dew point temperature at 850 hPa and the airtemperature at 500 hPa (the cross totals). The vertical totals and crosstotals are summed to obtain the index. It is strongly recommended that a variable with this standard name should have the attribute units_metadata="temperature: difference", meaning that it refers to temperature differences and implying that the origin of the temperature scale is irrelevant, because it is essential to know whether a temperature is on-scale or a difference in order to convert the units correctly (cf. https://cfconventions.org/cf-conventions/cf-conventions.html#temperature-units). @@ -2554,6 +2555,13 @@ The specification of a physical process by the phrase due_to_process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. Altitude is the (geometric) height above the geoid, which is the reference geopotential surface. The geoid is similar to mean sea level. "Bedrock" is the solid Earth surface beneath land ice, ocean water or soil. The zero of bedrock altitude change is arbitrary. Isostatic adjustment is the vertical movement of the lithosphere due to changing surface ice and water loads. + + m + + + The bedrock_depth_below_ground_level is the vertical distance between the ground and the bedrock. "Bedrock" refers to the surface of the consolidated rock, beneath any unconsolidated rock, sediment, soil, water or land ice. "Ground level" means the level of the solid surface in land areas without permanent inland water, beneath any snow, ice or surface water. + + @@ -2586,21 +2594,21 @@ K 118 - The brightness temperature of a body is the temperature of a black body which radiates the same power per unit solid angle per unit area. + The brightness temperature of a body is the temperature of a black body which radiates the same power per unit solid angle per unit area. It is strongly recommended that a variable with this standard name should have a units_metadata attribute, with one of the values "on-scale" or "difference", whichever is appropriate for the data, because it is essential to know whether the temperature is on-scale (meaning relative to the origin of the scale indicated by the units) or refers to temperature differences (implying that the origin of the temperature scale is irrevelant), in order to convert the units correctly (cf. https://cfconventions.org/cf-conventions/cf-conventions.html#temperature-units).. K - The brightness temperature of a body is the temperature of a black body which radiates the same power per unit solid angle per unit area. "anomaly" means difference from climatology. + The brightness temperature of a body is the temperature of a black body which radiates the same power per unit solid angle per unit area. "anomaly" means difference from climatology. It is strongly recommended that a variable with this standard name should have the attribute units_metadata="temperature: difference", meaning that it refers to temperature differences and implying that the origin of the temperature scale is irrelevant, because it is essential to know whether a temperature is on-scale or a difference in order to convert the units correctly (cf. https://cfconventions.org/cf-conventions/cf-conventions.html#temperature-units). K - cloud_top refers to the top of the highest cloud. brightness_temperature of a body is the temperature of a black body which radiates the same power per unit solid angle per unit area. A coordinate variable of radiation_wavelength, sensor_band_central_radiation_wavelength, or radiation_frequency may be specified to indicate that the brightness temperature applies at specific wavelengths or frequencies. + cloud_top refers to the top of the highest cloud. brightness_temperature of a body is the temperature of a black body which radiates the same power per unit solid angle per unit area. A coordinate variable of radiation_wavelength, sensor_band_central_radiation_wavelength, or radiation_frequency may be specified to indicate that the brightness temperature applies at specific wavelengths or frequencies. It is strongly recommended that a variable with this standard name should have a units_metadata attribute, with one of the values "on-scale" or "difference", whichever is appropriate for the data, because it is essential to know whether the temperature is on-scale (meaning relative to the origin of the scale indicated by the units) or refers to temperature differences (implying that the origin of the temperature scale is irrevelant), in order to convert the units correctly (cf. https://cfconventions.org/cf-conventions/cf-conventions.html#temperature-units). @@ -2670,7 +2678,7 @@ K - "Canopy temperature" is the bulk temperature of the canopy, not the surface (skin) temperature. "Canopy" means the vegetative covering over a surface. The canopy is often considered to be the outer surfaces of the vegetation. Plant height and the distribution, orientation and shape of plant leaves within a canopy influence the atmospheric environment and many plant processes within the canopy. Reference: AMS Glossary http://glossary.ametsoc.org/wiki/Canopy. + "Canopy temperature" is the bulk temperature of the canopy, not the surface (skin) temperature. "Canopy" means the vegetative covering over a surface. The canopy is often considered to be the outer surfaces of the vegetation. Plant height and the distribution, orientation and shape of plant leaves within a canopy influence the atmospheric environment and many plant processes within the canopy. Reference: AMS Glossary http://glossary.ametsoc.org/wiki/Canopy. It is strongly recommended that a variable with this standard name should have a units_metadata attribute, with one of the values "on-scale" or "difference", whichever is appropriate for the data, because it is essential to know whether the temperature is on-scale (meaning relative to the origin of the scale indicated by the units) or refers to temperature differences (implying that the origin of the temperature scale is irrevelant), in order to convert the units correctly (cf. https://cfconventions.org/cf-conventions/cf-conventions.html#temperature-units). @@ -2838,7 +2846,7 @@ K - "change_over_time_in_X" means change in a quantity X over a time-interval, which should be defined by the bounds of the time coordinate. Conservative Temperature is defined as part of the Thermodynamic Equation of Seawater 2010 (TEOS-10) which was adopted in 2010 by the International Oceanographic Commission (IOC). Conservative Temperature is specific potential enthalpy (which has the standard name sea_water_specific_potential_enthalpy) divided by a fixed value of the specific heat capacity of sea water, namely cp_0 = 3991.86795711963 J kg-1 K-1. Conservative Temperature is a more accurate measure of the "heat content" of sea water, by a factor of one hundred, than is potential temperature. Because of this, it can be regarded as being proportional to the heat content of sea water per unit mass. Reference: www.teos-10.org; McDougall, 2003 doi: 10.1175/1520-0485(2003)033<0945:PEACOV>2.0.CO;2. + The phrase "change_over_time_in_X" means change in a quantity X over a time-interval, which should be defined by the bounds of the time coordinate. Conservative Temperature is defined as part of the Thermodynamic Equation of Seawater 2010 (TEOS-10) which was adopted in 2010 by the International Oceanographic Commission (IOC). Conservative Temperature is specific potential enthalpy (which has the standard name sea_water_specific_potential_enthalpy) divided by a fixed value of the specific heat capacity of sea water, namely cp_0 = 3991.86795711963 J kg-1 K-1. Conservative Temperature is a more accurate measure of the "heat content" of sea water, by a factor of one hundred, than is potential temperature. Because of this, it can be regarded as being proportional to the heat content of sea water per unit mass. Reference: www.teos-10.org; McDougall, 2003 doi: 10.1175/1520-0485(2003)033<0945:PEACOV>2.0.CO;2. It is strongly recommended that a variable with this standard name should have the attribute units_metadata="temperature: difference", meaning that it refers to temperature differences and implying that the origin of the temperature scale is irrelevant, because it is essential to know whether a temperature is on-scale or a difference in order to convert the units correctly (cf. https://cfconventions.org/cf-conventions/cf-conventions.html#temperature-units). @@ -2866,7 +2874,7 @@ K - Potential temperature is the temperature a parcel of air or sea water would have if moved adiabatically to sea level pressure. "change_over_time_in_X" means change in a quantity X over a time-interval, which should be defined by the bounds of the time coordinate. + Potential temperature is the temperature a parcel of air or sea water would have if moved adiabatically to sea level pressure. The phrase "change_over_time_in_X" means change in a quantity X over a time-interval, which should be defined by the bounds of the time coordinate. It is strongly recommended that a variable with this standard name should have the attribute units_metadata="temperature: difference", meaning that it refers to temperature differences and implying that the origin of the temperature scale is irrelevant, because it is essential to know whether a temperature is on-scale or a difference in order to convert the units correctly (cf. https://cfconventions.org/cf-conventions/cf-conventions.html#temperature-units). @@ -2901,7 +2909,7 @@ K - "change_over_time_in_X" means change in a quantity X over a time-interval, which should be defined by the bounds of the time coordinate.Sea water temperature is the in situ temperature of the sea water. To specify the depth at which the temperature applies use a vertical coordinate variable or scalar coordinate variable. There are standard names for sea_surface_temperature, sea_surface_skin_temperature, sea_surface_subskin_temperature and sea_surface_foundation_temperature which can be used to describe data located at the specified surfaces. For observed data, depending on the period during which the observation was made, the measured in situ temperature was recorded against standard "scales". These historical scales include the International Practical Temperature Scale of 1948 (IPTS-48; 1948-1967), the International Practical Temperature Scale of 1968 (IPTS-68, Barber, 1969; 1968-1989) and the International Temperature Scale of 1990 (ITS-90, Saunders 1990; 1990 onwards). Conversion of data between these scales follows t68 = t48 - (4.4 x 10e-6) * t48(100 - t - 48); t90 = 0.99976 * t68. Observations made prior to 1948 (IPTS-48) have not been documented and therefore a conversion cannot be certain. Differences between t90 and t68 can be up to 0.01 at temperatures of 40 C and above; differences of 0.002-0.007 occur across the standard range of ocean temperatures (-10 - 30 C). The International Equation of State of Seawater 1980 (EOS-80, UNESCO, 1981) and the Practical Salinity Scale (PSS-78) were both based on IPTS-68, while the Thermodynamic Equation of Seawater 2010 (TEOS-10) is based on ITS-90. References: Barber, 1969, doi: 10.1088/0026-1394/5/2/001; UNESCO, 1981; Saunders, 1990, WOCE Newsletter, 10, September 1990. + The phrase "change_over_time_in_X" means change in a quantity X over a time-interval, which should be defined by the bounds of the time coordinate.Sea water temperature is the in situ temperature of the sea water. To specify the depth at which the temperature applies use a vertical coordinate variable or scalar coordinate variable. There are standard names for sea_surface_temperature, sea_surface_skin_temperature, sea_surface_subskin_temperature and sea_surface_foundation_temperature which can be used to describe data located at the specified surfaces. For observed data, depending on the period during which the observation was made, the measured in situ temperature was recorded against standard "scales". These historical scales include the International Practical Temperature Scale of 1948 (IPTS-48; 1948-1967), the International Practical Temperature Scale of 1968 (IPTS-68, Barber, 1969; 1968-1989) and the International Temperature Scale of 1990 (ITS-90, Saunders 1990; 1990 onwards). Conversion of data between these scales follows t68 = t48 - (4.4 x 10e-6) * t48(100 - t - 48); t90 = 0.99976 * t68. Observations made prior to 1948 (IPTS-48) have not been documented and therefore a conversion cannot be certain. Differences between t90 and t68 can be up to 0.01 at temperatures of 40 C and above; differences of 0.002-0.007 occur across the standard range of ocean temperatures (-10 - 30 C). The International Equation of State of Seawater 1980 (EOS-80, UNESCO, 1981) and the Practical Salinity Scale (PSS-78) were both based on IPTS-68, while the Thermodynamic Equation of Seawater 2010 (TEOS-10) is based on ITS-90. References: Barber, 1969, doi: 10.1088/0026-1394/5/2/001; UNESCO, 1981; Saunders, 1990, WOCE Newsletter, 10, September 1990. It is strongly recommended that a variable with this standard name should have the attribute units_metadata="temperature: difference", meaning that it refers to temperature differences and implying that the origin of the temperature scale is irrelevant, because it is essential to know whether a temperature is on-scale or a difference in order to convert the units correctly (cf. https://cfconventions.org/cf-conventions/cf-conventions.html#temperature-units). @@ -3174,7 +3182,7 @@ K m s-1 - Covariance refers to the sample covariance rather than the population covariance. The quantity with standard name covariance_over_longitude_of_northward_wind_and_air_temperature is the covariance of the deviations of meridional air velocity and air temperature about their respective zonal mean values. The data variable must be accompanied by a vertical coordinate variable or scalar coordinate variable and is calculated on an isosurface of that vertical coordinate. "Northward" indicates a vector component which is positive when directed northward (negative southward). Wind is defined as a two-dimensional (horizontal) air velocity vector, with no vertical component. (Vertical motion in the atmosphere has the standard name "upward_air_velocity"). Air temperature is the bulk temperature of the air, not the surface (skin) temperature. + Covariance refers to the sample covariance rather than the population covariance. The quantity with standard name covariance_over_longitude_of_northward_wind_and_air_temperature is the covariance of the deviations of meridional air velocity and air temperature about their respective zonal mean values. The data variable must be accompanied by a vertical coordinate variable or scalar coordinate variable and is calculated on an isosurface of that vertical coordinate. "Northward" indicates a vector component which is positive when directed northward (negative southward). Wind is defined as a two-dimensional (horizontal) air velocity vector, with no vertical component. (Vertical motion in the atmosphere has the standard name "upward_air_velocity"). Air temperature is the bulk temperature of the air, not the surface (skin) temperature. It is strongly recommended that a variable with this standard name should have the attribute units_metadata="temperature: difference", meaning that it refers to temperature differences and implying that the origin of the temperature scale is irrelevant, because it is essential to know whether a temperature is on-scale or a difference in order to convert the units correctly (cf. https://cfconventions.org/cf-conventions/cf-conventions.html#temperature-units). @@ -3258,14 +3266,14 @@ K 18 - Dew point depression is also called dew point deficit. It is the amount by which the air temperature exceeds its dew point temperature. Dew point temperature is the temperature at which a parcel of air reaches saturation upon being cooled at constant pressure and specific humidity. + Dew point depression is also called dew point deficit. It is the amount by which the air temperature exceeds its dew point temperature. Dew point temperature is the temperature at which a parcel of air reaches saturation upon being cooled at constant pressure and specific humidity. It is strongly recommended that a variable with this standard name should have the attribute units_metadata="temperature: difference", meaning that it refers to temperature differences and implying that the origin of the temperature scale is irrelevant, because it is essential to know whether a temperature is on-scale or a difference in order to convert the units correctly (cf. https://cfconventions.org/cf-conventions/cf-conventions.html#temperature-units). K 17 - Dew point temperature is the temperature at which a parcel of air reaches saturation upon being cooled at constant pressure and specific humidity. + Dew point temperature is the temperature at which a parcel of air reaches saturation upon being cooled at constant pressure and specific humidity. It is strongly recommended that a variable with this standard name should have a units_metadata attribute, with one of the values "on-scale" or "difference", whichever is appropriate for the data, because it is essential to know whether the temperature is on-scale (meaning relative to the origin of the scale indicated by the units) or refers to temperature differences (implying that the origin of the temperature scale is irrevelant), in order to convert the units correctly (cf. https://cfconventions.org/cf-conventions/cf-conventions.html#temperature-units). @@ -3300,7 +3308,7 @@ K - Sea surface temperature is usually abbreviated as "SST". It is the temperature of sea water near the surface (including the part under sea-ice, if any), not the skin or interface temperature, whose standard names are sea_surface_skin_temperature and surface_temperature, respectively. For the temperature of sea water at a particular depth or layer, a data variable of "sea_water_temperature" with a vertical coordinate axis should be used. Air temperature is the bulk temperature of the air, not the surface (skin) temperature. + Sea surface temperature is usually abbreviated as "SST". It is the temperature of sea water near the surface (including the part under sea-ice, if any), not the skin or interface temperature, whose standard names are sea_surface_skin_temperature and surface_temperature, respectively. For the temperature of sea water at a particular depth or layer, a data variable of "sea_water_temperature" with a vertical coordinate axis should be used. Air temperature is the bulk temperature of the air, not the surface (skin) temperature. It is strongly recommended that a variable with this standard name should have the attribute units_metadata="temperature: difference", meaning that it refers to temperature differences and implying that the origin of the temperature scale is irrelevant, because it is essential to know whether a temperature is on-scale or a difference in order to convert the units correctly (cf. https://cfconventions.org/cf-conventions/cf-conventions.html#temperature-units). @@ -3576,6 +3584,20 @@ Downwelling radiation is radiation from above. It does not mean "net downward". The sign convention is that "upwelling" is positive upwards and "downwelling" is positive downwards. The term "longwave" means longwave radiation. When thought of as being incident on a surface, a radiative flux is sometimes called "irradiance". In addition, it is identical with the quantity measured by a cosine-collector light-meter and sometimes called "vector irradiance". In accordance with common usage in geophysical disciplines, "flux" implies per unit area, called "flux density" in physics. A phrase assuming_condition indicates that the named quantity is the value which would obtain if all aspects of the system were unaltered except for the assumption of the circumstances specified by the condition. "Clear sky" means in the absence of clouds. + + W/m2 + + + Downwelling radiation is radiation from above. It does not mean "net downward". The sign convention is that "upwelling" is positive upwards and "downwelling" is positive downwards. The term "longwave" means longwave radiation. When thought of as being incident on a surface, a radiative flux is sometimes called "irradiance". In addition, it is identical with the quantity measured by a cosine-collector light-meter and sometimes called "vector irradiance". In accordance with common usage in geophysical disciplines, "flux" implies per unit area, called "flux density" in physics. A phrase assuming_condition indicates that the named quantity is the value which would obtain if all aspects of the system were unaltered except for the assumption of the circumstances specified by the condition. "Clear sky" means in the absence of clouds. This 3D ozone field acts as a reference ozone field in a diagnostic call to the model's radiation scheme. It is expressed in terms of mole fraction of ozone in air. It may be observation-based or model-derived. It may be from any time period. By using the same ozone reference in the diagnostic radiation call in two model simulations and calculating differences between the radiative flux diagnostics from the prognostic call to the radiation scheme and the diagnostic call to the radiation scheme with the ozone reference, an instantaneous radiative forcing for ozone can be calculated. + + + + W/m2 + + + Downwelling radiation is radiation from above. It does not mean "net downward". The sign convention is that "upwelling" is positive upwards and "downwelling" is positive downwards. The term "longwave" means longwave radiation. When thought of as being incident on a surface, a radiative flux is sometimes called "irradiance". In addition, it is identical with the quantity measured by a cosine-collector light-meter and sometimes called "vector irradiance". In accordance with common usage in geophysical disciplines, "flux" implies per unit area, called "flux density" in physics. A phrase assuming_condition indicates that the named quantity is the value which would obtain if all aspects of the system were unaltered except for the assumption of the circumstances specified by the condition. This 3D ozone field acts as a reference ozone field in a diagnostic call to the model's radiation scheme. It is expressed in terms of mole fraction of ozone in air. It may be observation-based or model-derived. It may be from any time period. By using the same ozone reference in the diagnostic radiation call in two model simulations and calculating differences between the radiative flux diagnostics from the prognostic call to the radiation scheme and the diagnostic call to the radiation scheme with the ozone reference, an instantaneous radiative forcing for ozone can be calculated. + + W m-2 sr-1 @@ -3730,6 +3752,20 @@ Downwelling radiation is radiation from above. It does not mean "net downward". The sign convention is that "upwelling" is positive upwards and "downwelling" is positive downwards. The term "shortwave" means shortwave radiation. When thought of as being incident on a surface, a radiative flux is sometimes called "irradiance". In addition, it is identical with the quantity measured by a cosine-collector light-meter and sometimes called "vector irradiance". In accordance with common usage in geophysical disciplines, "flux" implies per unit area, called "flux density" in physics. A phrase "assuming_condition" indicates that the named quantity is the value which would obtain if all aspects of the system were unaltered except for the assumption of the circumstances specified by the condition. "Clear sky" means in the absence of clouds. + + W/m2 + + + Downwelling radiation is radiation from above. It does not mean "net downward". The sign convention is that "upwelling" is positive upwards and "downwelling" is positive downwards. The term "shortwave" means shortwave radiation. When thought of as being incident on a surface, a radiative flux is sometimes called "irradiance". In addition, it is identical with the quantity measured by a cosine-collector light-meter and sometimes called "vector irradiance". In accordance with common usage in geophysical disciplines, "flux" implies per unit area, called "flux density" in physics. A phrase "assuming_condition" indicates that the named quantity is the value which would obtain if all aspects of the system were unaltered except for the assumption of the circumstances specified by the condition. "Clear sky" means in the absence of clouds. This 3D ozone field acts as a reference ozone field in a diagnostic call to the model's radiation scheme. It is expressed in terms of mole fraction of ozone in air. It may be observation-based or model-derived. It may be from any time period. By using the same ozone reference in the diagnostic radiation call in two model simulations and calculating differences between the radiative flux diagnostics from the prognostic call to the radiation scheme and the diagnostic call to the radiation scheme with the ozone reference, an instantaneous radiative forcing for ozone can be calculated. + + + + W/m2 + + + Downwelling radiation is radiation from above. It does not mean "net downward". The sign convention is that "upwelling" is positive upwards and "downwelling" is positive downwards. The term "shortwave" means shortwave radiation. When thought of as being incident on a surface, a radiative flux is sometimes called "irradiance". In addition, it is identical with the quantity measured by a cosine-collector light-meter and sometimes called "vector irradiance". In accordance with common usage in geophysical disciplines, "flux" implies per unit area, called "flux density" in physics. A phrase "assuming_condition" indicates that the named quantity is the value which would obtain if all aspects of the system were unaltered except for the assumption of the circumstances specified by the condition. This 3D ozone field acts as a reference ozone field in a diagnostic call to the model's radiation scheme. It is expressed in terms of mole fraction of ozone in air. It may be observation-based or model-derived. It may be from any time period. By using the same ozone reference in the diagnostic radiation call in two model simulations and calculating differences between the radiative flux diagnostics from the prognostic call to the radiation scheme and the diagnostic call to the radiation scheme with the ozone reference, an instantaneous radiative forcing for ozone can be calculated. + + W m-2 @@ -3772,13 +3808,6 @@ The quantity with standard name drainage_amount_through_base_of_soil_model is the amount of water that drains through the bottom of a soil column extending from the surface to a specified depth. "Drainage" is the process of removal of excess water from soil by gravitational flow. "Amount" means mass per unit area. A vertical coordinate variable or scalar coordinate with standard name "depth" should be used to specify the depth to which the soil column extends. - - kg m-2 - - - “Drainage” is the process of removal of excess water from soil by gravitational flow. "Amount" means mass per unit area. The vertical drainage amount in soil is the amount of water that drains through the bottom of a soil column extending from the surface to a specified depth. - - 1 @@ -3839,7 +3868,7 @@ K - The dynamical tropopause used in interpreting the dynamics of the upper troposphere and lower stratosphere. There are various definitions of dynamical tropopause in the scientific literature. + The dynamical tropopause used in interpreting the dynamics of the upper troposphere and lower stratosphere. There are various definitions of dynamical tropopause in the scientific literature. It is strongly recommended that a variable with this standard name should have a units_metadata attribute, with one of the values "on-scale" or "difference", whichever is appropriate for the data, because it is essential to know whether the temperature is on-scale (meaning relative to the origin of the scale indicated by the units) or refers to temperature differences (implying that the origin of the temperature scale is irrevelant), in order to convert the units correctly (cf. https://cfconventions.org/cf-conventions/cf-conventions.html#temperature-units). @@ -4182,7 +4211,7 @@ K m2 kg-1 s-1 vorpot - The Ertel potential vorticity is the scalar product of the atmospheric absolute vorticity vector and the gradient of potential temperature. It is a conserved quantity in the absence of friction and heat sources [AMS Glossary, http://glossary.ametsoc.org/wiki/Ertel_potential_vorticity]. A frequently used simplification of the general Ertel potential vorticity considers the Earth rotation vector to have only a vertical component. Then, only the vertical contribution of the scalar product is calculated. + The Ertel potential vorticity is the scalar product of the atmospheric absolute vorticity vector and the gradient of potential temperature. It is a conserved quantity in the absence of friction and heat sources [AMS Glossary, http://glossary.ametsoc.org/wiki/Ertel_potential_vorticity]. A frequently used simplification of the general Ertel potential vorticity considers the Earth rotation vector to have only a vertical component. Then, only the vertical contribution of the scalar product is calculated. It is strongly recommended that a variable with this standard name should have the attribute units_metadata="temperature: difference", meaning that it refers to temperature differences and implying that the origin of the temperature scale is irrelevant, because it is essential to know whether a temperature is on-scale or a difference in order to convert the units correctly (cf. https://cfconventions.org/cf-conventions/cf-conventions.html#temperature-units). @@ -4217,7 +4246,7 @@ K - The overall temperature of a fire area due to contributions from smoldering and flaming biomass. A data variable containing the area affected by fire should be given the standard name fire_area. + The overall temperature of a fire area due to contributions from smoldering and flaming biomass. A data variable containing the area affected by fire should be given the standard name fire_area. It is strongly recommended that a variable with this standard name should have a units_metadata attribute, with one of the values "on-scale" or "difference", whichever is appropriate for the data, because it is essential to know whether the temperature is on-scale (meaning relative to the origin of the scale indicated by the units) or refers to temperature differences (implying that the origin of the temperature scale is irrevelant), in order to convert the units correctly (cf. https://cfconventions.org/cf-conventions/cf-conventions.html#temperature-units). @@ -4700,7 +4729,7 @@ K - Air temperature is the bulk temperature of the air, not the surface (skin) temperature. The quantity with standard name heat_index_of_air_temperature is the perceived air temperature when relative humidity is taken into consideration (which makes it feel hotter than the actual air temperature). Heat index is only defined when the ambient air temperature is at or above 299.817 K. References: https://www.weather.gov/safety/heat-index; WMO codes registry entry http://codes.wmo.int/grib2/codeflag/4.2/_0-0-12. + Air temperature is the bulk temperature of the air, not the surface (skin) temperature. The quantity with standard name heat_index_of_air_temperature is the perceived air temperature when relative humidity is taken into consideration (which makes it feel hotter than the actual air temperature). Heat index is only defined when the ambient air temperature is at or above 299.817 K. References: https://www.weather.gov/safety/heat-index; WMO codes registry entry http://codes.wmo.int/grib2/codeflag/4.2/_0-0-12. It is strongly recommended that a variable with this standard name should have a units_metadata attribute, with one of the values "on-scale" or "difference", whichever is appropriate for the data, because it is essential to know whether the temperature is on-scale (meaning relative to the origin of the scale indicated by the units) or refers to temperature differences (implying that the origin of the temperature scale is irrevelant), in order to convert the units correctly (cf. https://cfconventions.org/cf-conventions/cf-conventions.html#temperature-units). @@ -4847,14 +4876,14 @@ kg degree_C m-2 - The phrase "integral_wrt_X_of_Y" means int Y dX. To specify the limits of the integral the data variable should have an axis for X and associated coordinate bounds. If no axis for X is associated with the data variable, or no coordinate bounds are specified, it is assumed that the integral is calculated over the entire vertical extent of the medium, e.g, if the medium is air the integral is assumed to be calculated over the full depth of the atmosphere. The phrase "wrt" means "with respect to". Depth is the vertical distance below the surface. The phrase "product_of_X_and_Y" means X*Y. Conservative Temperature is defined as part of the Thermodynamic Equation of Seawater 2010 (TEOS-10) which was adopted in 2010 by the International Oceanographic Commission (IOC). Conservative Temperature is specific potential enthalpy (which has the standard name sea_water_specific_potential_enthalpy) divided by a fixed value of the specific heat capacity of sea water, namely cp_0 = 3991.86795711963 J kg-1 K-1. Conservative Temperature is a more accurate measure of the "heat content" of sea water, by a factor of one hundred, than is potential temperature. Because of this, it can be regarded as being proportional to the heat content of sea water per unit mass. Reference: www.teos-10.org; McDougall, 2003 doi: 10.1175/1520-0485(2003)033<0945:PEACOV>2.0.CO;2. Sea water density is the in-situ density (not the potential density). For Boussinesq models, density is the constant Boussinesq reference density, a quantity which has the standard name reference_sea_water_density_for_boussinesq_approximation. + The phrase "integral_wrt_X_of_Y" means int Y dX. To specify the limits of the integral the data variable should have an axis for X and associated coordinate bounds. If no axis for X is associated with the data variable, or no coordinate bounds are specified, it is assumed that the integral is calculated over the entire vertical extent of the medium, e.g, if the medium is air the integral is assumed to be calculated over the full depth of the atmosphere. The phrase "wrt" means "with respect to". Depth is the vertical distance below the surface. The phrase "product_of_X_and_Y" means X*Y. Conservative Temperature is defined as part of the Thermodynamic Equation of Seawater 2010 (TEOS-10) which was adopted in 2010 by the International Oceanographic Commission (IOC). Conservative Temperature is specific potential enthalpy (which has the standard name sea_water_specific_potential_enthalpy) divided by a fixed value of the specific heat capacity of sea water, namely cp_0 = 3991.86795711963 J kg-1 K-1. Conservative Temperature is a more accurate measure of the "heat content" of sea water, by a factor of one hundred, than is potential temperature. Because of this, it can be regarded as being proportional to the heat content of sea water per unit mass. Reference: www.teos-10.org; McDougall, 2003 doi: 10.1175/1520-0485(2003)033<0945:PEACOV>2.0.CO;2. Sea water density is the in-situ density (not the potential density). For Boussinesq models, density is the constant Boussinesq reference density, a quantity which has the standard name reference_sea_water_density_for_boussinesq_approximation. It is strongly recommended that a variable with this standard name should have a units_metadata attribute, with one of the values "on-scale" or "difference", whichever is appropriate for the data, because it is essential to know whether the temperature is on-scale (meaning relative to the origin of the scale indicated by the units) or refers to temperature differences (implying that the origin of the temperature scale is irrevelant), in order to convert the units correctly (cf. https://cfconventions.org/cf-conventions/cf-conventions.html#temperature-units). kg degree_C m-2 - The phrase "integral_wrt_X_of_Y" means int Y dX. To specify the limits of the integral the data variable should have an axis for X and associated coordinate bounds. If no axis for X is associated with the data variable, or no coordinate bounds are specified, it is assumed that the integral is calculated over the entire vertical extent of the medium, e.g, if the medium is air the integral is assumed to be calculated over the full depth of the atmosphere. The phrase "wrt" means "with respect to". The phrase "product_of_X_and_Y" means X*Y. Depth is the vertical distance below the surface. Potential temperature is the temperature a parcel of air or sea water would have if moved adiabatically to sea level pressure. Sea water density is the in-situ density (not the potential density). For Boussinesq models, density is the constant Boussinesq reference density, a quantity which has the standard name reference_sea_water_density_for_boussinesq_approximation. + The phrase "integral_wrt_X_of_Y" means int Y dX. To specify the limits of the integral the data variable should have an axis for X and associated coordinate bounds. If no axis for X is associated with the data variable, or no coordinate bounds are specified, it is assumed that the integral is calculated over the entire vertical extent of the medium, e.g, if the medium is air the integral is assumed to be calculated over the full depth of the atmosphere. The phrase "wrt" means "with respect to". The phrase "product_of_X_and_Y" means X*Y. Depth is the vertical distance below the surface. Potential temperature is the temperature a parcel of air or sea water would have if moved adiabatically to sea level pressure. Sea water density is the in-situ density (not the potential density). For Boussinesq models, density is the constant Boussinesq reference density, a quantity which has the standard name reference_sea_water_density_for_boussinesq_approximation. It is strongly recommended that a variable with this standard name should have a units_metadata attribute, with one of the values "on-scale" or "difference", whichever is appropriate for the data, because it is essential to know whether the temperature is on-scale (meaning relative to the origin of the scale indicated by the units) or refers to temperature differences (implying that the origin of the temperature scale is irrevelant), in order to convert the units correctly (cf. https://cfconventions.org/cf-conventions/cf-conventions.html#temperature-units). @@ -4875,7 +4904,7 @@ K m - The phrase "integral_wrt_X_of_Y" means int Y dX. To specify the limits of the integral the data variable should have an axis for X and associated coordinate bounds. If no axis for X is associated with the data variable, or no coordinate bounds are specified, it is assumed that the integral is calculated over the entire vertical extent of the medium, e.g, if the medium is air the integral is assumed to be calculated over the full depth of the atmosphere. "wrt" means with respect to. Depth is the vertical distance below the surface. Sea water temperature is the in situ temperature of the sea water. For observed data, depending on the period during which the observation was made, the measured in situ temperature was recorded against standard "scales". These historical scales include the International Practical Temperature Scale of 1948 (IPTS-48; 1948-1967), the International Practical Temperature Scale of 1968 (IPTS-68, Barber, 1969; 1968-1989) and the International Temperature Scale of 1990 (ITS-90, Saunders 1990; 1990 onwards). Conversion of data between these scales follows t68 = t48 - (4.4 x 10e-6) * t48(100 - t - 48); t90 = 0.99976 * t68. Observations made prior to 1948 (IPTS-48) have not been documented and therefore a conversion cannot be certain. Differences between t90 and t68 can be up to 0.01 at temperatures of 40 C and above; differences of 0.002-0.007 occur across the standard range of ocean temperatures (-10 - 30 C). The International Equation of State of Seawater 1980 (EOS-80, UNESCO, 1981) and the Practical Salinity Scale (PSS-78) were both based on IPTS-68, while the Thermodynamic Equation of Seawater 2010 (TEOS-10) is based on ITS-90. References: Barber, 1969, doi: 10.1088/0026-1394/5/2/001; UNESCO, 1981; Saunders, 1990, WOCE Newsletter, 10, September 1990. + The phrase "integral_wrt_X_of_Y" means int Y dX. To specify the limits of the integral the data variable should have an axis for X and associated coordinate bounds. If no axis for X is associated with the data variable, or no coordinate bounds are specified, it is assumed that the integral is calculated over the entire vertical extent of the medium, e.g, if the medium is air the integral is assumed to be calculated over the full depth of the atmosphere. "wrt" means with respect to. Depth is the vertical distance below the surface. Sea water temperature is the in situ temperature of the sea water. For observed data, depending on the period during which the observation was made, the measured in situ temperature was recorded against standard "scales". These historical scales include the International Practical Temperature Scale of 1948 (IPTS-48; 1948-1967), the International Practical Temperature Scale of 1968 (IPTS-68, Barber, 1969; 1968-1989) and the International Temperature Scale of 1990 (ITS-90, Saunders 1990; 1990 onwards). Conversion of data between these scales follows t68 = t48 - (4.4 x 10e-6) * t48(100 - t - 48); t90 = 0.99976 * t68. Observations made prior to 1948 (IPTS-48) have not been documented and therefore a conversion cannot be certain. Differences between t90 and t68 can be up to 0.01 at temperatures of 40 C and above; differences of 0.002-0.007 occur across the standard range of ocean temperatures (-10 - 30 C). The International Equation of State of Seawater 1980 (EOS-80, UNESCO, 1981) and the Practical Salinity Scale (PSS-78) were both based on IPTS-68, while the Thermodynamic Equation of Seawater 2010 (TEOS-10) is based on ITS-90. References: Barber, 1969, doi: 10.1088/0026-1394/5/2/001; UNESCO, 1981; Saunders, 1990, WOCE Newsletter, 10, September 1990. It is strongly recommended that a variable with this standard name should have a units_metadata attribute, with one of the values "on-scale" or "difference", whichever is appropriate for the data, because it is essential to know whether the temperature is on-scale (meaning relative to the origin of the scale indicated by the units) or refers to temperature differences (implying that the origin of the temperature scale is irrevelant), in order to convert the units correctly (cf. https://cfconventions.org/cf-conventions/cf-conventions.html#temperature-units). @@ -4924,14 +4953,14 @@ K s - The phrase "integral_wrt_X_of_Y" means int Y dX. The data variable should have an axis for X specifying the limits of the integral as bounds. "wrt" means with respect to. Air temperature is the bulk temperature of the air, not the surface (skin) temperature. The air temperature deficit is the air temperature threshold minus the air temperature, where only positive values are included in the integral. Its integral with respect to time is often called after its units of "degree-days". The air_temperature variable, which is the data variable of the integral should have a scalar coordinate variable or a size-one coordinate variable with the standard name of air_temperature_threshold, to indicate the threshold. + The phrase "integral_wrt_X_of_Y" means int Y dX. The data variable should have an axis for X specifying the limits of the integral as bounds. "wrt" means with respect to. Air temperature is the bulk temperature of the air, not the surface (skin) temperature. The air temperature deficit is the air temperature threshold minus the air temperature, where only positive values are included in the integral. Its integral with respect to time is often called after its units of "degree-days". The air_temperature variable, which is the data variable of the integral should have a scalar coordinate variable or a size-one coordinate variable with the standard name of air_temperature_threshold, to indicate the threshold. It is strongly recommended that a variable with this standard name should have the attribute units_metadata="temperature: difference", meaning that it refers to temperature differences and implying that the origin of the temperature scale is irrelevant, because it is essential to know whether a temperature is on-scale or a difference in order to convert the units correctly (cf. https://cfconventions.org/cf-conventions/cf-conventions.html#temperature-units). K s - The phrase "integral_wrt_X_of_Y" means int Y dX. The data variable should have an axis for X specifying the limits of the integral as bounds. "wrt" means with respect to. Air temperature is the bulk temperature of the air, not the surface (skin) temperature. The air temperature excess is the air temperature minus the air temperature threshold, where only positive values are included in the integral. Its integral with respect to time is often called after its units of "degree-days". The air_temperature variable, which is the data variable of the integral should have a scalar coordinate variable or a size-one coordinate variable with the standard name of air_temperature_threshold, to indicate the threshold. + The phrase "integral_wrt_X_of_Y" means int Y dX. The data variable should have an axis for X specifying the limits of the integral as bounds. "wrt" means with respect to. Air temperature is the bulk temperature of the air, not the surface (skin) temperature. The air temperature excess is the air temperature minus the air temperature threshold, where only positive values are included in the integral. Its integral with respect to time is often called after its units of "degree-days". The air_temperature variable, which is the data variable of the integral should have a scalar coordinate variable or a size-one coordinate variable with the standard name of air_temperature_threshold, to indicate the threshold. It is strongly recommended that a variable with this standard name should have the attribute units_metadata="temperature: difference", meaning that it refers to temperature differences and implying that the origin of the temperature scale is irrelevant, because it is essential to know whether a temperature is on-scale or a difference in order to convert the units correctly (cf. https://cfconventions.org/cf-conventions/cf-conventions.html#temperature-units). @@ -7717,7 +7746,7 @@ K - "Land ice" means glaciers, ice-caps and ice-sheets resting on bedrock and also includes ice-shelves. The standard name land_ice_basal_temperature means the temperature of the land ice at its lower boundary. + "Land ice" means glaciers, ice-caps and ice-sheets resting on bedrock and also includes ice-shelves. The standard name land_ice_basal_temperature means the temperature of the land ice at its lower boundary. It is strongly recommended that a variable with this standard name should have a units_metadata attribute, with one of the values "on-scale" or "difference", whichever is appropriate for the data, because it is essential to know whether the temperature is on-scale (meaning relative to the origin of the scale indicated by the units) or refers to temperature differences (implying that the origin of the temperature scale is irrevelant), in order to convert the units correctly (cf. https://cfconventions.org/cf-conventions/cf-conventions.html#temperature-units). @@ -7857,7 +7886,7 @@ K - "Land ice" means glaciers, ice-caps and ice-sheets resting on bedrock and also includes ice-shelves. + "Land ice" means glaciers, ice-caps and ice-sheets resting on bedrock and also includes ice-shelves. It is strongly recommended that a variable with this standard name should have a units_metadata attribute, with one of the values "on-scale" or "difference", whichever is appropriate for the data, because it is essential to know whether the temperature is on-scale (meaning relative to the origin of the scale indicated by the units) or refers to temperature differences (implying that the origin of the temperature scale is irrevelant), in order to convert the units correctly (cf. https://cfconventions.org/cf-conventions/cf-conventions.html#temperature-units). @@ -7937,6 +7966,20 @@ "Content" indicates a quantity per unit area. + + 1 + + + Left singular vectors of the matrix representing the logarithmic scale remote sensing averaging kernels (Weber 2019; Schneider et al., 2022) of the methane mole fractions obtained by a remote sensing observation (fractional changes of methane in the retrieved atmosphere relative to the fractional changes of methane in the true atmosphere, Rodgers 2000; Keppens et al., 2015). + + + + 1 + + + Left singular vectors of the matrix representing the remote sensing averaging kernels (Weber 2019; Schneider et al., 2022) of the methane mole fractions obtained by a remote sensing observation (changes of methane in the retrieved atmosphere relative to the changes of methane in the true atmosphere, Rodgers 2000). + + J kg-1 @@ -12557,6 +12600,34 @@ Mole concentration means number of moles per unit volume, also called "molarity", and is used in the construction mole_concentration_of_X_in_Y, where X is a material constituent of Y. A chemical or biological species denoted by X may be described by a single term such as "nitrogen" or a phrase such as "nox_expressed_as_nitrogen". The phrase "expressed_as" is used in the construction A_expressed_as_B, where B is a chemical constituent of A. It means that the quantity indicated by the standard name is calculated solely with respect to the B contained in A, neglecting all other chemical constituents of A. Picophytoplankton are phytoplankton of less than 2 micrometers in size. Phytoplankton are algae that grow where there is sufficient light to support photosynthesis. + + mol m-3 + + + "Mole concentration" means the number of moles per unit volume, also called "molarity", and is used in the construction "mole_concentration_of_X_in_Y", where X is a material constituent of Y. A chemical or biological species denoted by X may be described by a single term such as "nitrogen" or a phrase such as "nox_expressed_as_nitrogen". "Dissolved inorganic carbon-13" is the sum of CO3_13C, HCO3_13C and H2CO3_13C. The subduction and subsequent transport of surface water carry into the interior ocean considerable quantities of dissolved inorganic carbon-13, which is entirely independent of biological activity (such as organic decomposition and oxidation) after the water leaves the sea surface. Such dissolved inorganic carbon-13 is termed “preformed” dissolved inorganic carbon-13 (Redfield,1942). + + + + mol m-3 + + + "Mole concentration" means the number of moles per unit volume, also called "molarity", and is used in the construction "mole_concentration_of_X_in_Y", where X is a material constituent of Y. A chemical or biological species denoted by X may be described by a single term such as "nitrogen" or a phrase such as "nox_expressed_as_nitrogen". "Dissolved inorganic carbon" is the sum of CO3, HCO3 and H2CO3. The subduction and subsequent transport of surface water carry into the interior ocean considerable quantities of dissolved inorganic carbon, which is entirely independent of biological activity (such as organic decomposition and oxidation) after the water leaves the sea surface. Such dissolved inorganic carbon is termed “preformed” dissolved inorganic carbon (Redfield,1942). + + + + mol m-3 + + + "Mole concentration" means the number of moles per unit volume, also called "molarity", and is used in the construction "mole_concentration_of_X_in_Y", where X is a material constituent of Y. A chemical or biological species denoted by X may be described by a single term such as "nitrogen" or a phrase such as "nox_expressed_as_nitrogen". "Dissolved inorganic phosphorus" means the sum of all inorganic phosphorus in solution (including phosphate, hydrogen phosphate, dihydrogen phosphate, and phosphoric acid). The subduction and subsequent transport of surface water carry into the interior ocean considerable quantities of nutrients, which are entirely independent of biological activity (such as organic decomposition and oxidation) after the water leaves the sea surface. Such nutrients are termed “preformed” nutrients (Redfield,1942). + + + + mol m-3 + + + "Mole concentration" means the number of moles per unit volume, also called "molarity", and is used in the construction "mole_concentration_of_X_in_Y", where X is a material constituent of Y. A chemical or biological species denoted by X may be described by a single term such as "nitrogen" or a phrase such as "nox_expressed_as_nitrogen". The subduction and subsequent transport of surface water carry into the interior ocean considerable quantities of dissolved oxygen, which are entirely independent of biological activity (such as organic decomposition and oxidation) after the water leaves the sea surface. Such dissolved oxygen is termed “preformed” dissolved oxygen (Redfield,1942). + + mol m-3 @@ -15313,7 +15384,7 @@ http://vocab.nerc.ac.uk/collection/P01/current/TPHSDSZZ/6/. degree_C - Perceived temperature (PT) is an equivalent air temperature of the actual thermal condition. It is the air temperature of a reference condition causing the same thermal perception in a human body considering air temperature, wind speed, humidity, solar and thermal radiation as well as clothing and activity level. It is not the perceived air temperature, that derives either from wind chill and heat index and has the standard_name apparent_air_temperature. + Perceived temperature (PT) is an equivalent air temperature of the actual thermal condition. It is the air temperature of a reference condition causing the same thermal perception in a human body considering air temperature, wind speed, humidity, solar and thermal radiation as well as clothing and activity level. It is not the perceived air temperature, that derives either from wind chill and heat index and has the standard_name apparent_air_temperature. It is strongly recommended that a variable with this standard name should have a units_metadata attribute, with one of the values "on-scale" or "difference", whichever is appropriate for the data, because it is essential to know whether the temperature is on-scale (meaning relative to the origin of the scale indicated by the units) or refers to temperature differences (implying that the origin of the temperature scale is irrevelant), in order to convert the units correctly (cf. https://cfconventions.org/cf-conventions/cf-conventions.html#temperature-units). @@ -15383,7 +15454,7 @@ http://vocab.nerc.ac.uk/collection/P01/current/TPHSDSZZ/6/. degree_C - Physiological equivalent temperature (PET) is an equivalent air temperature of the actual thermal condition. It is the air temperature of a reference condition without wind and solar radiation at which the heat budget of the human body is balanced with the same core and skin temperature. Note that PET here is not potential evapotranspiration. + Physiological equivalent temperature (PET) is an equivalent air temperature of the actual thermal condition. It is the air temperature of a reference condition without wind and solar radiation at which the heat budget of the human body is balanced with the same core and skin temperature. Note that PET here is not potential evapotranspiration. It is strongly recommended that a variable with this standard name should have a units_metadata attribute, with one of the values "on-scale" or "difference", whichever is appropriate for the data, because it is essential to know whether the temperature is on-scale (meaning relative to the origin of the scale indicated by the units) or refers to temperature differences (implying that the origin of the temperature scale is irrevelant), in order to convert the units correctly (cf. https://cfconventions.org/cf-conventions/cf-conventions.html#temperature-units). @@ -15803,7 +15874,7 @@ http://vocab.nerc.ac.uk/collection/P01/current/TPHSDSZZ/6/. K - "product_of_X_and_Y" means X*Y. "specific" means per unit mass. Air temperature is the bulk temperature of the air, not the surface (skin) temperature. Specific humidity is the mass fraction of water vapor in (moist) air. + The phrase "product_of_X_and_Y" means X*Y. "specific" means per unit mass. Air temperature is the bulk temperature of the air, not the surface (skin) temperature. Specific humidity is the mass fraction of water vapor in (moist) air. It is strongly recommended that a variable with this standard name should have a units_metadata attribute, with one of the values "on-scale" or "difference", whichever is appropriate for the data, because it is essential to know whether the temperature is on-scale (meaning relative to the origin of the scale indicated by the units) or refers to temperature differences (implying that the origin of the temperature scale is irrevelant), in order to convert the units correctly (cf. https://cfconventions.org/cf-conventions/cf-conventions.html#temperature-units). @@ -15817,14 +15888,14 @@ http://vocab.nerc.ac.uk/collection/P01/current/TPHSDSZZ/6/. K m s-1 - "product_of_X_and_Y" means X*Y. A velocity is a vector quantity. "Eastward" indicates a vector component which is positive when directed eastward (negative westward). + The phrase "product_of_X_and_Y" means X*Y. A velocity is a vector quantity. "Eastward" indicates a vector component which is positive when directed eastward (negative westward). It is strongly recommended that a variable with this standard name should have a units_metadata attribute, with one of the values "on-scale" or "difference", whichever is appropriate for the data, because it is essential to know whether the temperature is on-scale (meaning relative to the origin of the scale indicated by the units) or refers to temperature differences (implying that the origin of the temperature scale is irrevelant), in order to convert the units correctly (cf. https://cfconventions.org/cf-conventions/cf-conventions.html#temperature-units). K m s-1 - "product_of_X_and_Y" means X*Y. Air temperature is the bulk temperature of the air, not the surface (skin) temperature. "Eastward" indicates a vector component which is positive when directed eastward (negative westward). Wind is defined as a two-dimensional (horizontal) air velocity vector, with no vertical component. (Vertical motion in the atmosphere has the standard name upward_air_velocity.) + The phrase "product_of_X_and_Y" means X*Y. Air temperature is the bulk temperature of the air, not the surface (skin) temperature. "Eastward" indicates a vector component which is positive when directed eastward (negative westward). Wind is defined as a two-dimensional (horizontal) air velocity vector, with no vertical component. (Vertical motion in the atmosphere has the standard name upward_air_velocity.) It is strongly recommended that a variable with this standard name should have a units_metadata attribute, with one of the values "on-scale" or "difference", whichever is appropriate for the data, because it is essential to know whether the temperature is on-scale (meaning relative to the origin of the scale indicated by the units) or refers to temperature differences (implying that the origin of the temperature scale is irrevelant), in order to convert the units correctly (cf. https://cfconventions.org/cf-conventions/cf-conventions.html#temperature-units). @@ -15866,7 +15937,7 @@ http://vocab.nerc.ac.uk/collection/P01/current/TPHSDSZZ/6/. K Pa s-1 mpwapta - The phrase "product_of_X_and_Y" means X*Y. The phrase "tendency_of_X" means derivative of X with respect to time. The Lagrangian tendency of a quantity is its rate of change following the motion of the fluid, also called the "material derivative" or "convective derivative". The Lagrangian tendency of air pressure, often called "omega", plays the role of the upward component of air velocity when air pressure is being used as the vertical coordinate. If the vertical air velocity is upwards, it is negative when expressed as a tendency of air pressure; downwards is positive. Air pressure is the force per unit area which would be exerted when the moving gas molecules of which the air is composed strike a theoretical surface of any orientation. Air temperature is the bulk temperature of the air, not the surface (skin) temperature. + The phrase "product_of_X_and_Y" means X*Y. The phrase "tendency_of_X" means derivative of X with respect to time. The Lagrangian tendency of a quantity is its rate of change following the motion of the fluid, also called the "material derivative" or "convective derivative". The Lagrangian tendency of air pressure, often called "omega", plays the role of the upward component of air velocity when air pressure is being used as the vertical coordinate. If the vertical air velocity is upwards, it is negative when expressed as a tendency of air pressure; downwards is positive. Air pressure is the force per unit area which would be exerted when the moving gas molecules of which the air is composed strike a theoretical surface of any orientation. Air temperature is the bulk temperature of the air, not the surface (skin) temperature. It is strongly recommended that a variable with this standard name should have a units_metadata attribute, with one of the values "on-scale" or "difference", whichever is appropriate for the data, because it is essential to know whether the temperature is on-scale (meaning relative to the origin of the scale indicated by the units) or refers to temperature differences (implying that the origin of the temperature scale is irrevelant), in order to convert the units correctly (cf. https://cfconventions.org/cf-conventions/cf-conventions.html#temperature-units). @@ -15894,14 +15965,14 @@ http://vocab.nerc.ac.uk/collection/P01/current/TPHSDSZZ/6/. K m s-1 - "product_of_X_and_Y" means X*Y. A velocity is a vector quantity. "Northward" indicates a vector component which is positive when directed northward (negative southward). + The phrase "product_of_X_and_Y" means X*Y. A velocity is a vector quantity. "Northward" indicates a vector component which is positive when directed northward (negative southward). It is strongly recommended that a variable with this standard name should have a units_metadata attribute, with one of the values "on-scale" or "difference", whichever is appropriate for the data, because it is essential to know whether the temperature is on-scale (meaning relative to the origin of the scale indicated by the units) or refers to temperature differences (implying that the origin of the temperature scale is irrevelant), in order to convert the units correctly (cf. https://cfconventions.org/cf-conventions/cf-conventions.html#temperature-units). K m s-1 mpvta - "product_of_X_and_Y" means X*Y. Air temperature is the bulk temperature of the air, not the surface (skin) temperature. "Northward" indicates a vector component which is positive when directed northward (negative southward). Wind is defined as a two-dimensional (horizontal) air velocity vector, with no vertical component. (Vertical motion in the atmosphere has the standard name upward_air_velocity.) + The phrase "product_of_X_and_Y" means X*Y. Air temperature is the bulk temperature of the air, not the surface (skin) temperature. "Northward" indicates a vector component which is positive when directed northward (negative southward). Wind is defined as a two-dimensional (horizontal) air velocity vector, with no vertical component. (Vertical motion in the atmosphere has the standard name upward_air_velocity.) It is strongly recommended that a variable with this standard name should have a units_metadata attribute, with one of the values "on-scale" or "difference", whichever is appropriate for the data, because it is essential to know whether the temperature is on-scale (meaning relative to the origin of the scale indicated by the units) or refers to temperature differences (implying that the origin of the temperature scale is irrevelant), in order to convert the units correctly (cf. https://cfconventions.org/cf-conventions/cf-conventions.html#temperature-units). @@ -15936,7 +16007,7 @@ http://vocab.nerc.ac.uk/collection/P01/current/TPHSDSZZ/6/. K m s-1 - "product_of_X_and_Y" means X*Y. Air temperature is the bulk temperature of the air, not the surface (skin) temperature. A velocity is a vector quantity. "Upward" indicates a vector component which is positive when directed upward (negative downward). Upward air velocity is the vertical component of the 3D air velocity vector. + The phrase "product_of_X_and_Y" means X*Y. Air temperature is the bulk temperature of the air, not the surface (skin) temperature. A velocity is a vector quantity. "Upward" indicates a vector component which is positive when directed upward (negative downward). Upward air velocity is the vertical component of the 3D air velocity vector. It is strongly recommended that a variable with this standard name should have a units_metadata attribute, with one of the values "on-scale" or "difference", whichever is appropriate for the data, because it is essential to know whether the temperature is on-scale (meaning relative to the origin of the scale indicated by the units) or refers to temperature differences (implying that the origin of the temperature scale is irrevelant), in order to convert the units correctly (cf. https://cfconventions.org/cf-conventions/cf-conventions.html#temperature-units). @@ -18575,7 +18646,7 @@ http://vocab.nerc.ac.uk/collection/P01/current/TPHSDSZZ/6/. s - The quantity with standard name radio_signal_roundtrip_travel_time_in_air is the time taken for an electromagnetic signal to propagate from an emitting instrument such as a radar or lidar to a reflecting volume and back again. The signal returned to the instrument is the sum of all scattering from a given volume of air regardless of mechanism (examples are scattering by aerosols, hydrometeors and refractive index irregularities, or whatever else the instrument detects). + Time it takes for a radio wave, that was transmitted by an instrument to propagate through the air to the volume of air where it is scattered and return back to an instrument. The "instrument" (examples are radar and lidar) is the device used to make the observation. The "scatterers" are what causes the transmitted signal to be returned to the instrument (examples are aerosols, hydrometeors and refractive index irregularities in the air). A standard name referring to time taken for a radio signal to propagate from the emitting instrument to a scattering volume and back to an instrument. @@ -18620,6 +18691,20 @@ http://vocab.nerc.ac.uk/collection/P01/current/TPHSDSZZ/6/. + + 1 + + + Rank of the matrix representing the logarithmic scale remote sensing averaging kernels (Weber 2019; Schneider et al., 2022) of the methane mole fractions obtained by a remote sensing observation (fractional changes of methane in the retrieved atmosphere relative to the fractional changes of methane in the true atmosphere, Rodgers 2000; Keppens et al., 2015). + + + + 1 + + + Rank the matrix representing the remote sensing averaging kernels (Weber 2019; Schneider et al., 2022) of the methane mole fractions obtained by a remote sensing observation (changes of methane in the retrieved atmosphere relative to the changes of methane in the true atmosphere, Rodgers 2000). + + 1 @@ -18645,7 +18730,7 @@ http://vocab.nerc.ac.uk/collection/P01/current/TPHSDSZZ/6/. K s-1 - The quantity with standard name ratio_of_sea_water_potential_temperature_anomaly_to_relaxation_timescale is a correction term applied to modelled sea water potential temperature. The term is estimated as the deviation of model local sea water potential temperature from an observation-based climatology (e.g. World Ocean Database) weighted by a user-specified relaxation coefficient in s-1 (1/(relaxation timescale)). Potential temperature is the temperature a parcel of air or sea water would have if moved adiabatically to sea level pressure. The phrase "ratio_of_X_to_Y" means X/Y. The term "anomaly" means difference from climatology. + The quantity with standard name ratio_of_sea_water_potential_temperature_anomaly_to_relaxation_timescale is a correction term applied to modelled sea water potential temperature. The term is estimated as the deviation of model local sea water potential temperature from an observation-based climatology (e.g. World Ocean Database) weighted by a user-specified relaxation coefficient in s-1 (1/(relaxation timescale)). Potential temperature is the temperature a parcel of air or sea water would have if moved adiabatically to sea level pressure. The phrase "ratio_of_X_to_Y" means X/Y. The term "anomaly" means difference from climatology. It is strongly recommended that a variable with this standard name should have the attribute units_metadata="temperature: difference", meaning that it refers to temperature differences and implying that the origin of the temperature scale is irrelevant, because it is essential to know whether a temperature is on-scale or a difference in order to convert the units correctly (cf. https://cfconventions.org/cf-conventions/cf-conventions.html#temperature-units). @@ -18687,7 +18772,7 @@ http://vocab.nerc.ac.uk/collection/P01/current/TPHSDSZZ/6/. W - The quantity with standard name received_power_of_radio_wave_in_air_scattered_by_air refers to the received power of the signal at an instrument such as a radar or lidar. The signal returned to the instrument is the sum of all scattering from a given volume of air regardless of mechanism (examples are scattering by aerosols, hydrometeors and refractive index irregularities, or whatever else the instrument detects). + Power of a radio wave, that was transmitted by an instrument and propagates in the air where it's scattered by the air due to which its properties change, and it is received again by an instrument. The "instrument" (examples are radar and lidar) is the device used to make the observation. The "scatterers" are what causes the transmitted signal to be returned to the instrument (examples are aerosols, hydrometeors and refractive index irregularities in the air). A standard name referring to the received power of the signal at the instrument. @@ -18704,6 +18789,13 @@ http://vocab.nerc.ac.uk/collection/P01/current/TPHSDSZZ/6/. The period of time over which a parameter has been summarised (usually by averaging) in order to provide a reference (baseline) against which data has been compared. When a coordinate, scalar coordinate, or auxiliary coordinate variable with this standard name has bounds, then the bounds specify the beginning and end of the time period over which the reference was determined. If the reference represents an instant in time, rather than a period, then bounds may be omitted. It is not the time for which the actual measurements are valid; the standard name of time should be used for that. + + mol/mol + + + This ozone field acts as a reference ozone field in a diagnostic call to the model's radiation scheme. Mole fraction is used in the construction mole_fraction_of_X_in_Y, where X is a material constituent of Y. + + Pa @@ -18753,6 +18845,20 @@ http://vocab.nerc.ac.uk/collection/P01/current/TPHSDSZZ/6/. relative_sensor_azimuth_angle is the difference between the viewing geometries from two different sensors over the same observation target. It is the difference between the values of two quantities with standard name sensor_azimuth_angle. There is no standardized sign convention for relative_sensor_azimuth_angle. "Observation target" means a location on the Earth defined by the sensor performing the observations. A standard name also exists for relative_platform_azimuth_angle, where "platform" refers to the vehicle from which observations are made e.g. aeroplane, ship, or satellite. For some viewing geometries the sensor and the platform cannot be assumed to be close enough to neglect the difference in calculated azimuth angle. + + 1 + + + Logarithmic scale averaging kernels of the methane mole fractions obtained by a remote sensing observation (Rodgers, 2020). These kernels are also called fractional averaging kernels (Keppens et al., 2015) They represent the fractional changes of methane in the retrieved atmosphere relative to the fractional changes of methane in the true atmosphere. + + + + 1 + + + Averaging kernels of the methane mole fractions obtained by a remote sensing observation (changes of methane in the retrieved atmosphere relative to the changes of methane in the true atmosphere, Rodgers 2000). + + 1 @@ -18760,6 +18866,20 @@ http://vocab.nerc.ac.uk/collection/P01/current/TPHSDSZZ/6/. Richardson number is a measure of dynamic stability and can be used to diagnose the existence of turbulent flow. It is defined as the ratio of the buoyant suppression of turbulence (i.e. how statically stable or unstable the conditions are) to the kinetic energy available to generate turbulence in a shear flow. + + 1 + + + Right singular vectors of the matrix representing the logarithmic scale remote sensing averaging kernels (Weber 2019; Schneider et al., 2022) of the methane mole fractions obtained by a remote sensing observation (changes of methane in the retrieved atmosphere relative to the changes of methane in the true atmosphere, Rodgers 2000; Keppens et al., 2015). + + + + 1 + + + Right singular vectors of the matrix representing the remote sensing averaging kernels (Weber 2019; Schneider et al., 2022) of the methane mole fractions obtained by a remote sensing observation (changes of methane in the retrieved atmosphere relative to the changes of methane in the true atmosphere, Rodgers 2000). + + m @@ -18967,7 +19087,7 @@ http://vocab.nerc.ac.uk/collection/P01/current/TPHSDSZZ/6/. K - "Sea ice" means all ice floating in the sea which has formed from freezing sea water, rather than by other processes such as calving of land ice to form icebergs. The standard name sea_ice_basal_temperature means the temperature of the sea ice at its lower boundary. + "Sea ice" means all ice floating in the sea which has formed from freezing sea water, rather than by other processes such as calving of land ice to form icebergs. The standard name sea_ice_basal_temperature means the temperature of the sea ice at its lower boundary. It is strongly recommended that a variable with this standard name should have a units_metadata attribute, with one of the values "on-scale" or "difference", whichever is appropriate for the data, because it is essential to know whether the temperature is on-scale (meaning relative to the origin of the scale indicated by the units) or refers to temperature differences (implying that the origin of the temperature scale is irrevelant), in order to convert the units correctly (cf. https://cfconventions.org/cf-conventions/cf-conventions.html#temperature-units). @@ -19044,14 +19164,14 @@ http://vocab.nerc.ac.uk/collection/P01/current/TPHSDSZZ/6/. K - The surface temperature is the (skin) temperature at the interface, not the bulk temperature of the medium above or below. "Sea ice surface temperature" is the temperature that exists at the interface of sea ice and an overlying medium which may be air or snow. In areas of snow covered sea ice, sea_ice_surface_temperature is not the same as the quantity with standard name surface_temperature. "Sea ice" means all ice floating in the sea which has formed from freezing sea water, rather than by other processes such as calving of land ice to form icebergs. + The surface temperature is the (skin) temperature at the interface, not the bulk temperature of the medium above or below. "Sea ice surface temperature" is the temperature that exists at the interface of sea ice and an overlying medium which may be air or snow. In areas of snow covered sea ice, sea_ice_surface_temperature is not the same as the quantity with standard name surface_temperature. "Sea ice" means all ice floating in the sea which has formed from freezing sea water, rather than by other processes such as calving of land ice to form icebergs. It is strongly recommended that a variable with this standard name should have a units_metadata attribute, with one of the values "on-scale" or "difference", whichever is appropriate for the data, because it is essential to know whether the temperature is on-scale (meaning relative to the origin of the scale indicated by the units) or refers to temperature differences (implying that the origin of the temperature scale is irrevelant), in order to convert the units correctly (cf. https://cfconventions.org/cf-conventions/cf-conventions.html#temperature-units). K - Sea ice temperature is the bulk temperature of the sea ice, not the surface (skin) temperature. "Sea ice" means all ice floating in the sea which has formed from freezing sea water, rather than by other processes such as calving of land ice to form icebergs. + Sea ice temperature is the bulk temperature of the sea ice, not the surface (skin) temperature. "Sea ice" means all ice floating in the sea which has formed from freezing sea water, rather than by other processes such as calving of land ice to form icebergs. It is strongly recommended that a variable with this standard name should have a units_metadata attribute, with one of the values "on-scale" or "difference", whichever is appropriate for the data, because it is essential to know whether the temperature is on-scale (meaning relative to the origin of the scale indicated by the units) or refers to temperature differences (implying that the origin of the temperature scale is irrevelant), in order to convert the units correctly (cf. https://cfconventions.org/cf-conventions/cf-conventions.html#temperature-units). @@ -19191,7 +19311,7 @@ http://vocab.nerc.ac.uk/collection/P01/current/TPHSDSZZ/6/. K - The sea surface foundation temperature is the water temperature that is not influenced by a thermally stratified layer of diurnal temperature variability (either by daytime warming or nocturnal cooling). The foundation temperature is named to indicate that it is the temperature from which the growth of the diurnal thermocline develops each day, noting that on some occasions with a deep mixed layer there is no clear foundation temperature in the surface layer. In general, sea surface foundation temperature will be similar to a night time minimum or pre-dawn value at depths of between approximately 1 and 5 meters. In the absence of any diurnal signal, the foundation temperature is considered equivalent to the quantity with standard name sea_surface_subskin_temperature. The sea surface foundation temperature defines a level in the upper water column that varies in depth, space, and time depending on the local balance between thermal stratification and turbulent energy and is expected to change slowly over the course of a day. If possible, a data variable with the standard name sea_surface_foundation_temperature should be used with a scalar vertical coordinate variable to specify the depth of the foundation level. Sea surface foundation temperature is measured at the base of the diurnal thermocline or as close to the water surface as possible in the absence of thermal stratification. Only in situ contact thermometry is able to measure the sea surface foundation temperature. Analysis procedures must be used to estimate sea surface foundation temperature value from radiometric satellite measurements of the quantities with standard names sea_surface_skin_temperature and sea_surface_subskin_temperature. Sea surface foundation temperature provides a connection with the historical concept of a "bulk" sea surface temperature considered representative of the oceanic mixed layer temperature that is typically represented by any sea temperature measurement within the upper ocean over a depth range of 1 to approximately 20 meters. The general term, "bulk" sea surface temperature, has the standard name sea_surface_temperature with no associated vertical coordinate axis. Sea surface foundation temperature provides a more precise, well defined quantity than "bulk" sea surface temperature and, consequently, is more representative of the mixed layer temperature. The temperature of sea water at a particular depth (other than the foundation level) should be reported using the standard name sea_water_temperature and, wherever possible, supplying a vertical coordinate axis or scalar coordinate variable. + The sea surface foundation temperature is the water temperature that is not influenced by a thermally stratified layer of diurnal temperature variability (either by daytime warming or nocturnal cooling). The foundation temperature is named to indicate that it is the temperature from which the growth of the diurnal thermocline develops each day, noting that on some occasions with a deep mixed layer there is no clear foundation temperature in the surface layer. In general, sea surface foundation temperature will be similar to a night time minimum or pre-dawn value at depths of between approximately 1 and 5 meters. In the absence of any diurnal signal, the foundation temperature is considered equivalent to the quantity with standard name sea_surface_subskin_temperature. The sea surface foundation temperature defines a level in the upper water column that varies in depth, space, and time depending on the local balance between thermal stratification and turbulent energy and is expected to change slowly over the course of a day. If possible, a data variable with the standard name sea_surface_foundation_temperature should be used with a scalar vertical coordinate variable to specify the depth of the foundation level. Sea surface foundation temperature is measured at the base of the diurnal thermocline or as close to the water surface as possible in the absence of thermal stratification. Only in situ contact thermometry is able to measure the sea surface foundation temperature. Analysis procedures must be used to estimate sea surface foundation temperature value from radiometric satellite measurements of the quantities with standard names sea_surface_skin_temperature and sea_surface_subskin_temperature. Sea surface foundation temperature provides a connection with the historical concept of a "bulk" sea surface temperature considered representative of the oceanic mixed layer temperature that is typically represented by any sea temperature measurement within the upper ocean over a depth range of 1 to approximately 20 meters. The general term, "bulk" sea surface temperature, has the standard name sea_surface_temperature with no associated vertical coordinate axis. Sea surface foundation temperature provides a more precise, well defined quantity than "bulk" sea surface temperature and, consequently, is more representative of the mixed layer temperature. The temperature of sea water at a particular depth (other than the foundation level) should be reported using the standard name sea_water_temperature and, wherever possible, supplying a vertical coordinate axis or scalar coordinate variable. It is strongly recommended that a variable with this standard name should have a units_metadata attribute, with one of the values "on-scale" or "difference", whichever is appropriate for the data, because it is essential to know whether the temperature is on-scale (meaning relative to the origin of the scale indicated by the units) or refers to temperature differences (implying that the origin of the temperature scale is irrevelant), in order to convert the units correctly (cf. https://cfconventions.org/cf-conventions/cf-conventions.html#temperature-units). @@ -19415,14 +19535,14 @@ http://vocab.nerc.ac.uk/collection/P01/current/TPHSDSZZ/6/. K - The sea surface skin temperature is the temperature measured by an infrared radiometer typically operating at wavelengths in the range 3.7 - 12 micrometers. It represents the temperature within the conductive diffusion-dominated sub-layer at a depth of approximately 10 - 20 micrometers below the air-sea interface. Measurements of this quantity are subject to a large potential diurnal cycle including cool skin layer effects (especially at night under clear skies and low wind speed conditions) and warm layer effects in the daytime. + The sea surface skin temperature is the temperature measured by an infrared radiometer typically operating at wavelengths in the range 3.7 - 12 micrometers. It represents the temperature within the conductive diffusion-dominated sub-layer at a depth of approximately 10 - 20 micrometers below the air-sea interface. Measurements of this quantity are subject to a large potential diurnal cycle including cool skin layer effects (especially at night under clear skies and low wind speed conditions) and warm layer effects in the daytime. It is strongly recommended that a variable with this standard name should have a units_metadata attribute, with one of the values "on-scale" or "difference", whichever is appropriate for the data, because it is essential to know whether the temperature is on-scale (meaning relative to the origin of the scale indicated by the units) or refers to temperature differences (implying that the origin of the temperature scale is irrevelant), in order to convert the units correctly (cf. https://cfconventions.org/cf-conventions/cf-conventions.html#temperature-units). K - The sea surface subskin temperature is the temperature at the base of the conductive laminar sub-layer of the ocean surface, that is, at a depth of approximately 1 - 1.5 millimeters below the air-sea interface. For practical purposes, this quantity can be well approximated to the measurement of surface temperature by a microwave radiometer operating in the 6 - 11 gigahertz frequency range, but the relationship is neither direct nor invariant to changing physical conditions or to the specific geometry of the microwave measurements. Measurements of this quantity are subject to a large potential diurnal cycle due to thermal stratification of the upper ocean layer in low wind speed high solar irradiance conditions. + The sea surface subskin temperature is the temperature at the base of the conductive laminar sub-layer of the ocean surface, that is, at a depth of approximately 1 - 1.5 millimeters below the air-sea interface. For practical purposes, this quantity can be well approximated to the measurement of surface temperature by a microwave radiometer operating in the 6 - 11 gigahertz frequency range, but the relationship is neither direct nor invariant to changing physical conditions or to the specific geometry of the microwave measurements. Measurements of this quantity are subject to a large potential diurnal cycle due to thermal stratification of the upper ocean layer in low wind speed high solar irradiance conditions. It is strongly recommended that a variable with this standard name should have a units_metadata attribute, with one of the values "on-scale" or "difference", whichever is appropriate for the data, because it is essential to know whether the temperature is on-scale (meaning relative to the origin of the scale indicated by the units) or refers to temperature differences (implying that the origin of the temperature scale is irrevelant), in order to convert the units correctly (cf. https://cfconventions.org/cf-conventions/cf-conventions.html#temperature-units). @@ -19506,7 +19626,7 @@ http://vocab.nerc.ac.uk/collection/P01/current/TPHSDSZZ/6/. K - Sea surface temperature is usually abbreviated as "SST". It is the temperature of sea water near the surface (including the part under sea-ice, if any). More specific terms, namely sea_surface_skin_temperature, sea_surface_subskin_temperature, and surface_temperature are available for the skin, subskin, and interface temperature. respectively. For the temperature of sea water at a particular depth or layer, a data variable of sea_water_temperature with a vertical coordinate axis should be used. + Sea surface temperature is usually abbreviated as "SST". It is the temperature of sea water near the surface (including the part under sea-ice, if any). More specific terms, namely sea_surface_skin_temperature, sea_surface_subskin_temperature, and surface_temperature are available for the skin, subskin, and interface temperature. respectively. For the temperature of sea water at a particular depth or layer, a data variable of sea_water_temperature with a vertical coordinate axis should be used. It is strongly recommended that a variable with this standard name should have a units_metadata attribute, with one of the values "on-scale" or "difference", whichever is appropriate for the data, because it is essential to know whether the temperature is on-scale (meaning relative to the origin of the scale indicated by the units) or refers to temperature differences (implying that the origin of the temperature scale is irrevelant), in order to convert the units correctly (cf. https://cfconventions.org/cf-conventions/cf-conventions.html#temperature-units). @@ -19586,6 +19706,13 @@ http://vocab.nerc.ac.uk/collection/P01/current/TPHSDSZZ/6/. The wave directional spectrum can be written as a five dimensional function S(t,x,y,f,theta) where t is time, x and y are horizontal coordinates (such as longitude and latitude), f is frequency and theta is direction. S has the standard name sea_surface_wave_directional_variance_spectral_density. S can be integrated over direction to give S1= integral(S dtheta) and this quantity has the standard name sea_surface_wave_variance_spectral_density. The quantity with standard name sea_surface_wave_energy_at_variance_spectral_density_maximum, sometimes called peak wave energy, is the maximum value of the variance spectral density (max(S1)). + + W m-1 + + + Wave energy flux, or wave power, is the average rate of transfer of wave energy through a vertical plane of unit width perpendicular to the direction of wave propagation. It should be understood as omnidirectional, or as the sum of all wave power components regardless of direction. In deep water conditions, the wave energy flux can be obtained with the water density, the wave significant height and the energy period. + + s-1 @@ -19642,6 +19769,13 @@ http://vocab.nerc.ac.uk/collection/P01/current/TPHSDSZZ/6/. The trough is the lowest point of a wave. Trough depth is the vertical distance between the trough and the calm sea surface. Maximum trough depth is the maximum value measured during the observation period. + + degrees + + + The wave direction in each frequency band, calculated from the first-order components of the wave directional spectrum. The full directional wave spectrum is described as a Fourier series: S = a0/2 + a1cos(theta) + b1sin(theta) + a2cos(2theta) + b2sin(2theta). The Fourier coefficients a1, b1, a2, & b2 can be converted to polar coordinates as follows: R1 = (SQRT(a1a1+b1b1))/a0, R2 = (SQRT(a2a2+b2b2))/a0, ALPHA1 = 270.0-ARCTAN(b1,a1), ALPHA2 = 270.0-(0.5*ARCTAN(b2,a2)+{0 or 180, whichever minimizes the difference between ALPHA1 and ALPHA2}). ALPHA1 is the mean wave direction, which is determined from the first-order Fourier coefficients. This spectral parameter is a separate quantity from the bulk parameter (MWDIR), which has the standard name sea_surface_wave_from_direction_at_variance_spectral_density_maximum. The phrase "from_direction" is used in the construction X_from_direction and indicates the direction from which the velocity vector of X is coming. The direction is a bearing in the usual geographical sense, measured positive clockwise from due north. + + m @@ -19740,6 +19874,13 @@ http://vocab.nerc.ac.uk/collection/P01/current/TPHSDSZZ/6/. Wave period of the highest wave is the period determined from wave crests corresponding to the greatest vertical distance above mean level during the observation period. A period is an interval of time, or the time-period of an oscillation. Wave period is the interval of time between repeated features on the waveform such as crests, troughs or upward passes through the mean level. + + degrees + + + The wave direction in each frequency band, calculated from the second-order components of the wave directional spectrum. Since there is an ambiguity of 180 degrees in the calculation of Alpha2 (i.e. 90 degrees and 270 degrees result in equivalent spectra), the value closer to Alpha1 is selected. The full directional wave spectrum is described as a Fourier series: S = a0/2 + a1cos(theta) + b1sin(theta) + a2cos(2theta) + b2sin(2theta). The Fourier coefficients a1, b1, a2, & b2 can be converted to polar coordinates as follows: R1 = (SQRT(a1a1+b1b1))/a0, R2 = (SQRT(a2a2+b2b2))/a0, ALPHA1 = 270.0-ARCTAN(b1,a1), ALPHA2 = 270.0-(0.5*ARCTAN(b2,a2)+{0 or 180, whichever minimizes the difference between ALPHA1 and ALPHA2}). ALPHA2 is the principal wave direction, which is determined from the second-order Fourier coefficients. This spectral parameter is a separate quantity from the bulk parameter (MWDIR), which has the standard name sea_surface_wave_from_direction_at_variance_spectral_density_maximum. The phrase "from_direction" is used in the construction X_from_direction and indicates the direction from which the velocity vector of X is coming. The direction is a bearing in the usual geographical sense, measured positive clockwise from due north. + + m 100 @@ -19926,14 +20067,14 @@ http://vocab.nerc.ac.uk/collection/P01/current/TPHSDSZZ/6/. degree_C - The quantity with standard name sea_water_added_conservative_temperature is a passive tracer in an ocean model whose surface flux does not come from the atmosphere but is imposed externally upon the simulated climate system. The surface flux is expressed as a heat flux and converted to a passive tracer increment as if it were a heat flux being added to conservative temperature. The passive tracer is transported within the ocean as if it were conservative temperature. The passive tracer is zero in the control climate of the model. The passive tracer records added heat, as described for the CMIP6 FAFMIP experiment (doi:10.5194/gmd-9-3993-2016), following earlier ideas. Conservative Temperature is defined as part of the Thermodynamic Equation of Seawater 2010 (TEOS-10) which was adopted in 2010 by the International Oceanographic Commission (IOC). Conservative Temperature is specific potential enthalpy (which has the standard name sea_water_specific_potential_enthalpy) divided by a fixed value of the specific heat capacity of sea water, namely cp_0 = 3991.86795711963 J kg-1 K-1. Conservative Temperature is a more accurate measure of the "heat content" of sea water, by a factor of one hundred, than is potential temperature. Because of this, it can be regarded as being proportional to the heat content of sea water per unit mass. Reference: www.teos-10.org; McDougall, 2003 doi: 10.1175/1520-0485(2003)033<0945:PEACOV>2.0.CO;2. + The quantity with standard name sea_water_added_conservative_temperature is a passive tracer in an ocean model whose surface flux does not come from the atmosphere but is imposed externally upon the simulated climate system. The surface flux is expressed as a heat flux and converted to a passive tracer increment as if it were a heat flux being added to conservative temperature. The passive tracer is transported within the ocean as if it were conservative temperature. The passive tracer is zero in the control climate of the model. The passive tracer records added heat, as described for the CMIP6 FAFMIP experiment (doi:10.5194/gmd-9-3993-2016), following earlier ideas. Conservative Temperature is defined as part of the Thermodynamic Equation of Seawater 2010 (TEOS-10) which was adopted in 2010 by the International Oceanographic Commission (IOC). Conservative Temperature is specific potential enthalpy (which has the standard name sea_water_specific_potential_enthalpy) divided by a fixed value of the specific heat capacity of sea water, namely cp_0 = 3991.86795711963 J kg-1 K-1. Conservative Temperature is a more accurate measure of the "heat content" of sea water, by a factor of one hundred, than is potential temperature. Because of this, it can be regarded as being proportional to the heat content of sea water per unit mass. Reference: www.teos-10.org; McDougall, 2003 doi: 10.1175/1520-0485(2003)033<0945:PEACOV>2.0.CO;2. It is strongly recommended that a variable with this standard name should have the attribute units_metadata="temperature: difference", meaning that it refers to temperature differences and implying that the origin of the temperature scale is irrelevant, because it is essential to know whether a temperature is on-scale or a difference in order to convert the units correctly (cf. https://cfconventions.org/cf-conventions/cf-conventions.html#temperature-units). degree_C - The quantity with standard name sea_water_added_potential_temperature is a passive tracer in an ocean model whose surface flux does not come from the atmosphere but is imposed externally upon the simulated climate system. The surface flux is expressed as a heat flux and converted to a passive tracer increment as if it were a heat flux being added to potential temperature. The passive tracer is transported within the ocean as if it were potential temperature. The passive tracer is zero in the control climate of the model. The passive tracer records added heat, as described for the CMIP6 FAFMIP experiment (doi:10.5194/gmd-9-3993-2016), following earlier ideas. Potential temperature is the temperature a parcel of air or sea water would have if moved adiabatically to sea level pressure. + The quantity with standard name sea_water_added_potential_temperature is a passive tracer in an ocean model whose surface flux does not come from the atmosphere but is imposed externally upon the simulated climate system. The surface flux is expressed as a heat flux and converted to a passive tracer increment as if it were a heat flux being added to potential temperature. The passive tracer is transported within the ocean as if it were potential temperature. The passive tracer is zero in the control climate of the model. The passive tracer records added heat, as described for the CMIP6 FAFMIP experiment (doi:10.5194/gmd-9-3993-2016), following earlier ideas. Potential temperature is the temperature a parcel of air or sea water would have if moved adiabatically to sea level pressure. It is strongly recommended that a variable with this standard name should have the attribute units_metadata="temperature: difference", meaning that it refers to temperature differences and implying that the origin of the temperature scale is irrelevant, because it is essential to know whether a temperature is on-scale or a difference in order to convert the units correctly (cf. https://cfconventions.org/cf-conventions/cf-conventions.html#temperature-units). @@ -19968,7 +20109,7 @@ http://vocab.nerc.ac.uk/collection/P01/current/TPHSDSZZ/6/. K - Conservative Temperature is defined as part of the Thermodynamic Equation of Seawater 2010 (TEOS-10) which was adopted in 2010 by the International Oceanographic Commission (IOC). Conservative Temperature is specific potential enthalpy (which has the standard name sea_water_specific_potential_enthalpy) divided by a fixed value of the specific heat capacity of sea water, namely cp_0 = 3991.86795711963 J kg-1 K-1. Conservative Temperature is a more accurate measure of the "heat content" of sea water, by a factor of one hundred, than is potential temperature. Because of this, it can be regarded as being proportional to the heat content of sea water per unit mass. Reference: www.teos-10.org; McDougall, 2003 doi: 10.1175/1520-0485(2003)033<0945:PEACOV>2.0.CO;2. + Conservative Temperature is defined as part of the Thermodynamic Equation of Seawater 2010 (TEOS-10) which was adopted in 2010 by the International Oceanographic Commission (IOC). Conservative Temperature is specific potential enthalpy (which has the standard name sea_water_specific_potential_enthalpy) divided by a fixed value of the specific heat capacity of sea water, namely cp_0 = 3991.86795711963 J kg-1 K-1. Conservative Temperature is a more accurate measure of the "heat content" of sea water, by a factor of one hundred, than is potential temperature. Because of this, it can be regarded as being proportional to the heat content of sea water per unit mass. Reference: www.teos-10.org; McDougall, 2003 doi: 10.1175/1520-0485(2003)033<0945:PEACOV>2.0.CO;2. It is strongly recommended that a variable with this standard name should have a units_metadata attribute, with one of the values "on-scale" or "difference", whichever is appropriate for the data, because it is essential to know whether the temperature is on-scale (meaning relative to the origin of the scale indicated by the units) or refers to temperature differences (implying that the origin of the temperature scale is irrevelant), in order to convert the units correctly (cf. https://cfconventions.org/cf-conventions/cf-conventions.html#temperature-units). @@ -20066,14 +20207,14 @@ http://vocab.nerc.ac.uk/collection/P01/current/TPHSDSZZ/6/. K - Sea water potential temperature is the temperature a parcel of sea water would have if moved adiabatically to sea level pressure. + Sea water potential temperature is the temperature a parcel of sea water would have if moved adiabatically to sea level pressure. It is strongly recommended that a variable with this standard name should have a units_metadata attribute, with one of the values "on-scale" or "difference", whichever is appropriate for the data, because it is essential to know whether the temperature is on-scale (meaning relative to the origin of the scale indicated by the units) or refers to temperature differences (implying that the origin of the temperature scale is irrevelant), in order to convert the units correctly (cf. https://cfconventions.org/cf-conventions/cf-conventions.html#temperature-units). K - Potential temperature is the temperature a parcel of air or sea water would have if moved adiabatically to sea level pressure. The potential temperature at the sea floor is that adjacent to the ocean bottom, which would be the deepest grid cell in an ocean model and within the benthic boundary layer for measurements. + Potential temperature is the temperature a parcel of air or sea water would have if moved adiabatically to sea level pressure. The potential temperature at the sea floor is that adjacent to the ocean bottom, which would be the deepest grid cell in an ocean model and within the benthic boundary layer for measurements. It is strongly recommended that a variable with this standard name should have a units_metadata attribute, with one of the values "on-scale" or "difference", whichever is appropriate for the data, because it is essential to know whether the temperature is on-scale (meaning relative to the origin of the scale indicated by the units) or refers to temperature differences (implying that the origin of the temperature scale is irrevelant), in order to convert the units correctly (cf. https://cfconventions.org/cf-conventions/cf-conventions.html#temperature-units). @@ -20097,6 +20238,13 @@ http://vocab.nerc.ac.uk/collection/P01/current/TPHSDSZZ/6/. The practical salinity at the sea floor is that adjacent to the ocean bottom, which would be the deepest grid cell in an ocean model and within the benthic boundary layer for measurements. Practical Salinity, S_P, is a determination of the salinity of sea water, based on its electrical conductance. The measured conductance, corrected for temperature and pressure, is compared to the conductance of a standard potassium chloride solution, producing a value on the Practical Salinity Scale of 1978 (PSS-78). This name should not be used to describe salinity observations made before 1978, or ones not based on conductance measurements. Conversion of Practical Salinity to other precisely defined salinity measures should use the appropriate formulas specified by TEOS-10. Salinity quantities that do not match any of the precise definitions should be given the more general standard name of sea_water_salinity_at_sea_floor. Reference: www.teos-10.org; Lewis, 1980 doi:10.1109/JOE.1980.1145448. + + mol m-3 + + + "Mole concentration" means the number of moles per unit volume, also called "molarity", and is used in the construction "mole_concentration_of_X_in_Y", where X is a material constituent of Y. A chemical or biological species denoted by X may be described by a single term such as "nitrogen" or a phrase such as "nox_expressed_as_nitrogen". "Alkalinity" refers to total alkalinity equivalent concentration, including carbonate, borate, phosphorus, silicon, and nitrogen components. The subduction and subsequent transport of surface water carry into the interior ocean considerable quantities of alkalinity, which is entirely independent of biological activity (such as organic decomposition and oxidation) after the water leaves the sea surface. Such alkalinity is termed “preformed” alkalinity (Redfield,1942). + + g kg-1 @@ -20136,14 +20284,14 @@ http://vocab.nerc.ac.uk/collection/P01/current/TPHSDSZZ/6/. degree_C - The quantity with standard name sea_water_redistributed_conservative_temperature is a passive tracer in an ocean model which is subject to an externally imposed perturbative surface heat flux. The passive tracer is initialised to the conservative temperature in the control climate before the perturbation is imposed. Its surface flux is the heat flux from the atmosphere, not including the imposed perturbation, and is converted to a passive tracer increment as if it were being added to conservative temperature. The passive tracer is transported within the ocean as if it were conservative temperature. The passive tracer records redistributed heat, as described for the CMIP6 FAFMIP experiment (doi:10.5194/gmd-9-3993-2016), following earlier ideas. Conservative Temperature is defined as part of the Thermodynamic Equation of Seawater 2010 (TEOS-10) which was adopted in 2010 by the International Oceanographic Commission (IOC). Conservative Temperature is specific potential enthalpy (which has the standard name sea_water_specific_potential_enthalpy) divided by a fixed value of the specific heat capacity of sea water, namely cp_0 = 3991.86795711963 J kg-1 K-1. Conservative Temperature is a more accurate measure of the "heat content" of sea water, by a factor of one hundred, than is potential temperature. Because of this, it can be regarded as being proportional to the heat content of sea water per unit mass. Reference: www.teos-10.org; McDougall, 2003 doi: 10.1175/1520-0485(2003)033<0945:PEACOV>2.0.CO;2. + The quantity with standard name sea_water_redistributed_conservative_temperature is a passive tracer in an ocean model which is subject to an externally imposed perturbative surface heat flux. The passive tracer is initialised to the conservative temperature in the control climate before the perturbation is imposed. Its surface flux is the heat flux from the atmosphere, not including the imposed perturbation, and is converted to a passive tracer increment as if it were being added to conservative temperature. The passive tracer is transported within the ocean as if it were conservative temperature. The passive tracer records redistributed heat, as described for the CMIP6 FAFMIP experiment (doi:10.5194/gmd-9-3993-2016), following earlier ideas. Conservative Temperature is defined as part of the Thermodynamic Equation of Seawater 2010 (TEOS-10) which was adopted in 2010 by the International Oceanographic Commission (IOC). Conservative Temperature is specific potential enthalpy (which has the standard name sea_water_specific_potential_enthalpy) divided by a fixed value of the specific heat capacity of sea water, namely cp_0 = 3991.86795711963 J kg-1 K-1. Conservative Temperature is a more accurate measure of the "heat content" of sea water, by a factor of one hundred, than is potential temperature. Because of this, it can be regarded as being proportional to the heat content of sea water per unit mass. Reference: www.teos-10.org; McDougall, 2003 doi: 10.1175/1520-0485(2003)033<0945:PEACOV>2.0.CO;2. It is strongly recommended that a variable with this standard name should have a units_metadata attribute, with one of the values "on-scale" or "difference", whichever is appropriate for the data, because it is essential to know whether the temperature is on-scale (meaning relative to the origin of the scale indicated by the units) or refers to temperature differences (implying that the origin of the temperature scale is irrevelant), in order to convert the units correctly (cf. https://cfconventions.org/cf-conventions/cf-conventions.html#temperature-units). degree_C - The quantity with standard name sea_water_redistributed_potential_temperature is a passive tracer in an ocean model which is subject to an externally imposed perturbative surface heat flux. The passive tracer is initialised to the potential temperature in the control climate before the perturbation is imposed. Its surface flux is the heat flux from the atmosphere, not including the imposed perturbation, and is converted to a passive tracer increment as if it were being added to potential temperature. The passive tracer is transported within the ocean as if it were potential temperature. The passive tracer records redistributed heat, as described for the CMIP6 FAFMIP experiment (doi:10.5194/gmd-9-3993-2016), following earlier ideas. Potential temperature is the temperature a parcel of air or sea water would have if moved adiabatically to sea level pressure. + The quantity with standard name sea_water_redistributed_potential_temperature is a passive tracer in an ocean model which is subject to an externally imposed perturbative surface heat flux. The passive tracer is initialised to the potential temperature in the control climate before the perturbation is imposed. Its surface flux is the heat flux from the atmosphere, not including the imposed perturbation, and is converted to a passive tracer increment as if it were being added to potential temperature. The passive tracer is transported within the ocean as if it were potential temperature. The passive tracer records redistributed heat, as described for the CMIP6 FAFMIP experiment (doi:10.5194/gmd-9-3993-2016), following earlier ideas. Potential temperature is the temperature a parcel of air or sea water would have if moved adiabatically to sea level pressure. It is strongly recommended that a variable with this standard name should have a units_metadata attribute, with one of the values "on-scale" or "difference", whichever is appropriate for the data, because it is essential to know whether the temperature is on-scale (meaning relative to the origin of the scale indicated by the units) or refers to temperature differences (implying that the origin of the temperature scale is irrevelant), in order to convert the units correctly (cf. https://cfconventions.org/cf-conventions/cf-conventions.html#temperature-units). @@ -20216,6 +20364,13 @@ http://vocab.nerc.ac.uk/collection/P01/current/TPHSDSZZ/6/. Speed is the magnitude of velocity. The speed at the sea floor is that adjacent to the ocean bottom, which would be the deepest grid cell in an ocean model and within the benthic boundary layer for measurements. + + m s-1 + + + Speed is the magnitude of velocity. The specification of a physical process by the phrase "due_to_" process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. "Ekman drift" is the movement of a layer of water (the Ekman layer) due to the combination of wind stress at the sea surface and the Coriolis effect. Ekman drift is to the right of the wind direction in the Northern Hemisphere and the left in the Southern Hemisphere. Reference: https://www.open.edu/openlearn/science-maths-technology/the-oceans/content-section-4.3. + + m s-1 @@ -20234,28 +20389,28 @@ http://vocab.nerc.ac.uk/collection/P01/current/TPHSDSZZ/6/. K 80 to - Sea water temperature is the in situ temperature of the sea water. To specify the depth at which the temperature applies use a vertical coordinate variable or scalar coordinate variable. There are standard names for sea_surface_temperature, sea_surface_skin_temperature, sea_surface_subskin_temperature and sea_surface_foundation_temperature which can be used to describe data located at the specified surfaces. For observed data, depending on the period during which the observation was made, the measured in situ temperature was recorded against standard "scales". These historical scales include the International Practical Temperature Scale of 1948 (IPTS-48; 1948-1967), the International Practical Temperature Scale of 1968 (IPTS-68, Barber, 1969; 1968-1989) and the International Temperature Scale of 1990 (ITS-90, Saunders 1990; 1990 onwards). Conversion of data between these scales follows t68 = t48 - (4.4 x 10e-6) * t48(100 - t - 48); t90 = 0.99976 * t68. Observations made prior to 1948 (IPTS-48) have not been documented and therefore a conversion cannot be certain. Differences between t90 and t68 can be up to 0.01 at temperatures of 40 C and above; differences of 0.002-0.007 occur across the standard range of ocean temperatures (-10 - 30 C). The International Equation of State of Seawater 1980 (EOS-80, UNESCO, 1981) and the Practical Salinity Scale (PSS-78) were both based on IPTS-68, while the Thermodynamic Equation of Seawater 2010 (TEOS-10) is based on ITS-90. References: Barber, 1969, doi: 10.1088/0026-1394/5/2/001; UNESCO, 1981; Saunders, 1990, WOCE Newsletter, 10, September 1990. + Sea water temperature is the in situ temperature of the sea water. To specify the depth at which the temperature applies use a vertical coordinate variable or scalar coordinate variable. There are standard names for sea_surface_temperature, sea_surface_skin_temperature, sea_surface_subskin_temperature and sea_surface_foundation_temperature which can be used to describe data located at the specified surfaces. For observed data, depending on the period during which the observation was made, the measured in situ temperature was recorded against standard "scales". These historical scales include the International Practical Temperature Scale of 1948 (IPTS-48; 1948-1967), the International Practical Temperature Scale of 1968 (IPTS-68, Barber, 1969; 1968-1989) and the International Temperature Scale of 1990 (ITS-90, Saunders 1990; 1990 onwards). Conversion of data between these scales follows t68 = t48 - (4.4 x 10e-6) * t48(100 - t - 48); t90 = 0.99976 * t68. Observations made prior to 1948 (IPTS-48) have not been documented and therefore a conversion cannot be certain. Differences between t90 and t68 can be up to 0.01 at temperatures of 40 C and above; differences of 0.002-0.007 occur across the standard range of ocean temperatures (-10 - 30 C). The International Equation of State of Seawater 1980 (EOS-80, UNESCO, 1981) and the Practical Salinity Scale (PSS-78) were both based on IPTS-68, while the Thermodynamic Equation of Seawater 2010 (TEOS-10) is based on ITS-90. References: Barber, 1969, doi: 10.1088/0026-1394/5/2/001; UNESCO, 1981; Saunders, 1990, WOCE Newsletter, 10, September 1990. It is strongly recommended that a variable with this standard name should have a units_metadata attribute, with one of the values "on-scale" or "difference", whichever is appropriate for the data, because it is essential to know whether the temperature is on-scale (meaning relative to the origin of the scale indicated by the units) or refers to temperature differences (implying that the origin of the temperature scale is irrevelant), in order to convert the units correctly (cf. https://cfconventions.org/cf-conventions/cf-conventions.html#temperature-units). K - The term "anomaly" means difference from climatology. Sea water temperature is the in situ temperature of the sea water. To specify the depth at which the temperature anomaly applies, use a vertical coordinate variable or scalar coordinate variable. + The term "anomaly" means difference from climatology. Sea water temperature is the in situ temperature of the sea water. To specify the depth at which the temperature anomaly applies, use a vertical coordinate variable or scalar coordinate variable. It is strongly recommended that a variable with this standard name should have the attribute units_metadata="temperature: difference", meaning that it refers to temperature differences and implying that the origin of the temperature scale is irrelevant, because it is essential to know whether a temperature is on-scale or a difference in order to convert the units correctly (cf. https://cfconventions.org/cf-conventions/cf-conventions.html#temperature-units). K - Sea water temperature is the in situ temperature of the sea water. The temperature at the sea floor is that adjacent to the ocean bottom, which would be the deepest grid cell in an ocean model and within the benthic boundary layer for measurements. + Sea water temperature is the in situ temperature of the sea water. The temperature at the sea floor is that adjacent to the ocean bottom, which would be the deepest grid cell in an ocean model and within the benthic boundary layer for measurements. It is strongly recommended that a variable with this standard name should have a units_metadata attribute, with one of the values "on-scale" or "difference", whichever is appropriate for the data, because it is essential to know whether the temperature is on-scale (meaning relative to the origin of the scale indicated by the units) or refers to temperature differences (implying that the origin of the temperature scale is irrevelant), in order to convert the units correctly (cf. https://cfconventions.org/cf-conventions/cf-conventions.html#temperature-units). K - Sea water temperature is the in situ temperature of the sea water. + Sea water temperature is the in situ temperature of the sea water. It is strongly recommended that a variable with this standard name should have the attribute units_metadata="temperature: difference", meaning that it refers to temperature differences and implying that the origin of the temperature scale is irrelevant, because it is essential to know whether a temperature is on-scale or a difference in order to convert the units correctly (cf. https://cfconventions.org/cf-conventions/cf-conventions.html#temperature-units). @@ -20293,6 +20448,13 @@ http://vocab.nerc.ac.uk/collection/P01/current/TPHSDSZZ/6/. A velocity is a vector quantity. The phrase "to_direction" is used in the construction X_to_direction and indicates the direction towards which the velocity vector of X is headed. The direction is a bearing in the usual geographical sense, measured positive clockwise from due north. The direction at the sea floor is that adjacent to the ocean bottom, which would be the deepest grid cell in an ocean model and within the benthic boundary layer for measurements. + + degree + + + A velocity is a vector quantity. The phrase "to_direction" is used in the construction X_to_direction and indicates the direction towards which the velocity vector of X is headed. The direction is a bearing in the usual geographical sense, measured positive clockwise from due north. The specification of a physical process by the phrase "due_to_" process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. "Ekman drift" is the movement of a layer of water (the Ekman layer) due to the combination of wind stress at the sea surface and the Coriolis effect. Ekman drift is to the right of the wind direction in the Northern Hemisphere and the left in the Southern Hemisphere. Reference: https://www.open.edu/openlearn/science-maths-technology/the-oceans/content-section-4.3. + + degree @@ -20454,6 +20616,20 @@ http://vocab.nerc.ac.uk/collection/P01/current/TPHSDSZZ/6/. "Single scattering albedo" is the fraction of radiation in an incident light beam scattered by the particles of an aerosol reference volume for a given wavelength. It is the ratio of the scattering and the extinction coefficients of the aerosol particles in the reference volume. A coordinate variable with a standard name of radiation_wavelength or radiation_frequency should be included to specify either the wavelength or frequency. "Aerosol" means the system of suspended liquid or solid particles in air (except cloud droplets) and their carrier gas, the air itself. "Ambient_aerosol" means that the aerosol is measured or modelled at the ambient state of pressure, temperature and relative humidity that exists in its immediate environment. "Ambient aerosol particles" are aerosol particles that have taken up ambient water through hygroscopic growth. The extent of hygroscopic growth depends on the relative humidity and the composition of the particles. To specify the relative humidity and temperature at which the quantity described by the standard name applies, provide scalar coordinate variables with standard names of "relative_humidity" and "air_temperature". The specification of a physical process by the phrase "due_to_" process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. + + 1 + + + Singular values of the matrix representing the remote sensing averaging kernels (Weber 2019; Schneider et al., 2022) of the methane mole fractions obtained by a remote sensing observation (changes of methane in the retrieved atmosphere relative to the changes of methane in the true atmosphere, Rodgers 2000). + + + + 1 + + + Singular values of the matrix representing the remote sensing averaging kernels (Weber 2019; Schneider et al., 2022) of the methane mole fractions obtained by a remote sensing observation (changes of methane in the retrieved atmosphere relative to the changes of methane in the true atmosphere, Rodgers 2000). + + kg m-2 s-1 @@ -20710,21 +20886,21 @@ http://vocab.nerc.ac.uk/collection/P01/current/TPHSDSZZ/6/. K 85 - Soil temperature is the bulk temperature of the soil, not the surface (skin) temperature. "Soil" means the near-surface layer where plants sink their roots. For subsurface temperatures that extend beneath the soil layer or in areas where there is no surface soil layer, the standard name temperature_in_ground should be used. + Soil temperature is the bulk temperature of the soil, not the surface (skin) temperature. "Soil" means the near-surface layer where plants sink their roots. For subsurface temperatures that extend beneath the soil layer or in areas where there is no surface soil layer, the standard name temperature_in_ground should be used. It is strongly recommended that a variable with this standard name should have a units_metadata attribute, with one of the values "on-scale" or "difference", whichever is appropriate for the data, because it is essential to know whether the temperature is on-scale (meaning relative to the origin of the scale indicated by the units) or refers to temperature differences (implying that the origin of the temperature scale is irrevelant), in order to convert the units correctly (cf. https://cfconventions.org/cf-conventions/cf-conventions.html#temperature-units). J kg-1 K-1 - Thermal capacity, or heat capacity, is the amount of heat energy required to increase the temperature of 1 kg of material by 1 K. It is a property of the material. + Thermal capacity, or heat capacity, is the amount of heat energy required to increase the temperature of 1 kg of material by 1 K. It is a property of the material. It is strongly recommended that a variable with this standard name should have the attribute units_metadata="temperature: difference", meaning that it refers to temperature differences and implying that the origin of the temperature scale is irrelevant, because it is essential to know whether a temperature is on-scale or a difference in order to convert the units correctly (cf. https://cfconventions.org/cf-conventions/cf-conventions.html#temperature-units). W m-1 K-1 - Thermal conductivity is the constant k in the formula q = -k grad T where q is the heat transfer per unit time per unit area of a surface normal to the direction of transfer and grad T is the temperature gradient. Thermal conductivity is a property of the material. + Thermal conductivity is the constant k in the formula q = -k grad T where q is the heat transfer per unit time per unit area of a surface normal to the direction of transfer and grad T is the temperature gradient. Thermal conductivity is a property of the material. It is strongly recommended that a variable with this standard name should have the attribute units_metadata="temperature: difference", meaning that it refers to temperature differences and implying that the origin of the temperature scale is irrelevant, because it is essential to know whether a temperature is on-scale or a difference in order to convert the units correctly (cf. https://cfconventions.org/cf-conventions/cf-conventions.html#temperature-units). @@ -20913,14 +21089,14 @@ http://vocab.nerc.ac.uk/collection/P01/current/TPHSDSZZ/6/. J kg-1 K-1 - Thermal capacity, or heat capacity, is the amount of heat energy required to increase the temperature of 1 kg of material by 1 K. It is a property of the material. + Thermal capacity, or heat capacity, is the amount of heat energy required to increase the temperature of 1 kg of material by 1 K. It is a property of the material. It is strongly recommended that a variable with this standard name should have the attribute units_metadata="temperature: difference", meaning that it refers to temperature differences and implying that the origin of the temperature scale is irrelevant, because it is essential to know whether a temperature is on-scale or a difference in order to convert the units correctly (cf. https://cfconventions.org/cf-conventions/cf-conventions.html#temperature-units). J kg-1 K-1 - The specific heat capacity of sea water, Cp(ocean), is used in ocean models to convert between model prognostic temperature (potential or conservative temperature) and model heat content. + The specific heat capacity of sea water, Cp(ocean), is used in ocean models to convert between model prognostic temperature (potential or conservative temperature) and model heat content. It is strongly recommended that a variable with this standard name should have the attribute units_metadata="temperature: difference", meaning that it refers to temperature differences and implying that the origin of the temperature scale is irrelevant, because it is essential to know whether a temperature is on-scale or a difference in order to convert the units correctly (cf. https://cfconventions.org/cf-conventions/cf-conventions.html#temperature-units). @@ -21025,7 +21201,7 @@ http://vocab.nerc.ac.uk/collection/P01/current/TPHSDSZZ/6/. K2 mptta - "square_of_X" means X*X. Air temperature is the bulk temperature of the air, not the surface (skin) temperature. + The phrase "square_of_X" means X*X. Air temperature is the bulk temperature of the air, not the surface (skin) temperature. It is strongly recommended that a variable with this standard name should have a units_metadata attribute, with one of the values "on-scale" or "difference", whichever is appropriate for the data, because it is essential to know whether the temperature is on-scale (meaning relative to the origin of the scale indicated by the units) or refers to temperature differences (implying that the origin of the temperature scale is irrevelant), in order to convert the units correctly (cf. https://cfconventions.org/cf-conventions/cf-conventions.html#temperature-units). @@ -21095,7 +21271,7 @@ http://vocab.nerc.ac.uk/collection/P01/current/TPHSDSZZ/6/. K2 - Sea surface temperature is usually abbreviated as "SST". It is the temperature of sea water near the surface (including the part under sea-ice, if any), and not the skin temperature, whose standard name is surface_temperature. For the temperature of sea water at a particular depth or layer, a data variable of sea_water_temperature with a vertical coordinate axis should be used. "square_of_X" means X*X. + Sea surface temperature is usually abbreviated as "SST". It is the temperature of sea water near the surface (including the part under sea-ice, if any), and not the skin temperature, whose standard name is surface_temperature. For the temperature of sea water at a particular depth or layer, a data variable of sea_water_temperature with a vertical coordinate axis should be used. "square_of_X" means X*X. It is strongly recommended that a variable with this standard name should have a units_metadata attribute, with one of the values "on-scale" or "difference", whichever is appropriate for the data, because it is essential to know whether the temperature is on-scale (meaning relative to the origin of the scale indicated by the units) or refers to temperature differences (implying that the origin of the temperature scale is irrevelant), in order to convert the units correctly (cf. https://cfconventions.org/cf-conventions/cf-conventions.html#temperature-units). @@ -21116,7 +21292,7 @@ http://vocab.nerc.ac.uk/collection/P01/current/TPHSDSZZ/6/. K - In thermodynamics and fluid mechanics, stagnation temperature is the temperature at a stagnation point in a fluid flow. At a stagnation point the speed of the fluid is zero and all of the kinetic energy has been converted to internal energy and is added to the local static enthalpy. In both compressible and incompressible fluid flow, the stagnation temperature is equal to the total temperature at all points on the streamline leading to the stagnation point. In aviation, stagnation temperature is known as total air temperature and is measured by a temperature probe mounted on the surface of the aircraft. The probe is designed to bring the air to rest relative to the aircraft. As the air is brought to rest, kinetic energy is converted to internal energy. The air is compressed and experiences an adiabatic increase in temperature. Therefore, total air temperature is higher than the static (or ambient) air temperature. Total air temperature is an essential input to an air data computer in order to enable computation of static air temperature and hence true airspeed. + In thermodynamics and fluid mechanics, stagnation temperature is the temperature at a stagnation point in a fluid flow. At a stagnation point the speed of the fluid is zero and all of the kinetic energy has been converted to internal energy and is added to the local static enthalpy. In both compressible and incompressible fluid flow, the stagnation temperature is equal to the total temperature at all points on the streamline leading to the stagnation point. In aviation, stagnation temperature is known as total air temperature and is measured by a temperature probe mounted on the surface of the aircraft. The probe is designed to bring the air to rest relative to the aircraft. As the air is brought to rest, kinetic energy is converted to internal energy. The air is compressed and experiences an adiabatic increase in temperature. Therefore, total air temperature is higher than the static (or ambient) air temperature. Total air temperature is an essential input to an air data computer in order to enable computation of static air temperature and hence true airspeed. It is strongly recommended that a variable with this standard name should have a units_metadata attribute, with one of the values "on-scale" or "difference", whichever is appropriate for the data, because it is essential to know whether the temperature is on-scale (meaning relative to the origin of the scale indicated by the units) or refers to temperature differences (implying that the origin of the temperature scale is irrevelant), in order to convert the units correctly (cf. https://cfconventions.org/cf-conventions/cf-conventions.html#temperature-units). @@ -21368,7 +21544,7 @@ http://vocab.nerc.ac.uk/collection/P01/current/TPHSDSZZ/6/. K - The surface called "surface" means the lower boundary of the atmosphere.The brightness temperature of a body is the temperature of a black body which radiates the same power per unit solid angle per unit area. + The surface called "surface" means the lower boundary of the atmosphere.The brightness temperature of a body is the temperature of a black body which radiates the same power per unit solid angle per unit area. It is strongly recommended that a variable with this standard name should have a units_metadata attribute, with one of the values "on-scale" or "difference", whichever is appropriate for the data, because it is essential to know whether the temperature is on-scale (meaning relative to the origin of the scale indicated by the units) or refers to temperature differences (implying that the origin of the temperature scale is irrevelant), in order to convert the units correctly (cf. https://cfconventions.org/cf-conventions/cf-conventions.html#temperature-units). @@ -21707,6 +21883,20 @@ http://vocab.nerc.ac.uk/collection/P01/current/TPHSDSZZ/6/. The surface called "surface" means the lower boundary of the atmosphere. Downwelling radiation is radiation from above. It does not mean "net downward". The sign convention is that "upwelling" is positive upwards and "downwelling" is positive downwards. The term "longwave" means longwave radiation. When thought of as being incident on a surface, a radiative flux is sometimes called "irradiance". In addition, it is identical with the quantity measured by a cosine-collector light-meter and sometimes called "vector irradiance". In accordance with common usage in geophysical disciplines, "flux" implies per unit area, called "flux density" in physics. A phrase assuming_condition indicates that the named quantity is the value which would obtain if all aspects of the system were unaltered except for the assumption of the circumstances specified by the condition. "Clear sky" means in the absence of clouds. + + W/m2 + + + The surface called "surface" means the lower boundary of the atmosphere. Upwelling radiation is radiation from below. It does not mean "net upward". The sign convention is that "upwelling" is positive upwards and "downwelling" is positive downwards. The term "longwave" means longwave radiation. When thought of as being incident on a surface, a radiative flux is sometimes called "irradiance". In addition, it is identical with the quantity measured by a cosine-collector light-meter and sometimes called "vector irradiance". In accordance with common usage in geophysical disciplines, "flux" implies per unit area, called "flux density" in physics. A phrase assuming_condition indicates that the named quantity is the value which would obtain if all aspects of the system were unaltered except for the assumption of the circumstances specified by the condition. "Clear sky" means in the absence of clouds. This 3D ozone field acts as a reference ozone field in a diagnostic call to the model's radiation scheme. It is expressed in terms of mole fraction of ozone in air. It may be observation-based or model-derived. It may be from any time period. By using the same ozone reference in the diagnostic radiation call in two model simulations and calculating differences between the radiative flux diagnostics from the prognostic call to the radiation scheme and the diagnostic call to the radiation scheme with the ozone reference, an instantaneous radiative forcing for ozone can be calculated. + + + + W/m2 + + + The surface called "surface" means the lower boundary of the atmosphere. Downwelling radiation is radiation from above. It does not mean "net downward". The sign convention is that "upwelling" is positive upwards and "downwelling" is positive downwards. The term "longwave" means longwave radiation. When thought of as being incident on a surface, a radiative flux is sometimes called "irradiance". In addition, it is identical with the quantity measured by a cosine-collector light-meter and sometimes called "vector irradiance". In accordance with common usage in geophysical disciplines, "flux" implies per unit area, called "flux density" in physics. A phrase assuming_condition indicates that the named quantity is the value which would obtain if all aspects of the system were unaltered except for the assumption of the circumstances specified by the condition. This 3D ozone field acts as a reference ozone field in a diagnostic call to the model's radiation scheme. It is expressed in terms of mole fraction of ozone in air. It may be observation-based or model-derived. It may be from any time period. By using the same ozone reference in the diagnostic radiation call in two model simulations and calculating differences between the radiative flux diagnostics from the prognostic call to the radiation scheme and the diagnostic call to the radiation scheme with the ozone reference, an instantaneous radiative forcing for ozone can be calculated. + + W m-2 @@ -21868,6 +22058,20 @@ http://vocab.nerc.ac.uk/collection/P01/current/TPHSDSZZ/6/. The surface called "surface" means the lower boundary of the atmosphere. Downwelling radiation is radiation from above. It does not mean "net downward". The sign convention is that "upwelling" is positive upwards and "downwelling" is positive downwards. The term "shortwave" means shortwave radiation. Surface downwelling shortwave is the sum of direct and diffuse solar radiation incident on the surface, and is sometimes called "global radiation". When thought of as being incident on a surface, a radiative flux is sometimes called "irradiance". In addition, it is identical with the quantity measured by a cosine-collector light-meter and sometimes called "vector irradiance". In accordance with common usage in geophysical disciplines, "flux" implies per unit area, called "flux density" in physics. A phrase "assuming_condition" indicates that the named quantity is the value which would obtain if all aspects of the system were unaltered except for the assumption of the circumstances specified by the condition. "Clear sky" means in the absence of clouds. + + W/m2 + + + The surface called "surface" means the lower boundary of the atmosphere. Downwelling radiation is radiation from above. It does not mean "net downward". The sign convention is that "upwelling" is positive upwards and "downwelling" is positive downwards. The term "longwave" means longwave radiation. When thought of as being incident on a surface, a radiative flux is sometimes called "irradiance". In addition, it is identical with the quantity measured by a cosine-collector light-meter and sometimes called "vector irradiance". In accordance with common usage in geophysical disciplines, "flux" implies per unit area, called "flux density" in physics. A phrase assuming_condition indicates that the named quantity is the value which would obtain if all aspects of the system were unaltered except for the assumption of the circumstances specified by the condition. "Clear sky" means in the absence of clouds. This 3D ozone field acts as a reference ozone field in a diagnostic call to the model's radiation scheme. It is expressed in terms of mole fraction of ozone in air. It may be observation-based or model-derived. It may be from any time period. By using the same ozone reference in the diagnostic radiation call in two model simulations and calculating differences between the radiative flux diagnostics from the prognostic call to the radiation scheme and the diagnostic call to the radiation scheme with the ozone reference, an instantaneous radiative forcing for ozone can be calculated. + + + + W/m2 + + + The surface called "surface" means the lower boundary of the atmosphere. Downwelling radiation is radiation from above. It does not mean "net downward". The sign convention is that "upwelling" is positive upwards and "downwelling" is positive downwards. The term "longwave" means longwave radiation. When thought of as being incident on a surface, a radiative flux is sometimes called "irradiance". In addition, it is identical with the quantity measured by a cosine-collector light-meter and sometimes called "vector irradiance". In accordance with common usage in geophysical disciplines, "flux" implies per unit area, called "flux density" in physics. A phrase assuming_condition indicates that the named quantity is the value which would obtain if all aspects of the system were unaltered except for the assumption of the circumstances specified by the condition. This 3D ozone field acts as a reference ozone field in a diagnostic call to the model's radiation scheme. It is expressed in terms of mole fraction of ozone in air. It may be observation-based or model-derived. It may be from any time period. By using the same ozone reference in the diagnostic radiation call in two model simulations and calculating differences between the radiative flux diagnostics from the prognostic call to the radiation scheme and the diagnostic call to the radiation scheme with the ozone reference, an instantaneous radiative forcing for ozone can be calculated. + + W m-2 @@ -24924,14 +25128,14 @@ http://vocab.nerc.ac.uk/collection/P01/current/TPHSDSZZ/6/. K E139 ts - The surface called "surface" means the lower boundary of the atmosphere. The surface temperature is the temperature at the interface, not the bulk temperature of the medium above or below. Unless indicated in the cell_methods attribute, a quantity is assumed to apply to the whole area of each horizontal grid box. Previously, the qualifier where_type was used to specify that the quantity applies only to the part of the grid box of the named type. Names containing the where_type qualifier are deprecated and newly created data should use the cell_methods attribute to indicate the horizontal area to which the quantity applies. + The surface called "surface" means the lower boundary of the atmosphere. The surface temperature is the temperature at the interface, not the bulk temperature of the medium above or below. Unless indicated in the cell_methods attribute, a quantity is assumed to apply to the whole area of each horizontal grid box. Previously, the qualifier where_type was used to specify that the quantity applies only to the part of the grid box of the named type. Names containing the where_type qualifier are deprecated and newly created data should use the cell_methods attribute to indicate the horizontal area to which the quantity applies. In order to convert the units correctly, it is essential to know whether a temperature is on-scale or a difference. Therefore this standard strongly recommends that any variable whose units involve a temperature unit should also have a units_metadata attribute to make the distinction. It is strongly recommended that a variable with this standard name should have a units_metadata attribute, with one of the values "on-scale" or "difference", whichever is appropriate for the data, because it is essential to know whether the temperature is on-scale (meaning relative to the origin of the scale indicated by the units) or refers to temperature differences (implying that the origin of the temperature scale is irrevelant), in order to convert the units correctly (cf. https://cfconventions.org/cf-conventions/cf-conventions.html#temperature-units). K - The surface called "surface" means the lower boundary of the atmosphere. "anomaly" means difference from climatology. The surface temperature is the (skin) temperature at the interface, not the bulk temperature of the medium above or below. + The surface called "surface" means the lower boundary of the atmosphere. "anomaly" means difference from climatology. The surface temperature is the (skin) temperature at the interface, not the bulk temperature of the medium above or below. It is strongly recommended that a variable with this standard name should have the attribute units_metadata="temperature: difference", meaning that it refers to temperature differences and implying that the origin of the temperature scale is irrelevant, because it is essential to know whether a temperature is on-scale or a difference in order to convert the units correctly (cf. https://cfconventions.org/cf-conventions/cf-conventions.html#temperature-units). @@ -25284,6 +25488,13 @@ http://vocab.nerc.ac.uk/collection/P01/current/TPHSDSZZ/6/. The surface called "surface" means the lower boundary of the atmosphere. Upwelling radiation is radiation from below. It does not mean "net upward". The sign convention is that "upwelling" is positive upwards and "downwelling" is positive downwards. The term "longwave" means longwave radiation. When thought of as being incident on a surface, a radiative flux is sometimes called "irradiance". In addition, it is identical with the quantity measured by a cosine-collector light-meter and sometimes called "vector irradiance". In accordance with common usage in geophysical disciplines, "flux" implies per unit area, called "flux density" in physics. A phrase assuming_condition indicates that the named quantity is the value which would obtain if all aspects of the system were unaltered except for the assumption of the circumstances specified by the condition. "Clear sky" means in the absence of clouds. + + W/m2 + + + The surface called "surface" means the lower boundary of the atmosphere. Upwelling radiation is radiation from below. It does not mean "net upward". The sign convention is that "upwelling" is positive upwards and "downwelling" is positive downwards. The term "longwave" means longwave radiation. When thought of as being incident on a surface, a radiative flux is sometimes called "irradiance". In addition, it is identical with the quantity measured by a cosine-collector light-meter and sometimes called "vector irradiance". In accordance with common usage in geophysical disciplines, "flux" implies per unit area, called "flux density" in physics. A phrase assuming_condition indicates that the named quantity is the value which would obtain if all aspects of the system were unaltered except for the assumption of the circumstances specified by the condition. This 3D ozone field acts as a reference ozone field in a diagnostic call to the model's radiation scheme. It is expressed in terms of mole fraction of ozone in air. It may be observation-based or model-derived. It may be from any time period. By using the same ozone reference in the diagnostic radiation call in two model simulations and calculating differences between the radiative flux diagnostics from the prognostic call to the radiation scheme and the diagnostic call to the radiation scheme with the ozone reference, an instantaneous radiative forcing for ozone can be calculated. + + mol m-2 s-1 @@ -25382,6 +25593,20 @@ http://vocab.nerc.ac.uk/collection/P01/current/TPHSDSZZ/6/. The surface called "surface" means the lower boundary of the atmosphere. Upwelling radiation is radiation from below. It does not mean "net upward". The sign convention is that "upwelling" is positive upwards and "downwelling" is positive downwards. The term "shortwave" means shortwave radiation. When thought of as being incident on a surface, a radiative flux is sometimes called "irradiance". In addition, it is identical with the quantity measured by a cosine-collector light-meter and sometimes called "vector irradiance". In accordance with common usage in geophysical disciplines, "flux" implies per unit area, called "flux density" in physics. A phrase "assuming_condition" indicates that the named quantity is the value which would obtain if all aspects of the system were unaltered except for the assumption of the circumstances specified by the condition. "Clear sky" means in the absence of clouds. + + W/m2 + + + The surface called "surface" means the lower boundary of the atmosphere. Upwelling radiation is radiation from below. It does not mean "net upward". The sign convention is that "upwelling" is positive upwards and "downwelling" is positive downwards. The term "shortwave" means shortwave radiation. When thought of as being incident on a surface, a radiative flux is sometimes called "irradiance". In addition, it is identical with the quantity measured by a cosine-collector light-meter and sometimes called "vector irradiance". In accordance with common usage in geophysical disciplines, "flux" implies per unit area, called "flux density" in physics. A phrase "assuming_condition" indicates that the named quantity is the value which would obtain if all aspects of the system were unaltered except for the assumption of the circumstances specified by the condition. "Clear sky" means in the absence of clouds. This 3D ozone field acts as a reference ozone field in a diagnostic call to the model's radiation scheme. It is expressed in terms of mole fraction of ozone in air. It may be observation-based or model-derived. It may be from any time period. By using the same ozone reference in the diagnostic radiation call in two model simulations and calculating differences between the radiative flux diagnostics from the prognostic call to the radiation scheme and the diagnostic call to the radiation scheme with the ozone reference, an instantaneous radiative forcing for ozone can be calculated. + + + + W/m2 + + + The surface called "surface" means the lower boundary of the atmosphere. Upwelling radiation is radiation from below. It does not mean "net upward". The sign convention is that "upwelling" is positive upwards and "downwelling" is positive downwards. The term "shortwave" means shortwave radiation. When thought of as being incident on a surface, a radiative flux is sometimes called "irradiance". In addition, it is identical with the quantity measured by a cosine-collector light-meter and sometimes called "vector irradiance". In accordance with common usage in geophysical disciplines, "flux" implies per unit area, called "flux density" in physics. A phrase "assuming_condition" indicates that the named quantity is the value which would obtain if all aspects of the system were unaltered except for the assumption of the circumstances specified by the condition. This 3D ozone field acts as a reference ozone field in a diagnostic call to the model's radiation scheme. It is expressed in terms of mole fraction of ozone in air. It may be observation-based or model-derived. It may be from any time period. By using the same ozone reference in the diagnostic radiation call in two model simulations and calculating differences between the radiative flux diagnostics from the prognostic call to the radiation scheme and the diagnostic call to the radiation scheme with the ozone reference, an instantaneous radiative forcing for ozone can be calculated. + + kg m-2 @@ -25407,28 +25632,28 @@ http://vocab.nerc.ac.uk/collection/P01/current/TPHSDSZZ/6/. K - The quantity with standard name temperature_at_base_of_ice_sheet_model is the lower boundary temperature that is used to force ice sheet models. Beneath ice shelves it is the temperature at the ice-ocean interface. Beneath grounded ice, it is the temperature at the ice-bedrock interface. In all instances the temperature is that of the interface itself and not that of the medium above or below the interface. + The quantity with standard name temperature_at_base_of_ice_sheet_model is the lower boundary temperature that is used to force ice sheet models. Beneath ice shelves it is the temperature at the ice-ocean interface. Beneath grounded ice, it is the temperature at the ice-bedrock interface. In all instances the temperature is that of the interface itself and not that of the medium above or below the interface. It is strongly recommended that a variable with this standard name should have a units_metadata attribute, with one of the values "on-scale" or "difference", whichever is appropriate for the data, because it is essential to know whether the temperature is on-scale (meaning relative to the origin of the scale indicated by the units) or refers to temperature differences (implying that the origin of the temperature scale is irrevelant), in order to convert the units correctly (cf. https://cfconventions.org/cf-conventions/cf-conventions.html#temperature-units). K - The quantity with standard name temperature_at_top_of_ice_sheet_model is the upper boundary temperature that is used to force ice sheet models. It is the temperature at the interface between the ice sheet and the overlying medium which may be snow or the atmosphere. In all instances the temperature is that of the interface itself and not that of the medium above or below the interface. + The quantity with standard name temperature_at_top_of_ice_sheet_model is the upper boundary temperature that is used to force ice sheet models. It is the temperature at the interface between the ice sheet and the overlying medium which may be snow or the atmosphere. In all instances the temperature is that of the interface itself and not that of the medium above or below the interface. It is strongly recommended that a variable with this standard name should have a units_metadata attribute, with one of the values "on-scale" or "difference", whichever is appropriate for the data, because it is essential to know whether the temperature is on-scale (meaning relative to the origin of the scale indicated by the units) or refers to temperature differences (implying that the origin of the temperature scale is irrevelant), in order to convert the units correctly (cf. https://cfconventions.org/cf-conventions/cf-conventions.html#temperature-units). K - This quantity is defined as the temperature difference between a parcel of air lifted adiabatically from a starting air pressure to a finishing air pressure in the troposphere and the ambient air temperature at the finishing air pressure in the troposphere. It is often called the lifted index (LI) and provides a measure of the instability of the atmosphere. The air parcel is "lifted" by moving the air parcel from the starting air pressure to the Lifting Condensation Level (dry adiabatically) and then from the Lifting Condensation Level to the finishing air pressure (wet adiabatically). Air temperature is the bulk temperature of the air. Coordinate variables of original_air_pressure_of_lifted_parcel and final_air_pressure_of_lifted_parcel should be specified to indicate the specific air pressures at which the parcel lifting starts (starting air pressure) and the temperature difference is calculated at (finishing air pressure), respectively. + This quantity is defined as the temperature difference between a parcel of air lifted adiabatically from a starting air pressure to a finishing air pressure in the troposphere and the ambient air temperature at the finishing air pressure in the troposphere. It is often called the lifted index (LI) and provides a measure of the instability of the atmosphere. The air parcel is "lifted" by moving the air parcel from the starting air pressure to the Lifting Condensation Level (dry adiabatically) and then from the Lifting Condensation Level to the finishing air pressure (wet adiabatically). Air temperature is the bulk temperature of the air. Coordinate variables of original_air_pressure_of_lifted_parcel and final_air_pressure_of_lifted_parcel should be specified to indicate the specific air pressures at which the parcel lifting starts (starting air pressure) and the temperature difference is calculated at (finishing air pressure), respectively. It is strongly recommended that a variable with this standard name should have the attribute units_metadata="temperature: difference", meaning that it refers to temperature differences and implying that the origin of the temperature scale is irrelevant, because it is essential to know whether a temperature is on-scale or a difference in order to convert the units correctly (cf. https://cfconventions.org/cf-conventions/cf-conventions.html#temperature-units). K - This quantity is defined as the temperature difference between a parcel of air lifted adiabatically from the surface to a finishing air pressure in the troposphere and the ambient air temperature at the finishing air pressure in the troposphere. It is often called the lifted index (LI) and provides a measure of the instability of the atmosphere. The air parcel is "lifted" by moving the air parcel from the surface to the Lifting Condensation Level (dry adiabatically) and then from the Lifting Condensation Level to the finishing air pressure (wet adiabatically). Air temperature is the bulk temperature of the air, not the surface (skin) temperature. The term "surface" means the lower boundary of the atmosphere. A coordinate variable of final_air_pressure_of_lifted_parcel should be specified to indicate the specific air pressure that the temperature difference is calculated at. + This quantity is defined as the temperature difference between a parcel of air lifted adiabatically from the surface to a finishing air pressure in the troposphere and the ambient air temperature at the finishing air pressure in the troposphere. It is often called the lifted index (LI) and provides a measure of the instability of the atmosphere. The air parcel is "lifted" by moving the air parcel from the surface to the Lifting Condensation Level (dry adiabatically) and then from the Lifting Condensation Level to the finishing air pressure (wet adiabatically). Air temperature is the bulk temperature of the air, not the surface (skin) temperature. The term "surface" means the lower boundary of the atmosphere. A coordinate variable of final_air_pressure_of_lifted_parcel should be specified to indicate the specific air pressure that the temperature difference is calculated at. It is strongly recommended that a variable with this standard name should have the attribute units_metadata="temperature: difference", meaning that it refers to temperature differences and implying that the origin of the temperature scale is irrelevant, because it is essential to know whether a temperature is on-scale or a difference in order to convert the units correctly (cf. https://cfconventions.org/cf-conventions/cf-conventions.html#temperature-units). @@ -25456,28 +25681,28 @@ http://vocab.nerc.ac.uk/collection/P01/current/TPHSDSZZ/6/. K - The temperature at any given depth (or in a layer) below the surface of the ground, excluding surficial snow and ice (but not permafrost or soil). For temperatures in surface lying snow and ice, the more specific standard names temperature_in_surface_snow and land_ice_temperature should be used. For temperatures measured or modelled specifically for the soil layer (the near-surface layer where plants sink their roots) the standard name soil_temperature should be used. + The temperature at any given depth (or in a layer) below the surface of the ground, excluding surficial snow and ice (but not permafrost or soil). For temperatures in surface lying snow and ice, the more specific standard names temperature_in_surface_snow and land_ice_temperature should be used. For temperatures measured or modelled specifically for the soil layer (the near-surface layer where plants sink their roots) the standard name soil_temperature should be used. It is strongly recommended that a variable with this standard name should have a units_metadata attribute, with one of the values "on-scale" or "difference", whichever is appropriate for the data, because it is essential to know whether the temperature is on-scale (meaning relative to the origin of the scale indicated by the units) or refers to temperature differences (implying that the origin of the temperature scale is irrevelant), in order to convert the units correctly (cf. https://cfconventions.org/cf-conventions/cf-conventions.html#temperature-units). K E238 - "Temperature in surface snow" is the bulk temperature of the snow, not the surface (skin) temperature. Surface snow refers to the snow on the solid ground or on surface ice cover, but excludes, for example, falling snowflakes and snow on plants. + "Temperature in surface snow" is the bulk temperature of the snow, not the surface (skin) temperature. Surface snow refers to the snow on the solid ground or on surface ice cover, but excludes, for example, falling snowflakes and snow on plants. It is strongly recommended that a variable with this standard name should have a units_metadata attribute, with one of the values "on-scale" or "difference", whichever is appropriate for the data, because it is essential to know whether the temperature is on-scale (meaning relative to the origin of the scale indicated by the units) or refers to temperature differences (implying that the origin of the temperature scale is irrevelant), in order to convert the units correctly (cf. https://cfconventions.org/cf-conventions/cf-conventions.html#temperature-units). K - The temperature_of_analysis_of_sea_water is the reference temperature for the effects of temperature on the measurement of another variable. This temperature should be measured, but may have been calculated, or assumed. For example, the temperature of the sample when measuring pH, or the temperature of equilibration in the case of dissolved gases. The linkage between the data variable and the variable with a standard_name of temperature_of_analysis_of_sea_water is achieved using the ancillary_variables attribute on the data variable. + The temperature_of_analysis_of_sea_water is the reference temperature for the effects of temperature on the measurement of another variable. This temperature should be measured, but may have been calculated, or assumed. For example, the temperature of the sample when measuring pH, or the temperature of equilibration in the case of dissolved gases. The linkage between the data variable and the variable with a standard_name of temperature_of_analysis_of_sea_water is achieved using the ancillary_variables attribute on the data variable. It is strongly recommended that a variable with this standard name should have a units_metadata attribute, with one of the values "on-scale" or "difference", whichever is appropriate for the data, because it is essential to know whether the temperature is on-scale (meaning relative to the origin of the scale indicated by the units) or refers to temperature differences (implying that the origin of the temperature scale is irrevelant), in order to convert the units correctly (cf. https://cfconventions.org/cf-conventions/cf-conventions.html#temperature-units). K - Temperature_of_sensor_for_oxygen_in_sea_water is the instrument temperature used in calculating the concentration of oxygen in sea water; it is not a measurement of the ambient water temperature. + Temperature_of_sensor_for_oxygen_in_sea_water is the instrument temperature used in calculating the concentration of oxygen in sea water; it is not a measurement of the ambient water temperature. It is strongly recommended that a variable with this standard name should have a units_metadata attribute, with one of the values "on-scale" or "difference", whichever is appropriate for the data, because it is essential to know whether the temperature is on-scale (meaning relative to the origin of the scale indicated by the units) or refers to temperature differences (implying that the origin of the temperature scale is irrevelant), in order to convert the units correctly (cf. https://cfconventions.org/cf-conventions/cf-conventions.html#temperature-units). @@ -25498,154 +25723,154 @@ http://vocab.nerc.ac.uk/collection/P01/current/TPHSDSZZ/6/. K s-1 - "tendency_of_X" means derivative of X with respect to time. Air temperature is the bulk temperature of the air, not the surface (skin) temperature. + The phrase "tendency_of_X" means derivative of X with respect to time. Air temperature is the bulk temperature of the air, not the surface (skin) temperature. It is strongly recommended that a variable with this standard name should have the attribute units_metadata="temperature: difference", meaning that it refers to temperature differences and implying that the origin of the temperature scale is irrelevant, because it is essential to know whether a temperature is on-scale or a difference in order to convert the units correctly (cf. https://cfconventions.org/cf-conventions/cf-conventions.html#temperature-units). K s-1 - The specification of a physical process by the phrase due_to_process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. "tendency_of_X" means derivative of X with respect to time. Air temperature is the bulk temperature of the air, not the surface (skin) temperature. + The specification of a physical process by the phrase due_to_process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. "tendency_of_X" means derivative of X with respect to time. Air temperature is the bulk temperature of the air, not the surface (skin) temperature. It is strongly recommended that a variable with this standard name should have the attribute units_metadata="temperature: difference", meaning that it refers to temperature differences and implying that the origin of the temperature scale is irrelevant, because it is essential to know whether a temperature is on-scale or a difference in order to convert the units correctly (cf. https://cfconventions.org/cf-conventions/cf-conventions.html#temperature-units). K s-1 - The phrase "tendency_of_X" means derivative of X with respect to time. Air temperature is the bulk temperature of the air, not the surface (skin) temperature. The specification of a physical process by the phrase "due_to_" process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. "Boundary layer mixing" means turbulent motions that transport heat, water, momentum and chemical constituents within the atmospheric boundary layer and affect exchanges between the surface and the atmosphere. The atmospheric boundary layer is typically characterised by a well-mixed sub-cloud layer of order 500 metres, and by a more extended conditionally unstable layer with boundary-layer clouds up to 2 km. (Reference: IPCC Third Assessment Report, Working Group 1: The Scientific Basis, 7.2.2.3, https://archive.ipcc.ch/ipccreports/tar/wg1/273.htm). + The phrase "tendency_of_X" means derivative of X with respect to time. Air temperature is the bulk temperature of the air, not the surface (skin) temperature. The specification of a physical process by the phrase "due_to_" process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. "Boundary layer mixing" means turbulent motions that transport heat, water, momentum and chemical constituents within the atmospheric boundary layer and affect exchanges between the surface and the atmosphere. The atmospheric boundary layer is typically characterised by a well-mixed sub-cloud layer of order 500 metres, and by a more extended conditionally unstable layer with boundary-layer clouds up to 2 km. (Reference: IPCC Third Assessment Report, Working Group 1: The Scientific Basis, 7.2.2.3, https://archive.ipcc.ch/ipccreports/tar/wg1/273.htm). It is strongly recommended that a variable with this standard name should have the attribute units_metadata="temperature: difference", meaning that it refers to temperature differences and implying that the origin of the temperature scale is irrelevant, because it is essential to know whether a temperature is on-scale or a difference in order to convert the units correctly (cf. https://cfconventions.org/cf-conventions/cf-conventions.html#temperature-units). K s-1 - Air temperature is the bulk temperature of the air, not the surface (skin) temperature. The specification of a physical process by the phrase due_to_process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. "tendency_of_X" means derivative of X with respect to time. + Air temperature is the bulk temperature of the air, not the surface (skin) temperature. The specification of a physical process by the phrase due_to_process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. "tendency_of_X" means derivative of X with respect to time. It is strongly recommended that a variable with this standard name should have the attribute units_metadata="temperature: difference", meaning that it refers to temperature differences and implying that the origin of the temperature scale is irrelevant, because it is essential to know whether a temperature is on-scale or a difference in order to convert the units correctly (cf. https://cfconventions.org/cf-conventions/cf-conventions.html#temperature-units). K s-1 tnt - The specification of a physical process by the phrase due_to_process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. "tendency_of_X" means derivative of X with respect to time. Air temperature is the bulk temperature of the air, not the surface (skin) temperature. + The specification of a physical process by the phrase due_to_process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. "tendency_of_X" means derivative of X with respect to time. Air temperature is the bulk temperature of the air, not the surface (skin) temperature. It is strongly recommended that a variable with this standard name should have the attribute units_metadata="temperature: difference", meaning that it refers to temperature differences and implying that the origin of the temperature scale is irrelevant, because it is essential to know whether a temperature is on-scale or a difference in order to convert the units correctly (cf. https://cfconventions.org/cf-conventions/cf-conventions.html#temperature-units). K s-1 - The specification of a physical process by the phrase due_to_process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. "tendency_of_X" means derivative of X with respect to time. Air temperature is the bulk temperature of the air, not the surface (skin) temperature. + The specification of a physical process by the phrase due_to_process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. "tendency_of_X" means derivative of X with respect to time. Air temperature is the bulk temperature of the air, not the surface (skin) temperature. It is strongly recommended that a variable with this standard name should have the attribute units_metadata="temperature: difference", meaning that it refers to temperature differences and implying that the origin of the temperature scale is irrelevant, because it is essential to know whether a temperature is on-scale or a difference in order to convert the units correctly (cf. https://cfconventions.org/cf-conventions/cf-conventions.html#temperature-units). K s-1 - The phrase "tendency_of_X" means derivative of X with respect to time. Air temperature is the bulk temperature of the air, not the surface (skin) temperature. The specification of a physical process by the phrase "due_to_" process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. "Nonorographic" gravity waves refer to gravity waves which are not generated by flow over orography. The dissipation of gravity waves generates heating through an eddy heat flux convergence and through a viscous stress term. + The phrase "tendency_of_X" means derivative of X with respect to time. Air temperature is the bulk temperature of the air, not the surface (skin) temperature. The specification of a physical process by the phrase "due_to_" process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. "Nonorographic" gravity waves refer to gravity waves which are not generated by flow over orography. The dissipation of gravity waves generates heating through an eddy heat flux convergence and through a viscous stress term. It is strongly recommended that a variable with this standard name should have the attribute units_metadata="temperature: difference", meaning that it refers to temperature differences and implying that the origin of the temperature scale is irrelevant, because it is essential to know whether a temperature is on-scale or a difference in order to convert the units correctly (cf. https://cfconventions.org/cf-conventions/cf-conventions.html#temperature-units). K s-1 - The phrase "tendency_of_X" means derivative of X with respect to time. Air temperature is the bulk temperature of the air, not the surface (skin) temperature. The specification of a physical process by the phrase "due_to_" process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. "Orographic gravity waves" refer to gravity waves which are generated by flow over orography. The dissipation of gravity waves generates heating through an eddy heat flux convergence and through a viscous stress term. + The phrase "tendency_of_X" means derivative of X with respect to time. Air temperature is the bulk temperature of the air, not the surface (skin) temperature. The specification of a physical process by the phrase "due_to_" process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. "Orographic gravity waves" refer to gravity waves which are generated by flow over orography. The dissipation of gravity waves generates heating through an eddy heat flux convergence and through a viscous stress term. It is strongly recommended that a variable with this standard name should have the attribute units_metadata="temperature: difference", meaning that it refers to temperature differences and implying that the origin of the temperature scale is irrelevant, because it is essential to know whether a temperature is on-scale or a difference in order to convert the units correctly (cf. https://cfconventions.org/cf-conventions/cf-conventions.html#temperature-units). K s-1 tntdc - The specification of a physical process by the phrase due_to_process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. "tendency_of_X" means derivative of X with respect to time. Air temperature is the bulk temperature of the air, not the surface (skin) temperature. + The specification of a physical process by the phrase due_to_process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. "tendency_of_X" means derivative of X with respect to time. Air temperature is the bulk temperature of the air, not the surface (skin) temperature. It is strongly recommended that a variable with this standard name should have the attribute units_metadata="temperature: difference", meaning that it refers to temperature differences and implying that the origin of the temperature scale is irrelevant, because it is essential to know whether a temperature is on-scale or a difference in order to convert the units correctly (cf. https://cfconventions.org/cf-conventions/cf-conventions.html#temperature-units). K s-1 tntlw - The specification of a physical process by the phrase due_to_process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. "tendency_of_X" means derivative of X with respect to time. "longwave" means longwave radiation. Air temperature is the bulk temperature of the air, not the surface (skin) temperature. + The specification of a physical process by the phrase due_to_process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. "tendency_of_X" means derivative of X with respect to time. "longwave" means longwave radiation. Air temperature is the bulk temperature of the air, not the surface (skin) temperature. It is strongly recommended that a variable with this standard name should have the attribute units_metadata="temperature: difference", meaning that it refers to temperature differences and implying that the origin of the temperature scale is irrelevant, because it is essential to know whether a temperature is on-scale or a difference in order to convert the units correctly (cf. https://cfconventions.org/cf-conventions/cf-conventions.html#temperature-units). K s-1 - The specification of a physical process by the phrase due_to_process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. A phrase assuming_condition indicates that the named quantity is the value which would obtain if all aspects of the system were unaltered except for the assumption of the circumstances specified by the condition. "tendency_of_X" means derivative of X with respect to time. "longwave" means longwave radiation. Air temperature is the bulk temperature of the air, not the surface (skin) temperature. + The specification of a physical process by the phrase due_to_process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. A phrase assuming_condition indicates that the named quantity is the value which would obtain if all aspects of the system were unaltered except for the assumption of the circumstances specified by the condition. "tendency_of_X" means derivative of X with respect to time. "longwave" means longwave radiation. Air temperature is the bulk temperature of the air, not the surface (skin) temperature. It is strongly recommended that a variable with this standard name should have the attribute units_metadata="temperature: difference", meaning that it refers to temperature differences and implying that the origin of the temperature scale is irrelevant, because it is essential to know whether a temperature is on-scale or a difference in order to convert the units correctly (cf. https://cfconventions.org/cf-conventions/cf-conventions.html#temperature-units). K s-1 - The phrase "tendency_of_X" means derivative of X with respect to time. Air temperature is the bulk temperature of the air, not the surface (skin) temperature. The specification of a physical process by the phrase "due_to_" process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. The term "longwave" means longwave radiation. "Aerosol" means the system of suspended liquid or solid particles in air (except cloud droplets) and their carrier gas, the air itself. "Ambient_aerosol" means that the aerosol is measured or modelled at the ambient state of pressure, temperature and relative humidity that exists in its immediate environment. "Ambient aerosol particles" are aerosol particles that have taken up ambient water through hygroscopic growth. The extent of hygroscopic growth depends on the relative humidity and the composition of the particles. To specify the relative humidity and temperature at which the quantity described by the standard name applies, provide scalar coordinate variables with standard names of "relative_humidity" and "air_temperature". Volcanic aerosols include both volcanic ash and secondary products such as sulphate aerosols formed from gaseous emissions of volcanic eruptions. + The phrase "tendency_of_X" means derivative of X with respect to time. Air temperature is the bulk temperature of the air, not the surface (skin) temperature. The specification of a physical process by the phrase "due_to_" process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. The term "longwave" means longwave radiation. "Aerosol" means the system of suspended liquid or solid particles in air (except cloud droplets) and their carrier gas, the air itself. "Ambient_aerosol" means that the aerosol is measured or modelled at the ambient state of pressure, temperature and relative humidity that exists in its immediate environment. "Ambient aerosol particles" are aerosol particles that have taken up ambient water through hygroscopic growth. The extent of hygroscopic growth depends on the relative humidity and the composition of the particles. To specify the relative humidity and temperature at which the quantity described by the standard name applies, provide scalar coordinate variables with standard names of "relative_humidity" and "air_temperature". Volcanic aerosols include both volcanic ash and secondary products such as sulphate aerosols formed from gaseous emissions of volcanic eruptions. It is strongly recommended that a variable with this standard name should have the attribute units_metadata="temperature: difference", meaning that it refers to temperature differences and implying that the origin of the temperature scale is irrelevant, because it is essential to know whether a temperature is on-scale or a difference in order to convert the units correctly (cf. https://cfconventions.org/cf-conventions/cf-conventions.html#temperature-units). K s-1 - Air temperature is the bulk temperature of the air, not the surface (skin) temperature. The specification of a physical process by the phrase due_to_process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. "tendency_of_X" means derivative of X with respect to time. + Air temperature is the bulk temperature of the air, not the surface (skin) temperature. The specification of a physical process by the phrase due_to_process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. "tendency_of_X" means derivative of X with respect to time. It is strongly recommended that a variable with this standard name should have the attribute units_metadata="temperature: difference", meaning that it refers to temperature differences and implying that the origin of the temperature scale is irrelevant, because it is essential to know whether a temperature is on-scale or a difference in order to convert the units correctly (cf. https://cfconventions.org/cf-conventions/cf-conventions.html#temperature-units). K s-1 tntmc - The specification of a physical process by the phrase due_to_process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. "tendency_of_X" means derivative of X with respect to time. Air temperature is the bulk temperature of the air, not the surface (skin) temperature. + The specification of a physical process by the phrase due_to_process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. "tendency_of_X" means derivative of X with respect to time. Air temperature is the bulk temperature of the air, not the surface (skin) temperature. It is strongly recommended that a variable with this standard name should have the attribute units_metadata="temperature: difference", meaning that it refers to temperature differences and implying that the origin of the temperature scale is irrelevant, because it is essential to know whether a temperature is on-scale or a difference in order to convert the units correctly (cf. https://cfconventions.org/cf-conventions/cf-conventions.html#temperature-units). K s-1 - The specification of a physical process by the phrase due_to_process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. "tendency_of_X" means derivative of X with respect to time. Air temperature is the bulk temperature of the air, not the surface (skin) temperature. + The specification of a physical process by the phrase due_to_process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. "tendency_of_X" means derivative of X with respect to time. Air temperature is the bulk temperature of the air, not the surface (skin) temperature. It is strongly recommended that a variable with this standard name should have the attribute units_metadata="temperature: difference", meaning that it refers to temperature differences and implying that the origin of the temperature scale is irrelevant, because it is essential to know whether a temperature is on-scale or a difference in order to convert the units correctly (cf. https://cfconventions.org/cf-conventions/cf-conventions.html#temperature-units). K s-1 tntsw - The specification of a physical process by the phrase due_to_process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. "tendency_of_X" means derivative of X with respect to time. "shortwave" means shortwave radiation. Air temperature is the bulk temperature of the air, not the surface (skin) temperature. + The specification of a physical process by the phrase due_to_process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. "tendency_of_X" means derivative of X with respect to time. "shortwave" means shortwave radiation. Air temperature is the bulk temperature of the air, not the surface (skin) temperature. It is strongly recommended that a variable with this standard name should have the attribute units_metadata="temperature: difference", meaning that it refers to temperature differences and implying that the origin of the temperature scale is irrelevant, because it is essential to know whether a temperature is on-scale or a difference in order to convert the units correctly (cf. https://cfconventions.org/cf-conventions/cf-conventions.html#temperature-units). K s-1 - The specification of a physical process by the phrase due_to_process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. A phrase assuming_condition indicates that the named quantity is the value which would obtain if all aspects of the system were unaltered except for the assumption of the circumstances specified by the condition. "tendency_of_X" means derivative of X with respect to time. "shortwave" means shortwave radiation. Air temperature is the bulk temperature of the air, not the surface (skin) temperature. + The specification of a physical process by the phrase due_to_process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. A phrase assuming_condition indicates that the named quantity is the value which would obtain if all aspects of the system were unaltered except for the assumption of the circumstances specified by the condition. "tendency_of_X" means derivative of X with respect to time. "shortwave" means shortwave radiation. Air temperature is the bulk temperature of the air, not the surface (skin) temperature. It is strongly recommended that a variable with this standard name should have the attribute units_metadata="temperature: difference", meaning that it refers to temperature differences and implying that the origin of the temperature scale is irrelevant, because it is essential to know whether a temperature is on-scale or a difference in order to convert the units correctly (cf. https://cfconventions.org/cf-conventions/cf-conventions.html#temperature-units). K s-1 - The phrase "tendency_of_X" means derivative of X with respect to time. Air temperature is the bulk temperature of the air, not the surface (skin) temperature. The specification of a physical process by the phrase "due_to_" process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. The term "shortwave" means shortwave radiation. "Aerosol" means the system of suspended liquid or solid particles in air (except cloud droplets) and their carrier gas, the air itself. "Ambient aerosol particles" are aerosol particles that have taken up ambient water through hygroscopic growth. The extent of hygroscopic growth depends on the relative humidity and the composition of the particles. To specify the relative humidity and temperature at which the quantity described by the standard name applies, provide scalar coordinate variables with standard names of "relative_humidity" and "air_temperature". Volcanic aerosols include both volcanic ash and secondary products such as sulphate aerosols formed from gaseous emissions of volcanic eruptions. + The phrase "tendency_of_X" means derivative of X with respect to time. Air temperature is the bulk temperature of the air, not the surface (skin) temperature. The specification of a physical process by the phrase "due_to_" process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. The term "shortwave" means shortwave radiation. "Aerosol" means the system of suspended liquid or solid particles in air (except cloud droplets) and their carrier gas, the air itself. "Ambient aerosol particles" are aerosol particles that have taken up ambient water through hygroscopic growth. The extent of hygroscopic growth depends on the relative humidity and the composition of the particles. To specify the relative humidity and temperature at which the quantity described by the standard name applies, provide scalar coordinate variables with standard names of "relative_humidity" and "air_temperature". Volcanic aerosols include both volcanic ash and secondary products such as sulphate aerosols formed from gaseous emissions of volcanic eruptions. It is strongly recommended that a variable with this standard name should have the attribute units_metadata="temperature: difference", meaning that it refers to temperature differences and implying that the origin of the temperature scale is irrelevant, because it is essential to know whether a temperature is on-scale or a difference in order to convert the units correctly (cf. https://cfconventions.org/cf-conventions/cf-conventions.html#temperature-units). K s-1 - The phrase "tendency_of_X" means derivative of X with respect to time. Air temperature is the bulk temperature of the air, not the surface (skin) temperature. The specification of a physical process by the phrase "due_to_" process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. In an atmosphere model, stratiform cloud is that produced by large-scale convergence (not the convection schemes). "Precipitation" in the earth's atmosphere means precipitation of water in all phases. A variable with the standard name tendency_of_air_temperature_due_to_stratiform_cloud_and_precipitation should contain net latent heating effects of all processes which convert stratiform clouds and precipitation between water vapor, liquid or ice phases. + The phrase "tendency_of_X" means derivative of X with respect to time. Air temperature is the bulk temperature of the air, not the surface (skin) temperature. The specification of a physical process by the phrase "due_to_" process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. In an atmosphere model, stratiform cloud is that produced by large-scale convergence (not the convection schemes). "Precipitation" in the earth's atmosphere means precipitation of water in all phases. A variable with the standard name tendency_of_air_temperature_due_to_stratiform_cloud_and_precipitation should contain net latent heating effects of all processes which convert stratiform clouds and precipitation between water vapor, liquid or ice phases. It is strongly recommended that a variable with this standard name should have the attribute units_metadata="temperature: difference", meaning that it refers to temperature differences and implying that the origin of the temperature scale is irrelevant, because it is essential to know whether a temperature is on-scale or a difference in order to convert the units correctly (cf. https://cfconventions.org/cf-conventions/cf-conventions.html#temperature-units). K s-1 - The phrase "tendency_of_X" means derivative of X with respect to time. Air temperature is the bulk temperature of the air, not the surface (skin) temperature. The specification of a physical process by the phrase due_to_process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. In an atmosphere model, stratiform cloud is that produced by large-scale convergence (not the convection schemes). "Precipitation" in the earth's atmosphere means precipitation of water in all phases. "Boundary layer mixing" means turbulent motions that transport heat, water, momentum and chemical constituents within the atmospheric boundary layer and affect exchanges between the surface and the atmosphere. The atmospheric boundary layer is typically characterised by a well-mixed sub-cloud layer of order 500 metres, and by a more extended conditionally unstable layer with boundary-layer clouds up to 2 km. (Reference: IPCC Third Assessment Report, Working Group 1: The Scientific Basis, 7.2.2.3, https://archive.ipcc.ch/ipccreports/tar/wg1/273.htm). + The phrase "tendency_of_X" means derivative of X with respect to time. Air temperature is the bulk temperature of the air, not the surface (skin) temperature. The specification of a physical process by the phrase due_to_process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. In an atmosphere model, stratiform cloud is that produced by large-scale convergence (not the convection schemes). "Precipitation" in the earth's atmosphere means precipitation of water in all phases. "Boundary layer mixing" means turbulent motions that transport heat, water, momentum and chemical constituents within the atmospheric boundary layer and affect exchanges between the surface and the atmosphere. The atmospheric boundary layer is typically characterised by a well-mixed sub-cloud layer of order 500 metres, and by a more extended conditionally unstable layer with boundary-layer clouds up to 2 km. (Reference: IPCC Third Assessment Report, Working Group 1: The Scientific Basis, 7.2.2.3, https://archive.ipcc.ch/ipccreports/tar/wg1/273.htm). It is strongly recommended that a variable with this standard name should have the attribute units_metadata="temperature: difference", meaning that it refers to temperature differences and implying that the origin of the temperature scale is irrelevant, because it is essential to know whether a temperature is on-scale or a difference in order to convert the units correctly (cf. https://cfconventions.org/cf-conventions/cf-conventions.html#temperature-units). K s-1 tntlsp - The phrase "tendency_of_X" means derivative of X with respect to time. Air temperature is the bulk temperature of the air, not the surface (skin) temperature. The specification of a physical process by the phrase due_to_process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. In an atmosphere model, stratiform cloud is that produced by large-scale convergence (not the convection schemes). "Precipitation" in the earth's atmosphere means precipitation of water in all phases. + The phrase "tendency_of_X" means derivative of X with respect to time. Air temperature is the bulk temperature of the air, not the surface (skin) temperature. The specification of a physical process by the phrase due_to_process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. In an atmosphere model, stratiform cloud is that produced by large-scale convergence (not the convection schemes). "Precipitation" in the earth's atmosphere means precipitation of water in all phases. It is strongly recommended that a variable with this standard name should have the attribute units_metadata="temperature: difference", meaning that it refers to temperature differences and implying that the origin of the temperature scale is irrelevant, because it is essential to know whether a temperature is on-scale or a difference in order to convert the units correctly (cf. https://cfconventions.org/cf-conventions/cf-conventions.html#temperature-units). K s-1 - The specification of a physical process by the phrase due_to_process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. "tendency_of_X" means derivative of X with respect to time. Air temperature is the bulk temperature of the air, not the surface (skin) temperature. + The specification of a physical process by the phrase due_to_process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. "tendency_of_X" means derivative of X with respect to time. Air temperature is the bulk temperature of the air, not the surface (skin) temperature. It is strongly recommended that a variable with this standard name should have the attribute units_metadata="temperature: difference", meaning that it refers to temperature differences and implying that the origin of the temperature scale is irrelevant, because it is essential to know whether a temperature is on-scale or a difference in order to convert the units correctly (cf. https://cfconventions.org/cf-conventions/cf-conventions.html#temperature-units). @@ -31056,35 +31281,35 @@ http://vocab.nerc.ac.uk/collection/P01/current/TPHSDSZZ/6/. K s-1 - "tendency_of_X" means derivative of X with respect to time. Sea water temperature is the in situ temperature of the sea water. For observed data, depending on the period during which the observation was made, the measured in situ temperature was recorded against standard "scales". These historical scales include the International Practical Temperature Scale of 1948 (IPTS-48; 1948-1967), the International Practical Temperature Scale of 1968 (IPTS-68, Barber, 1969; 1968-1989) and the International Temperature Scale of 1990 (ITS-90, Saunders 1990; 1990 onwards). Conversion of data between these scales follows t68 = t48 - (4.4 x 10e-6) * t48(100 - t - 48); t90 = 0.99976 * t68. Observations made prior to 1948 (IPTS-48) have not been documented and therefore a conversion cannot be certain. Differences between t90 and t68 can be up to 0.01 at temperatures of 40 C and above; differences of 0.002-0.007 occur across the standard range of ocean temperatures (-10 - 30 C). The International Equation of State of Seawater 1980 (EOS-80, UNESCO, 1981) and the Practical Salinity Scale (PSS-78) were both based on IPTS-68, while the Thermodynamic Equation of Seawater 2010 (TEOS-10) is based on ITS-90. References: Barber, 1969, doi: 10.1088/0026-1394/5/2/001; UNESCO, 1981; Saunders, 1990, WOCE Newsletter, 10, September 1990. + The phrase "tendency_of_X" means derivative of X with respect to time. Sea water temperature is the in situ temperature of the sea water. For observed data, depending on the period during which the observation was made, the measured in situ temperature was recorded against standard "scales". These historical scales include the International Practical Temperature Scale of 1948 (IPTS-48; 1948-1967), the International Practical Temperature Scale of 1968 (IPTS-68, Barber, 1969; 1968-1989) and the International Temperature Scale of 1990 (ITS-90, Saunders 1990; 1990 onwards). Conversion of data between these scales follows t68 = t48 - (4.4 x 10e-6) * t48(100 - t - 48); t90 = 0.99976 * t68. Observations made prior to 1948 (IPTS-48) have not been documented and therefore a conversion cannot be certain. Differences between t90 and t68 can be up to 0.01 at temperatures of 40 C and above; differences of 0.002-0.007 occur across the standard range of ocean temperatures (-10 - 30 C). The International Equation of State of Seawater 1980 (EOS-80, UNESCO, 1981) and the Practical Salinity Scale (PSS-78) were both based on IPTS-68, while the Thermodynamic Equation of Seawater 2010 (TEOS-10) is based on ITS-90. References: Barber, 1969, doi: 10.1088/0026-1394/5/2/001; UNESCO, 1981; Saunders, 1990, WOCE Newsletter, 10, September 1990. It is strongly recommended that a variable with this standard name should have the attribute units_metadata="temperature: difference", meaning that it refers to temperature differences and implying that the origin of the temperature scale is irrelevant, because it is essential to know whether a temperature is on-scale or a difference in order to convert the units correctly (cf. https://cfconventions.org/cf-conventions/cf-conventions.html#temperature-units). K s-1 - "tendency_of_X" means derivative of X with respect to time. The specification of a physical process by the phrase due_to_process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. Sea water temperature is the in situ temperature of the sea water. For observed data, depending on the period during which the observation was made, the measured in situ temperature was recorded against standard "scales". These historical scales include the International Practical Temperature Scale of 1948 (IPTS-48; 1948-1967), the International Practical Temperature Scale of 1968 (IPTS-68, Barber, 1969; 1968-1989) and the International Temperature Scale of 1990 (ITS-90, Saunders 1990; 1990 onwards). Conversion of data between these scales follows t68 = t48 - (4.4 x 10e-6) * t48(100 - t - 48); t90 = 0.99976 * t68. Observations made prior to 1948 (IPTS-48) have not been documented and therefore a conversion cannot be certain. Differences between t90 and t68 can be up to 0.01 at temperatures of 40 C and above; differences of 0.002-0.007 occur across the standard range of ocean temperatures (-10 - 30 C). The International Equation of State of Seawater 1980 (EOS-80, UNESCO, 1981) and the Practical Salinity Scale (PSS-78) were both based on IPTS-68, while the Thermodynamic Equation of Seawater 2010 (TEOS-10) is based on ITS-90. References: Barber, 1969, doi: 10.1088/0026-1394/5/2/001; UNESCO, 1981; Saunders, 1990, WOCE Newsletter, 10, September 1990. + The phrase "tendency_of_X" means derivative of X with respect to time. The specification of a physical process by the phrase due_to_process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. Sea water temperature is the in situ temperature of the sea water. For observed data, depending on the period during which the observation was made, the measured in situ temperature was recorded against standard "scales". These historical scales include the International Practical Temperature Scale of 1948 (IPTS-48; 1948-1967), the International Practical Temperature Scale of 1968 (IPTS-68, Barber, 1969; 1968-1989) and the International Temperature Scale of 1990 (ITS-90, Saunders 1990; 1990 onwards). Conversion of data between these scales follows t68 = t48 - (4.4 x 10e-6) * t48(100 - t - 48); t90 = 0.99976 * t68. Observations made prior to 1948 (IPTS-48) have not been documented and therefore a conversion cannot be certain. Differences between t90 and t68 can be up to 0.01 at temperatures of 40 C and above; differences of 0.002-0.007 occur across the standard range of ocean temperatures (-10 - 30 C). The International Equation of State of Seawater 1980 (EOS-80, UNESCO, 1981) and the Practical Salinity Scale (PSS-78) were both based on IPTS-68, while the Thermodynamic Equation of Seawater 2010 (TEOS-10) is based on ITS-90. References: Barber, 1969, doi: 10.1088/0026-1394/5/2/001; UNESCO, 1981; Saunders, 1990, WOCE Newsletter, 10, September 1990. It is strongly recommended that a variable with this standard name should have the attribute units_metadata="temperature: difference", meaning that it refers to temperature differences and implying that the origin of the temperature scale is irrelevant, because it is essential to know whether a temperature is on-scale or a difference in order to convert the units correctly (cf. https://cfconventions.org/cf-conventions/cf-conventions.html#temperature-units). K s-1 - "tendency_of_X" means derivative of X with respect to time. The specification of a physical process by the phrase due_to_process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. "Horizontal mixing" means any horizontal transport other than by advection and parameterized eddy advection, usually represented as horizontal diffusion in ocean models. Sea water temperature is the in situ temperature of the sea water. For observed data, depending on the period during which the observation was made, the measured in situ temperature was recorded against standard "scales". These historical scales include the International Practical Temperature Scale of 1948 (IPTS-48; 1948-1967), the International Practical Temperature Scale of 1968 (IPTS-68, Barber, 1969; 1968-1989) and the International Temperature Scale of 1990 (ITS-90, Saunders 1990; 1990 onwards). Conversion of data between these scales follows t68 = t48 - (4.4 x 10e-6) * t48(100 - t - 48); t90 = 0.99976 * t68. Observations made prior to 1948 (IPTS-48) have not been documented and therefore a conversion cannot be certain. Differences between t90 and t68 can be up to 0.01 at temperatures of 40 C and above; differences of 0.002-0.007 occur across the standard range of ocean temperatures (-10 - 30 C). The International Equation of State of Seawater 1980 (EOS-80, UNESCO, 1981) and the Practical Salinity Scale (PSS-78) were both based on IPTS-68, while the Thermodynamic Equation of Seawater 2010 (TEOS-10) is based on ITS-90. References: Barber, 1969, doi: 10.1088/0026-1394/5/2/001; UNESCO, 1981; Saunders, 1990, WOCE Newsletter, 10, September 1990. + The phrase "tendency_of_X" means derivative of X with respect to time. The specification of a physical process by the phrase due_to_process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. "Horizontal mixing" means any horizontal transport other than by advection and parameterized eddy advection, usually represented as horizontal diffusion in ocean models. Sea water temperature is the in situ temperature of the sea water. For observed data, depending on the period during which the observation was made, the measured in situ temperature was recorded against standard "scales". These historical scales include the International Practical Temperature Scale of 1948 (IPTS-48; 1948-1967), the International Practical Temperature Scale of 1968 (IPTS-68, Barber, 1969; 1968-1989) and the International Temperature Scale of 1990 (ITS-90, Saunders 1990; 1990 onwards). Conversion of data between these scales follows t68 = t48 - (4.4 x 10e-6) * t48(100 - t - 48); t90 = 0.99976 * t68. Observations made prior to 1948 (IPTS-48) have not been documented and therefore a conversion cannot be certain. Differences between t90 and t68 can be up to 0.01 at temperatures of 40 C and above; differences of 0.002-0.007 occur across the standard range of ocean temperatures (-10 - 30 C). The International Equation of State of Seawater 1980 (EOS-80, UNESCO, 1981) and the Practical Salinity Scale (PSS-78) were both based on IPTS-68, while the Thermodynamic Equation of Seawater 2010 (TEOS-10) is based on ITS-90. References: Barber, 1969, doi: 10.1088/0026-1394/5/2/001; UNESCO, 1981; Saunders, 1990, WOCE Newsletter, 10, September 1990. It is strongly recommended that a variable with this standard name should have the attribute units_metadata="temperature: difference", meaning that it refers to temperature differences and implying that the origin of the temperature scale is irrelevant, because it is essential to know whether a temperature is on-scale or a difference in order to convert the units correctly (cf. https://cfconventions.org/cf-conventions/cf-conventions.html#temperature-units). K s-1 - "tendency_of_X" means derivative of X with respect to time. The specification of a physical process by the phrase due_to_process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. Parameterized eddy advection in an ocean model means the part due to a scheme representing parameterized eddy-induced advective effects not included in the resolved model velocity field. Parameterized eddy advection can be represented on various spatial scales and there are standard names for parameterized_mesoscale_eddy_advection and parameterized_submesoscale_eddy_advection which both contribute to the total parameterized eddy advection. Sea water temperature is the in situ temperature of the sea water. For observed data, depending on the period during which the observation was made, the measured in situ temperature was recorded against standard "scales". These historical scales include the International Practical Temperature Scale of 1948 (IPTS-48; 1948-1967), the International Practical Temperature Scale of 1968 (IPTS-68, Barber, 1969; 1968-1989) and the International Temperature Scale of 1990 (ITS-90, Saunders 1990; 1990 onwards). Conversion of data between these scales follows t68 = t48 - (4.4 x 10e-6) * t48(100 - t - 48); t90 = 0.99976 * t68. Observations made prior to 1948 (IPTS-48) have not been documented and therefore a conversion cannot be certain. Differences between t90 and t68 can be up to 0.01 at temperatures of 40 C and above; differences of 0.002-0.007 occur across the standard range of ocean temperatures (-10 - 30 C). The International Equation of State of Seawater 1980 (EOS-80, UNESCO, 1981) and the Practical Salinity Scale (PSS-78) were both based on IPTS-68, while the Thermodynamic Equation of Seawater 2010 (TEOS-10) is based on ITS-90. References: Barber, 1969, doi: 10.1088/0026-1394/5/2/001; UNESCO, 1981; Saunders, 1990, WOCE Newsletter, 10, September 1990. + The phrase "tendency_of_X" means derivative of X with respect to time. The specification of a physical process by the phrase due_to_process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. Parameterized eddy advection in an ocean model means the part due to a scheme representing parameterized eddy-induced advective effects not included in the resolved model velocity field. Parameterized eddy advection can be represented on various spatial scales and there are standard names for parameterized_mesoscale_eddy_advection and parameterized_submesoscale_eddy_advection which both contribute to the total parameterized eddy advection. Sea water temperature is the in situ temperature of the sea water. For observed data, depending on the period during which the observation was made, the measured in situ temperature was recorded against standard "scales". These historical scales include the International Practical Temperature Scale of 1948 (IPTS-48; 1948-1967), the International Practical Temperature Scale of 1968 (IPTS-68, Barber, 1969; 1968-1989) and the International Temperature Scale of 1990 (ITS-90, Saunders 1990; 1990 onwards). Conversion of data between these scales follows t68 = t48 - (4.4 x 10e-6) * t48(100 - t - 48); t90 = 0.99976 * t68. Observations made prior to 1948 (IPTS-48) have not been documented and therefore a conversion cannot be certain. Differences between t90 and t68 can be up to 0.01 at temperatures of 40 C and above; differences of 0.002-0.007 occur across the standard range of ocean temperatures (-10 - 30 C). The International Equation of State of Seawater 1980 (EOS-80, UNESCO, 1981) and the Practical Salinity Scale (PSS-78) were both based on IPTS-68, while the Thermodynamic Equation of Seawater 2010 (TEOS-10) is based on ITS-90. References: Barber, 1969, doi: 10.1088/0026-1394/5/2/001; UNESCO, 1981; Saunders, 1990, WOCE Newsletter, 10, September 1990. It is strongly recommended that a variable with this standard name should have the attribute units_metadata="temperature: difference", meaning that it refers to temperature differences and implying that the origin of the temperature scale is irrelevant, because it is essential to know whether a temperature is on-scale or a difference in order to convert the units correctly (cf. https://cfconventions.org/cf-conventions/cf-conventions.html#temperature-units). K s-1 - "tendency_of_X" means derivative of X with respect to time. The specification of a physical process by the phrase due_to_process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. "Vertical mixing" means any vertical transport other than by advection and parameterized eddy advection, represented by a combination of vertical diffusion, turbulent mixing and convection in ocean models. Sea water temperature is the in situ temperature of the sea water. For observed data, depending on the period during which the observation was made, the measured in situ temperature was recorded against standard "scales". These historical scales include the International Practical Temperature Scale of 1948 (IPTS-48; 1948-1967), the International Practical Temperature Scale of 1968 (IPTS-68, Barber, 1969; 1968-1989) and the International Temperature Scale of 1990 (ITS-90, Saunders 1990; 1990 onwards). Conversion of data between these scales follows t68 = t48 - (4.4 x 10e-6) * t48(100 - t - 48); t90 = 0.99976 * t68. Observations made prior to 1948 (IPTS-48) have not been documented and therefore a conversion cannot be certain. Differences between t90 and t68 can be up to 0.01 at temperatures of 40 C and above; differences of 0.002-0.007 occur across the standard range of ocean temperatures (-10 - 30 C). The International Equation of State of Seawater 1980 (EOS-80, UNESCO, 1981) and the Practical Salinity Scale (PSS-78) were both based on IPTS-68, while the Thermodynamic Equation of Seawater 2010 (TEOS-10) is based on ITS-90. References: Barber, 1969, doi: 10.1088/0026-1394/5/2/001; UNESCO, 1981; Saunders, 1990, WOCE Newsletter, 10, September 1990. + The phrase "tendency_of_X" means derivative of X with respect to time. The specification of a physical process by the phrase due_to_process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. "Vertical mixing" means any vertical transport other than by advection and parameterized eddy advection, represented by a combination of vertical diffusion, turbulent mixing and convection in ocean models. Sea water temperature is the in situ temperature of the sea water. For observed data, depending on the period during which the observation was made, the measured in situ temperature was recorded against standard "scales". These historical scales include the International Practical Temperature Scale of 1948 (IPTS-48; 1948-1967), the International Practical Temperature Scale of 1968 (IPTS-68, Barber, 1969; 1968-1989) and the International Temperature Scale of 1990 (ITS-90, Saunders 1990; 1990 onwards). Conversion of data between these scales follows t68 = t48 - (4.4 x 10e-6) * t48(100 - t - 48); t90 = 0.99976 * t68. Observations made prior to 1948 (IPTS-48) have not been documented and therefore a conversion cannot be certain. Differences between t90 and t68 can be up to 0.01 at temperatures of 40 C and above; differences of 0.002-0.007 occur across the standard range of ocean temperatures (-10 - 30 C). The International Equation of State of Seawater 1980 (EOS-80, UNESCO, 1981) and the Practical Salinity Scale (PSS-78) were both based on IPTS-68, while the Thermodynamic Equation of Seawater 2010 (TEOS-10) is based on ITS-90. References: Barber, 1969, doi: 10.1088/0026-1394/5/2/001; UNESCO, 1981; Saunders, 1990, WOCE Newsletter, 10, September 1990. It is strongly recommended that a variable with this standard name should have the attribute units_metadata="temperature: difference", meaning that it refers to temperature differences and implying that the origin of the temperature scale is irrelevant, because it is essential to know whether a temperature is on-scale or a difference in order to convert the units correctly (cf. https://cfconventions.org/cf-conventions/cf-conventions.html#temperature-units). @@ -31294,7 +31519,7 @@ http://vocab.nerc.ac.uk/collection/P01/current/TPHSDSZZ/6/. W m-1 K-1 - Thermal conductivity is the constant k in the formula q = -k grad T where q is the heat transfer per unit time per unit area of a surface normal to the direction of transfer and grad T is the temperature gradient. Thermal conductivity is a property of the material. + Thermal conductivity is the constant k in the formula q = -k grad T where q is the heat transfer per unit time per unit area of a surface normal to the direction of transfer and grad T is the temperature gradient. Thermal conductivity is a property of the material. It is strongly recommended that a variable with this standard name should have the attribute units_metadata="temperature: difference", meaning that it refers to temperature differences and implying that the origin of the temperature scale is irrelevant, because it is essential to know whether a temperature is on-scale or a difference in order to convert the units correctly (cf. https://cfconventions.org/cf-conventions/cf-conventions.html#temperature-units). @@ -31497,28 +31722,28 @@ http://vocab.nerc.ac.uk/collection/P01/current/TPHSDSZZ/6/. K - The brightness temperature of a body is the temperature of a black body which radiates the same power per unit solid angle per unit area. "toa" means top of atmosphere. + The brightness temperature of a body is the temperature of a black body which radiates the same power per unit solid angle per unit area. "toa" means top of atmosphere. It is strongly recommended that a variable with this standard name should have a units_metadata attribute, with one of the values "on-scale" or "difference", whichever is appropriate for the data, because it is essential to know whether the temperature is on-scale (meaning relative to the origin of the scale indicated by the units) or refers to temperature differences (implying that the origin of the temperature scale is irrevelant), in order to convert the units correctly (cf. https://cfconventions.org/cf-conventions/cf-conventions.html#temperature-units). K - The brightness temperature of a body is the temperature of a black body which radiates the same power per unit solid angle per unit area. A phrase assuming_condition indicates that the named quantity is the value which would obtain if all aspects of the system were unaltered except for the assumption of the circumstances specified by the condition. "toa" means top of atmosphere. + The brightness temperature of a body is the temperature of a black body which radiates the same power per unit solid angle per unit area. A phrase assuming_condition indicates that the named quantity is the value which would obtain if all aspects of the system were unaltered except for the assumption of the circumstances specified by the condition. "toa" means top of atmosphere. It is strongly recommended that a variable with this standard name should have a units_metadata attribute, with one of the values "on-scale" or "difference", whichever is appropriate for the data, because it is essential to know whether the temperature is on-scale (meaning relative to the origin of the scale indicated by the units) or refers to temperature differences (implying that the origin of the temperature scale is irrevelant), in order to convert the units correctly (cf. https://cfconventions.org/cf-conventions/cf-conventions.html#temperature-units). K - toa_brightness_temperature_bias_at_standard_scene_due_to_intercalibration is the difference between top-of-atmosphere (TOA) brightness temperatureof the reference sensor and TOA brightness temperature of themonitored sensor. This TOA brightness temperature difference is a measure of the calibration difference between the monitored and reference sensors. The standard scene is a target area with typical Earth surface and atmospheric conditions that is accepted as a reference. Brightness temperature of a body is the temperature of a black body which radiates the same power per unit solid angle per unit area at a given wavenumber. TOA brightness temperature of the standard scene is calculated using a radiative transfer simulation for a given viewing geometry. The resultant top-of-atmosphere spectral radiance is then integrated with each sensor's spectral response function and converted to equivalent brightness temperature. The specification of a physical process by the phrase "due_to_" process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. + toa_brightness_temperature_bias_at_standard_scene_due_to_intercalibration is the difference between top-of-atmosphere (TOA) brightness temperature of the reference sensor and TOA brightness temperature of the monitored sensor. This TOA brightness temperature difference is a measure of the calibration difference between the monitored and reference sensors. The standard scene is a target area with typical Earth surface and atmospheric conditions that is accepted as a reference. Brightness temperature of a body is the temperature of a black body which radiates the same power per unit solid angle per unit area at a given wavenumber. TOA brightness temperature of the standard scene is calculated using a radiative transfer simulation for a given viewing geometry. The resultant top-of-atmosphere spectral radiance is then integrated with each sensor's spectral response function and converted to equivalent brightness temperature. The specification of a physical process by the phrase "due_to_" process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. It is strongly recommended that a variable with this standard name should have the attribute units_metadata="temperature: difference", meaning that it refers to temperature differences and implying that the origin of the temperature scale is irrelevant, because it is essential to know whether a temperature is on-scale or a difference in order to convert the units correctly (cf. https://cfconventions.org/cf-conventions/cf-conventions.html#temperature-units). K - "toa" means top of atmosphere. The brightness temperature of a body is the temperature of a black body which radiates the same power per unit solid angle per unit area at a given wavenumber. The standard scene is a target area with typical Earth surface and atmospheric conditions that is accepted as a reference. The toa radiance of the standard scene is calculated using a radiative transfer model for a given viewing geometry. The resultant toa spectral radiance is then integrated with a sensor's spectral response function and converted to equivalent brightness temperature. + "toa" means top of atmosphere. The brightness temperature of a body is the temperature of a black body which radiates the same power per unit solid angle per unit area at a given wavenumber. The standard scene is a target area with typical Earth surface and atmospheric conditions that is accepted as a reference. The toa radiance of the standard scene is calculated using a radiative transfer model for a given viewing geometry. The resultant toa spectral radiance is then integrated with a sensor's spectral response function and converted to equivalent brightness temperature. It is strongly recommended that a variable with this standard name should have a units_metadata attribute, with one of the values "on-scale" or "difference", whichever is appropriate for the data, because it is essential to know whether the temperature is on-scale (meaning relative to the origin of the scale indicated by the units) or refers to temperature differences (implying that the origin of the temperature scale is irrevelant), in order to convert the units correctly (cf. https://cfconventions.org/cf-conventions/cf-conventions.html#temperature-units). @@ -31640,6 +31865,20 @@ http://vocab.nerc.ac.uk/collection/P01/current/TPHSDSZZ/6/. A phrase assuming_condition indicates that the named quantity is the value which would obtain if all aspects of the system were unaltered except for the assumption of the circumstances specified by the condition. "longwave" means longwave radiation. "toa" means top of atmosphere. The TOA outgoing longwave flux is the upwelling thermal radiative flux, often called the "outgoing longwave radiation" or "OLR". In accordance with common usage in geophysical disciplines, "flux" implies per unit area, called "flux density" in physics. + + W/m2 + + + A phrase assuming_condition indicates that the named quantity is the value which would obtain if all aspects of the system were unaltered except for the assumption of the circumstances specified by the condition. "longwave" means longwave radiation. "toa" means top of atmosphere. The TOA outgoing longwave flux is the upwelling thermal radiative flux, often called the "outgoing longwave radiation" or "OLR". In accordance with common usage in geophysical disciplines, "flux" implies per unit area, called "flux density" in physics. "Clear sky" means in the absence of clouds. This 3D ozone field acts as a reference ozone field in a diagnostic call to the model's radiation scheme. It is expressed in terms of mole fraction of ozone in air. It may be observation-based or model-derived. It may be from any time period. By using the same ozone reference in the diagnostic radiation call in two model simulations and calculating differences between the radiative flux diagnostics from the prognostic call to the radiation scheme and the diagnostic call to the radiation scheme with the ozone reference, an instantaneous radiative forcing for ozone can be calculated. + + + + W/m2 + + + A phrase assuming_condition indicates that the named quantity is the value which would obtain if all aspects of the system were unaltered except for the assumption of the circumstances specified by the condition. "longwave" means longwave radiation. "toa" means top of atmosphere. The TOA outgoing longwave flux is the upwelling thermal radiative flux, often called the "outgoing longwave radiation" or "OLR". In accordance with common usage in geophysical disciplines, "flux" implies per unit area, called "flux density" in physics. This 3D ozone field acts as a reference ozone field in a diagnostic call to the model's radiation scheme. It is expressed in terms of mole fraction of ozone in air. It may be observation-based or model-derived. It may be from any time period. By using the same ozone reference in the diagnostic radiation call in two model simulations and calculating differences between the radiative flux diagnostics from the prognostic call to the radiation scheme and the diagnostic call to the radiation scheme with the ozone reference, an instantaneous radiative forcing for ozone can be calculated. + + W m-2 @@ -31717,6 +31956,13 @@ http://vocab.nerc.ac.uk/collection/P01/current/TPHSDSZZ/6/. The abbreviation "toa" means top of atmosphere. The term "shortwave" means shortwave radiation. The TOA outgoing shortwave flux is the reflected and scattered solar radiative flux i.e. the "upwelling" TOA shortwave flux, sometimes called the "outgoing shortwave radiation" or "OSR". In accordance with common usage in geophysical disciplines, "flux" implies per unit area, called "flux density" in physics. A phrase "assuming_condition" indicates that the named quantity is the value which would obtain if all aspects of the system were unaltered except for the assumption of the circumstances specified by the condition. "Clear sky" means in the absence of clouds. + + W/m2 + + + The abbreviation "toa" means top of atmosphere. The term "shortwave" means shortwave radiation. The TOA outgoing shortwave flux is the reflected and scattered solar radiative flux i.e. the "upwelling" TOA shortwave flux, sometimes called the "outgoing shortwave radiation" or "OSR". In accordance with common usage in geophysical disciplines, "flux" implies per unit area, called "flux density" in physics. A phrase "assuming_condition" indicates that the named quantity is the value which would obtain if all aspects of the system were unaltered except for the assumption of the circumstances specified by the condition. "Clear sky" means in the absence of clouds. This 3D ozone field acts as a reference ozone field in a diagnostic call to the model's radiation scheme. It is expressed in terms of mole fraction of ozone in air. It may be observation-based or model-derived. It may be from any time period. By using the same ozone reference in the diagnostic radiation call in two model simulations and calculating differences between the radiative flux diagnostics from the prognostic call to the radiation scheme and the diagnostic call to the radiation scheme with the ozone reference, an instantaneous radiative forcing for ozone can be calculated. + + W m-2 @@ -31724,6 +31970,13 @@ http://vocab.nerc.ac.uk/collection/P01/current/TPHSDSZZ/6/. The abbreviation "toa" means top of atmosphere. The term "shortwave" means shortwave radiation. The TOA outgoing shortwave flux is the reflected and scattered solar radiative flux i.e. the "upwelling" TOA shortwave flux, sometimes called the "outgoing shortwave radiation" or "OSR". In accordance with common usage in geophysical disciplines, "flux" implies per unit area, called "flux density" in physics. A phrase "assuming_condition" indicates that the named quantity is the value which would obtain if all aspects of the system were unaltered except for the assumption of the circumstances specified by the condition. + + W/m2 + + + The abbreviation "toa" means top of atmosphere. The term "shortwave" means shortwave radiation. The TOA outgoing shortwave flux is the reflected and scattered solar radiative flux i.e. the "upwelling" TOA shortwave flux, sometimes called the "outgoing shortwave radiation" or "OSR". In accordance with common usage in geophysical disciplines, "flux" implies per unit area, called "flux density" in physics. A phrase "assuming_condition" indicates that the named quantity is the value which would obtain if all aspects of the system were unaltered except for the assumption of the circumstances specified by the condition. This 3D ozone field acts as a reference ozone field in a diagnostic call to the model's radiation scheme. It is expressed in terms of mole fraction of ozone in air. It may be observation-based or model-derived. It may be from any time period. By using the same ozone reference in the diagnostic radiation call in two model simulations and calculating differences between the radiative flux diagnostics from the prognostic call to the radiation scheme and the diagnostic call to the radiation scheme with the ozone reference, an instantaneous radiative forcing for ozone can be calculated. + + W m-2 @@ -31777,7 +32030,7 @@ http://vocab.nerc.ac.uk/collection/P01/current/TPHSDSZZ/6/. K - The quantity with standard name tropical_cyclone_eye_brightness_temperature is the warmest brightness temperature value in the eye region of a tropical cyclone (0 - 24 km from the storm center) derived using the Advanced Dvorak Technique, based on satellite observations. Reference: Olander, T. L., & Velden, C. S., The Advanced Dvorak Technique: Continued Development of an Objective Scheme to Estimate Tropical Cyclone Intensity Using Geostationary Infrared Satellite Imagery (2007). American Meteorological Society Weather and Forecasting, 22, 287-298. The brightness temperature of a body is the temperature of a black body which radiates the same power per unit solid angle per unit area. + The quantity with standard name tropical_cyclone_eye_brightness_temperature is the warmest brightness temperature value in the eye region of a tropical cyclone (0 - 24 km from the storm center) derived using the Advanced Dvorak Technique, based on satellite observations. Reference: Olander, T. L., & Velden, C. S., The Advanced Dvorak Technique: Continued Development of an Objective Scheme to Estimate Tropical Cyclone Intensity Using Geostationary Infrared Satellite Imagery (2007). American Meteorological Society Weather and Forecasting, 22, 287-298. The brightness temperature of a body is the temperature of a black body which radiates the same power per unit solid angle per unit area. It is strongly recommended that a variable with this standard name should have a units_metadata attribute, with one of the values "on-scale" or "difference", whichever is appropriate for the data, because it is essential to know whether the temperature is on-scale (meaning relative to the origin of the scale indicated by the units) or refers to temperature differences (implying that the origin of the temperature scale is irrevelant), in order to convert the units correctly (cf. https://cfconventions.org/cf-conventions/cf-conventions.html#temperature-units). @@ -31819,7 +32072,7 @@ http://vocab.nerc.ac.uk/collection/P01/current/TPHSDSZZ/6/. K - Air temperature is the bulk temperature of the air, not the surface (skin) temperature. + Air temperature is the bulk temperature of the air, not the surface (skin) temperature. It is strongly recommended that a variable with this standard name should have a units_metadata attribute, with one of the values "on-scale" or "difference", whichever is appropriate for the data, because it is essential to know whether the temperature is on-scale (meaning relative to the origin of the scale indicated by the units) or refers to temperature differences (implying that the origin of the temperature scale is irrevelant), in order to convert the units correctly (cf. https://cfconventions.org/cf-conventions/cf-conventions.html#temperature-units). @@ -31959,7 +32212,7 @@ http://vocab.nerc.ac.uk/collection/P01/current/TPHSDSZZ/6/. degree_C - Universal Thermal Comfort Index (UTCI) is an equivalent temperature of the actual thermal condition. Reference: utci.org. It is the air temperature of a reference condition causing the same dynamic physiological response in a human body considering its energy budget, physiology and clothing adaptation. + Universal Thermal Comfort Index (UTCI) is an equivalent temperature of the actual thermal condition. Reference: utci.org. It is the air temperature of a reference condition causing the same dynamic physiological response in a human body considering its energy budget, physiology and clothing adaptation. It is strongly recommended that a variable with this standard name should have a units_metadata attribute, with one of the values "on-scale" or "difference", whichever is appropriate for the data, because it is essential to know whether the temperature is on-scale (meaning relative to the origin of the scale indicated by the units) or refers to temperature differences (implying that the origin of the temperature scale is irrevelant), in order to convert the units correctly (cf. https://cfconventions.org/cf-conventions/cf-conventions.html#temperature-units). @@ -31971,16 +32224,16 @@ http://vocab.nerc.ac.uk/collection/P01/current/TPHSDSZZ/6/. s-1 - 45 + The quantity with standard name upward_derivative_of_eastward_wind is the derivative of the eastward component of wind with respect to height. The phrase "component_derivative_of_X" means derivative of X with respect to distance in the component direction, which may be "northward", "southward", "eastward", "westward", "upward", "downward", "x" or "y". The last two indicate derivatives along the axes of the grid, in the case where they are not true longitude and latitude. A positive value indicates that X is increasing with distance along the positive direction of the axis. Wind is defined as a two-dimensional (horizontal) air velocity vector, with no vertical component. (Vertical motion in the atmosphere has the standard name "upward_air_velocity"). s-1 - 46 + - The quantity with standard name upward_derivative_of_northward_wind is the derivative of the northward component of wind with respect to height. The phrase "component_derivative_of_X" means derivative of X with respect to distance in the component direction, which may be "northward", "southward", "eastward", "westward", "upward", "downward", "x" or "y". The last two indicate derivatives along the axes of the grid, in the case where they are not true longitude and latitude. A positive value indicates that X is increasing with distance along the positive direction of the axis. Wind is defined as a two-dimensional (horizontal) air velocity vector, with no vertical component. (Vertical motion in the atmosphere has the standard name "upward_air_velocity"). + The quantity with standard name upward_derivative_of_northward_wind is the derivative of the northward component of wind speed with respect to height. The phrase "component_derivative_of_X" means derivative of X with respect to distance in the component direction, which may be "northward", "southward", "eastward", "westward", "upward", "downward", "x" or "y". The last two indicate derivatives along the axes of the grid, in the case where they are not true longitude and latitude. A positive value indicates that X is increasing with distance along the positive direction of the axis. Wind is defined as a two-dimensional (horizontal) air velocity vector, with no vertical component. (Vertical motion in the atmosphere has the standard name "upward_air_velocity"). @@ -32193,6 +32446,20 @@ http://vocab.nerc.ac.uk/collection/P01/current/TPHSDSZZ/6/. The term "longwave" means longwave radiation. Upwelling radiation is radiation from below. It does not mean "net upward". The sign convention is that "upwelling" is positive upwards and "downwelling" is positive downwards. When thought of as being incident on a surface, a radiative flux is sometimes called "irradiance". In addition, it is identical with the quantity measured by a cosine-collector light-meter and sometimes called "vector irradiance". In accordance with common usage in geophysical disciplines, "flux" implies per unit area, called "flux density" in physics. A phrase assuming_condition indicates that the named quantity is the value which would obtain if all aspects of the system were unaltered except for the assumption of the circumstances specified by the condition. "Clear sky" means in the absence of clouds. + + W/m2 + + + The term "longwave" means longwave radiation. Upwelling radiation is radiation from below. It does not mean "net upward". The sign convention is that "upwelling" is positive upwards and "downwelling" is positive downwards. When thought of as being incident on a surface, a radiative flux is sometimes called "irradiance". In addition, it is identical with the quantity measured by a cosine-collector light-meter and sometimes called "vector irradiance". In accordance with common usage in geophysical disciplines, "flux" implies per unit area, called "flux density" in physics. A phrase assuming_condition indicates that the named quantity is the value which would obtain if all aspects of the system were unaltered except for the assumption of the circumstances specified by the condition. "Clear sky" means in the absence of clouds. This 3D ozone field acts as a reference ozone field in a diagnostic call to the model's radiation scheme. It is expressed in terms of mole fraction of ozone in air. It may be observation-based or model-derived. It may be from any time period. By using the same ozone reference in the diagnostic radiation call in two model simulations and calculating differences between the radiative flux diagnostics from the prognostic call to the radiation scheme and the diagnostic call to the radiation scheme with the ozone reference, an instantaneous radiative forcing for ozone can be calculated. + + + + W/m2 + + + The term "longwave" means longwave radiation. Upwelling radiation is radiation from below. It does not mean "net upward". The sign convention is that "upwelling" is positive upwards and "downwelling" is positive downwards. When thought of as being incident on a surface, a radiative flux is sometimes called "irradiance". In addition, it is identical with the quantity measured by a cosine-collector light-meter and sometimes called "vector irradiance". In accordance with common usage in geophysical disciplines, "flux" implies per unit area, called "flux density" in physics. A phrase assuming_condition indicates that the named quantity is the value which would obtain if all aspects of the system were unaltered except for the assumption of the circumstances specified by the condition. This 3D ozone field acts as a reference ozone field in a diagnostic call to the model's radiation scheme. It is expressed in terms of mole fraction of ozone in air. It may be observation-based or model-derived. It may be from any time period. By using the same ozone reference in the diagnostic radiation call in two model simulations and calculating differences between the radiative flux diagnostics from the prognostic call to the radiation scheme and the diagnostic call to the radiation scheme with the ozone reference, an instantaneous radiative forcing for ozone can be calculated. + + W m-2 sr-1 @@ -32242,6 +32509,20 @@ http://vocab.nerc.ac.uk/collection/P01/current/TPHSDSZZ/6/. Upwelling radiation is radiation from below. It does not mean "net upward". The sign convention is that "upwelling" is positive upwards and "downwelling" is positive downwards. The term "shortwave" means shortwave radiation. When thought of as being incident on a surface, a radiative flux is sometimes called "irradiance". In addition, it is identical with the quantity measured by a cosine-collector light-meter and sometimes called "vector irradiance". In accordance with common usage in geophysical disciplines, "flux" implies per unit area, called "flux density" in physics. A phrase "assuming_condition" indicates that the named quantity is the value which would obtain if all aspects of the system were unaltered except for the assumption of the circumstances specified by the condition. "Clear sky" means in the absence of clouds. + + W/m2 + + + Upwelling radiation is radiation from below. It does not mean "net upward". The sign convention is that "upwelling" is positive upwards and "downwelling" is positive downwards. The term "shortwave" means shortwave radiation. When thought of as being incident on a surface, a radiative flux is sometimes called "irradiance". In addition, it is identical with the quantity measured by a cosine-collector light-meter and sometimes called "vector irradiance". In accordance with common usage in geophysical disciplines, "flux" implies per unit area, called "flux density" in physics. A phrase "assuming_condition" indicates that the named quantity is the value which would obtain if all aspects of the system were unaltered except for the assumption of the circumstances specified by the condition. "Clear sky" means in the absence of clouds. This 3D ozone field acts as a reference ozone field in a diagnostic call to the model's radiation scheme. It is expressed in terms of mole fraction of ozone in air. It may be observation-based or model-derived. It may be from any time period. By using the same ozone reference in the diagnostic radiation call in two model simulations and calculating differences between the radiative flux diagnostics from the prognostic call to the radiation scheme and the diagnostic call to the radiation scheme with the ozone reference, an instantaneous radiative forcing for ozone can be calculated. + + + + W/m2 + + + Upwelling radiation is radiation from below. It does not mean "net upward". The sign convention is that "upwelling" is positive upwards and "downwelling" is positive downwards. The term "shortwave" means shortwave radiation. When thought of as being incident on a surface, a radiative flux is sometimes called "irradiance". In addition, it is identical with the quantity measured by a cosine-collector light-meter and sometimes called "vector irradiance". In accordance with common usage in geophysical disciplines, "flux" implies per unit area, called "flux density" in physics. A phrase "assuming_condition" indicates that the named quantity is the value which would obtain if all aspects of the system were unaltered except for the assumption of the circumstances specified by the condition. This 3D ozone field acts as a reference ozone field in a diagnostic call to the model's radiation scheme. It is expressed in terms of mole fraction of ozone in air. It may be observation-based or model-derived. It may be from any time period. By using the same ozone reference in the diagnostic radiation call in two model simulations and calculating differences between the radiative flux diagnostics from the prognostic call to the radiation scheme and the diagnostic call to the radiation scheme with the ozone reference, an instantaneous radiative forcing for ozone can be calculated. + + W m-2 sr-1 @@ -32351,7 +32632,7 @@ http://vocab.nerc.ac.uk/collection/P01/current/TPHSDSZZ/6/. K 12 - The virtual temperature of air is the temperature at which the dry air constituent of a parcel of moist air would have the same density as the moist air at the same pressure. + The virtual temperature of air is the temperature at which the dry air constituent of a parcel of moist air would have the same density as the moist air at the same pressure. It is strongly recommended that a variable with this standard name should have a units_metadata attribute, with one of the values "on-scale" or "difference", whichever is appropriate for the data, because it is essential to know whether the temperature is on-scale (meaning relative to the origin of the scale indicated by the units) or refers to temperature differences (implying that the origin of the temperature scale is irrevelant), in order to convert the units correctly (cf. https://cfconventions.org/cf-conventions/cf-conventions.html#temperature-units). @@ -32361,6 +32642,34 @@ http://vocab.nerc.ac.uk/collection/P01/current/TPHSDSZZ/6/. The visibility is the distance at which something can be seen. + + m-1 + + + The volume scattering/absorption/attenuation coefficient is the fractional change of radiative flux per unit path length due to the stated process. Coefficients with canonical units of m2 s-1 i.e. multiplied by density have standard names with specific_ instead of volume_. A scattering_angle should not be specified with this quantity. The scattering/absorption/attenuation coefficient is assumed to be an integral over all wavelengths, unless a coordinate of radiation_wavelength is included to specify the wavelength. Radiative flux is the sum of shortwave and longwave radiative fluxes. "Aerosol" means the system of suspended liquid or solid particles in air (except cloud droplets) and their carrier gas, the air itself. To specify the relative humidity at which the sample was measured, provide a scalar coordinate variable with the standard name of "relative_humidity". The specification of a physical process by the phrase "due_to_" process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. + + + + m-1 + + + The volume scattering/absorption/attenuation coefficient is the fractional change of radiative flux per unit path length due to the stated process. Coefficients with canonical units of m2 s-1 i.e. multiplied by density have standard names with specific_ instead of volume_. A scattering_angle should not be specified with this quantity. The scattering/absorption/attenuation coefficient is assumed to be an integral over all wavelengths, unless a coordinate of radiation_wavelength is included to specify the wavelength. Radiative flux is the sum of shortwave and longwave radiative fluxes. "Aerosol" means the system of suspended liquid or solid particles in air (except cloud droplets) and their carrier gas, the air itself. To specify the relative humidity at which the sample was measured, provide a scalar coordinate variable with the standard name of "relative_humidity". The specification of a physical process by the phrase "due_to_" process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. "Standard_temperature_and_pressure" refer to a reference volume at 273.15 K temperature and 1013.25 hPa pressure. + + + + m-1 + + + The volume scattering/absorption/attenuation coefficient is the fractional change of radiative flux per unit path length due to the stated process. Coefficients with canonical units of m2 s-1 i.e. multiplied by density have standard names with specific_ instead of volume_. A scattering_angle should not be specified with this quantity. The scattering/absorption/attenuation coefficient is assumed to be an integral over all wavelengths, unless a coordinate of radiation_wavelength is included to specify the wavelength. Radiative flux is the sum of shortwave and longwave radiative fluxes. "Aerosol" means the system of suspended liquid or solid particles in air (except cloud droplets) and their carrier gas, the air itself. "Ambient aerosol particles" are aerosol particles that have taken up ambient water through hygroscopic growth. The extent of hygroscopic growth depends on the relative humidity and the composition of the particles. To specify the relative humidity at which the sample was measured, provide a scalar coordinate variable with the standard name of "relative_humidity". The specification of a physical process by the phrase "due_to_" process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. + + + + m-1 + + + The volume scattering/absorption/attenuation coefficient is the fractional change of radiative flux per unit path length due to the stated process. Coefficients with canonical units of m2 s-1 i.e. multiplied by density have standard names with "specific_" instead of "volume_". A scattering_angle should not be specified with this quantity. The scattering/absorption/attenuation coefficient is assumed to be an integral over all wavelengths, unless a coordinate of radiation_wavelength is included to specify the wavelength. Radiative flux is the sum of shortwave and longwave radiative fluxes. "Aerosol" means the system of suspended liquid or solid particles in air (except cloud droplets) and their carrier gas, the air itself. "Ambient_aerosol" means that the aerosol is measured or modelled at the ambient state of pressure, temperature and relative humidity that exists in its immediate environment. "Ambient aerosol particles" are aerosol particles that have taken up ambient water through hygroscopic growth. The extent of hygroscopic growth depends on the relative humidity and the composition of the particles. To specify the relative humidity at which the sample was measured, provide a scalar coordinate variable with the standard name of "relative_humidity". The specification of a physical process by the phrase "due_to_" process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. "Standard_temperature_and_pressure" refer to a reference volume at 273.15 K temperature and 1013.25 hPa pressure. + + m-1 @@ -32368,6 +32677,195 @@ http://vocab.nerc.ac.uk/collection/P01/current/TPHSDSZZ/6/. The volume scattering/absorption/attenuation coefficient is the fractional change of radiative flux per unit path length due to the stated process. Coefficients with canonical units of m2 s-1 i.e. multiplied by density have standard names with "specific_" instead of "volume_". The scattering/absorption/attenuation coefficient is assumed to be an integral over all wavelengths unless a coordinate of "radiation_wavelength" or "radiation_frequency" is included to specify the wavelength. Radiative flux is the sum of shortwave and longwave radiative fluxes. "Aerosol" means the system of suspended liquid or solid particles in air (except cloud droplets) and their carrier gas, the air itself. "Dried_aerosol" means that the aerosol sample has been dried from the ambient state, but that the dry state (relative humidity less than 40 per cent) has not necessarily been reached. To specify the relative humidity at which the sample was measured, provide a scalar coordinate variable with the standard name of "relative_humidity". The specification of a physical process by the phrase "due_to_" process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. + + m-1 + + + The volume scattering/absorption/attenuation coefficient is the fractional change of radiative flux per unit path length due to the stated process. Coefficients with canonical units of m2 s-1 i.e. multiplied by density have standard names with specific_ instead of volume_. A scattering_angle should not be specified with this quantity. The scattering/absorption/attenuation coefficient is assumed to be an integral over all wavelengths, unless a coordinate of radiation_wavelength is included to specify the wavelength. Radiative flux is the sum of shortwave and longwave radiative fluxes. "Aerosol" means the system of suspended liquid or solid particles in air (except cloud droplets) and their carrier gas, the air itself. "Dried_aerosol_particles" means that the aerosol sample has been dried from the ambient state before sizing, but that the dry state (relative humidity less than 40 per cent) has not necessarily been reached. To specify the relative humidity at which the sample was measured, provide a scalar coordinate variable with the standard name of "relative_humidity". The specification of a physical process by the phrase "due_to_" process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. "Standard_temperature_and_pressure" refer to a reference volume at 273.15 K temperature and 1013.25 hPa pressure. + + + + m-1 + + + The volume scattering/absorption/attenuation coefficient is the fractional change of radiative flux per unit path length due to the stated process. Coefficients with canonical units of m2 s-1 i.e. multiplied by density have standard names with specific_ instead of volume_. A scattering_angle should not be specified with this quantity. The scattering/absorption/attenuation coefficient is assumed to be an integral over all wavelengths, unless a coordinate of radiation_wavelength is included to specify the wavelength. Radiative flux is the sum of shortwave and longwave radiative fluxes. "Aerosol" means the system of suspended liquid or solid particles in air (except cloud droplets) and their carrier gas, the air itself. Aerosol particles take up ambient water (a process known as hygroscopic growth) depending on the relative humidity and the composition of the particles. "Dry aerosol particles" means aerosol particles without any water uptake. The specification of a physical process by the phrase "due_to_" process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. + + + + m-1 + + + The volume scattering/absorption/attenuation coefficient is the fractional change of radiative flux per unit path length due to the stated process. Coefficients with canonical units of m2 s-1 i.e. multiplied by density have standard names with specific_ instead of volume_. A scattering_angle should not be specified with this quantity. The scattering/absorption/attenuation coefficient is assumed to be an integral over all wavelengths, unless a coordinate of radiation_wavelength is included to specify the wavelength. Radiative flux is the sum of shortwave and longwave radiative fluxes. "Aerosol" means the system of suspended liquid or solid particles in air (except cloud droplets) and their carrier gas, the air itself. Aerosol particles take up ambient water (a process known as hygroscopic growth) depending on the relative humidity and the composition of the particles. "Dry aerosol particles" means aerosol particles without any water uptake. The specification of a physical process by the phrase "due_to_" process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. "Standard_temperature_and_pressure" refer to a reference volume at 273.15 K temperature and 1013.25 hPa pressure. + + + + m-1 + + + The volume scattering/absorption/attenuation coefficient is the fractional change of radiative flux per unit path length due to the stated process. Coefficients with canonical units of m2 s-1 i.e. multiplied by density have standard names with specific_ instead of volume_. A scattering_angle should not be specified with this quantity. The scattering/absorption/attenuation coefficient is assumed to be an integral over all wavelengths, unless a coordinate of radiation_wavelength is included to specify the wavelength. Radiative flux is the sum of shortwave and longwave radiative fluxes. "Aerosol" means the system of suspended liquid or solid particles in air (except cloud droplets) and their carrier gas, the air itself. To specify the relative humidity at which the sample was measured, provide a scalar coordinate variable with the standard name of "relative_humidity". The specification of a physical process by the phrase "due_to_" process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. "Pm10 aerosol particles" means atmospheric particulate compounds with an aerodynamic diameter of less than or equal to 10 micrometers. + + + + m-1 + + + The volume scattering/absorption/attenuation coefficient is the fractional change of radiative flux per unit path length due to the stated process. Coefficients with canonical units of m2 s-1 i.e. multiplied by density have standard names with specific_ instead of volume_. A scattering_angle should not be specified with this quantity. The scattering/absorption/attenuation coefficient is assumed to be an integral over all wavelengths, unless a coordinate of radiation_wavelength is included to specify the wavelength. Radiative flux is the sum of shortwave and longwave radiative fluxes. "Aerosol" means the system of suspended liquid or solid particles in air (except cloud droplets) and their carrier gas, the air itself. To specify the relative humidity at which the sample was measured, provide a scalar coordinate variable with the standard name of "relative_humidity". The specification of a physical process by the phrase "due_to_" process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. "Pm10 aerosol particles" means atmospheric particulate compounds with an aerodynamic diameter of less than or equal to 10 micrometers. "Standard_temperature_and_pressure" refer to a reference volume at 273.15 K temperature and 1013.25 hPa pressure. + + + + m-1 + + + The volume scattering/absorption/attenuation coefficient is the fractional change of radiative flux per unit path length due to the stated process. Coefficients with canonical units of m2 s-1 i.e. multiplied by density have standard names with specific_ instead of volume_. A scattering_angle should not be specified with this quantity. The scattering/absorption/attenuation coefficient is assumed to be an integral over all wavelengths, unless a coordinate of radiation_wavelength is included to specify the wavelength. Radiative flux is the sum of shortwave and longwave radiative fluxes. "Aerosol" means the system of suspended liquid or solid particles in air (except cloud droplets) and their carrier gas, the air itself. "Ambient aerosol particles" are aerosol particles that have taken up ambient water through hygroscopic growth. The extent of hygroscopic growth depends on the relative humidity and the composition of the particles. To specify the relative humidity at which the sample was measured, provide a scalar coordinate variable with the standard name of "relative_humidity". The specification of a physical process by the phrase "due_to_" process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. "Pm10 aerosol particles" means atmospheric particulate compounds with an aerodynamic diameter of less than or equal to 10 micrometers. + + + + m-1 + + + The volume scattering/absorption/attenuation coefficient is the fractional change of radiative flux per unit path length due to the stated process. Coefficients with canonical units of m2 s-1 i.e. multiplied by density have standard names with specific_ instead of volume_. A scattering_angle should not be specified with this quantity. The scattering/absorption/attenuation coefficient is assumed to be an integral over all wavelengths, unless a coordinate of radiation_wavelength is included to specify the wavelength. Radiative flux is the sum of shortwave and longwave radiative fluxes. "Aerosol" means the system of suspended liquid or solid particles in air (except cloud droplets) and their carrier gas, the air itself. "Ambient aerosol particles" are aerosol particles that have taken up ambient water through hygroscopic growth. The extent of hygroscopic growth depends on the relative humidity and the composition of the particles. To specify the relative humidity at which the sample was measured, provide a scalar coordinate variable with the standard name of "relative_humidity". The specification of a physical process by the phrase "due_to_" process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. "Pm10 aerosol particles" means atmospheric particulate compounds with an aerodynamic diameter of less than or equal to 10 micrometers. "Standard_temperature_and_pressure" refer to a reference volume at 273.15 K temperature and 1013.25 hPa pressure. + + + + m-1 + + + The volume scattering/absorption/attenuation coefficient is the fractional change of radiative flux per unit path length due to the stated process. Coefficients with canonical units of m2 s-1 i.e. multiplied by density have standard names with specific_ instead of volume_. A scattering_angle should not be specified with this quantity. The scattering/absorption/attenuation coefficient is assumed to be an integral over all wavelengths, unless a coordinate of radiation_wavelength is included to specify the wavelength. Radiative flux is the sum of shortwave and longwave radiative fluxes. "Aerosol" means the system of suspended liquid or solid particles in air (except cloud droplets) and their carrier gas, the air itself. "Dried_aerosol_particles" means that the aerosol sample has been dried from the ambient state before sizing, but that the dry state (relative humidity less than 40 per cent) has not necessarily been reached. To specify the relative humidity at which the sample was measured, provide a scalar coordinate variable with the standard name of "relative_humidity". The specification of a physical process by the phrase "due_to_" process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. "Pm10 aerosol particles" means atmospheric particulate compounds with an aerodynamic diameter of less than or equal to 10 micrometers. + + + + m-1 + + + The volume scattering/absorption/attenuation coefficient is the fractional change of radiative flux per unit path length due to the stated process. Coefficients with canonical units of m2 s-1 i.e. multiplied by density have standard names with specific_ instead of volume_. A scattering_angle should not be specified with this quantity. The scattering/absorption/attenuation coefficient is assumed to be an integral over all wavelengths, unless a coordinate of radiation_wavelength is included to specify the wavelength. Radiative flux is the sum of shortwave and longwave radiative fluxes. "Aerosol" means the system of suspended liquid or solid particles in air (except cloud droplets) and their carrier gas, the air itself. "Dried_aerosol_particles" means that the aerosol sample has been dried from the ambient state before sizing, but that the dry state (relative humidity less than 40 per cent) has not necessarily been reached. To specify the relative humidity at which the sample was measured, provide a scalar coordinate variable with the standard name of "relative_humidity". The specification of a physical process by the phrase "due_to_" process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. "Pm10 aerosol particles" means atmospheric particulate compounds with an aerodynamic diameter of less than or equal to 10 micrometers. "Standard_temperature_and_pressure" refer to a reference volume at 273.15 K temperature and 1013.25 hPa pressure. + + + + m-1 + + + The volume scattering/absorption/attenuation coefficient is the fractional change of radiative flux per unit path length due to the stated process. Coefficients with canonical units of m2 s-1 i.e. multiplied by density have standard names with specific_ instead of volume_. A scattering_angle should not be specified with this quantity. The scattering/absorption/attenuation coefficient is assumed to be an integral over all wavelengths, unless a coordinate of radiation_wavelength is included to specify the wavelength. Radiative flux is the sum of shortwave and longwave radiative fluxes. "Aerosol" means the system of suspended liquid or solid particles in air (except cloud droplets) and their carrier gas, the air itself. Aerosol particles take up ambient water (a process known as hygroscopic growth) depending on the relative humidity and the composition of the particles. "Dry aerosol particles" means aerosol particles without any water uptake. The specification of a physical process by the phrase "due_to_" process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. "Pm10 aerosol particles" means atmospheric particulate compounds with an aerodynamic diameter of less than or equal to 10 micrometers. + + + + m-1 + + + The volume scattering/absorption/attenuation coefficient is the fractional change of radiative flux per unit path length due to the stated process. Coefficients with canonical units of m2 s-1 i.e. multiplied by density have standard names with specific_ instead of volume_. A scattering_angle should not be specified with this quantity. The scattering/absorption/attenuation coefficient is assumed to be an integral over all wavelengths, unless a coordinate of radiation_wavelength is included to specify the wavelength. Radiative flux is the sum of shortwave and longwave radiative fluxes. "Aerosol" means the system of suspended liquid or solid particles in air (except cloud droplets) and their carrier gas, the air itself. Aerosol particles take up ambient water (a process known as hygroscopic growth) depending on the relative humidity and the composition of the particles. "Dry aerosol particles" means aerosol particles without any water uptake. The specification of a physical process by the phrase "due_to_" process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. "Pm10 aerosol particles" means atmospheric particulate compounds with an aerodynamic diameter of less than or equal to 10 micrometers. "Standard_temperature_and_pressure" refer to a reference volume at 273.15 K temperature and 1013.25 hPa pressure. + + + + m-1 + + + The volume scattering/absorption/attenuation coefficient is the fractional change of radiative flux per unit path length due to the stated process. Coefficients with canonical units of m2 s-1 i.e. multiplied by density have standard names with specific_ instead of volume_. A scattering_angle should not be specified with this quantity. The scattering/absorption/attenuation coefficient is assumed to be an integral over all wavelengths, unless a coordinate of radiation_wavelength is included to specify the wavelength. Radiative flux is the sum of shortwave and longwave radiative fluxes. "Aerosol" means the system of suspended liquid or solid particles in air (except cloud droplets) and their carrier gas, the air itself. To specify the relative humidity at which the sample was measured, provide a scalar coordinate variable with the standard name of "relative_humidity". The specification of a physical process by the phrase "due_to_" process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. "Pm1 aerosol particles" means atmospheric particulate compounds with an aerodynamic diameter of less than or equal to 1 micrometers. + + + + m-1 + + + The volume scattering/absorption/attenuation coefficient is the fractional change of radiative flux per unit path length due to the stated process. Coefficients with canonical units of m2 s-1 i.e. multiplied by density have standard names with specific_ instead of volume_. A scattering_angle should not be specified with this quantity. The scattering/absorption/attenuation coefficient is assumed to be an integral over all wavelengths, unless a coordinate of radiation_wavelength is included to specify the wavelength. Radiative flux is the sum of shortwave and longwave radiative fluxes. "Aerosol" means the system of suspended liquid or solid particles in air (except cloud droplets) and their carrier gas, the air itself. To specify the relative humidity at which the sample was measured, provide a scalar coordinate variable with the standard name of "relative_humidity". The specification of a physical process by the phrase "due_to_" process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. "Pm1 aerosol particles" means atmospheric particulate compounds with an aerodynamic diameter of less than or equal to 1 micrometers. "Standard_temperature_and_pressure" refer to a reference volume at 273.15 K temperature and 1013.25 hPa pressure. + + + + m-1 + + + The volume scattering/absorption/attenuation coefficient is the fractional change of radiative flux per unit path length due to the stated process. Coefficients with canonical units of m2 s-1 i.e. multiplied by density have standard names with specific_ instead of volume_. A scattering_angle should not be specified with this quantity. The scattering/absorption/attenuation coefficient is assumed to be an integral over all wavelengths, unless a coordinate of radiation_wavelength is included to specify the wavelength. Radiative flux is the sum of shortwave and longwave radiative fluxes. "Aerosol" means the system of suspended liquid or solid particles in air (except cloud droplets) and their carrier gas, the air itself. "Ambient aerosol particles" are aerosol particles that have taken up ambient water through hygroscopic growth. The extent of hygroscopic growth depends on the relative humidity and the composition of the particles. To specify the relative humidity at which the sample was measured, provide a scalar coordinate variable with the standard name of "relative_humidity". The specification of a physical process by the phrase "due_to_" process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. "Pm1 aerosol particles" means atmospheric particulate compounds with an aerodynamic diameter of less than or equal to 1 micrometers. + + + + m-1 + + + The volume scattering/absorption/attenuation coefficient is the fractional change of radiative flux per unit path length due to the stated process. Coefficients with canonical units of m2 s-1 i.e. multiplied by density have standard names with specific_ instead of volume_. A scattering_angle should not be specified with this quantity. The scattering/absorption/attenuation coefficient is assumed to be an integral over all wavelengths, unless a coordinate of radiation_wavelength is included to specify the wavelength. Radiative flux is the sum of shortwave and longwave radiative fluxes. "Aerosol" means the system of suspended liquid or solid particles in air (except cloud droplets) and their carrier gas, the air itself. "Ambient aerosol particles" are aerosol particles that have taken up ambient water through hygroscopic growth. The extent of hygroscopic growth depends on the relative humidity and the composition of the particles. To specify the relative humidity at which the sample was measured, provide a scalar coordinate variable with the standard name of "relative_humidity". The specification of a physical process by the phrase "due_to_" process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. "Pm1 aerosol particles" means atmospheric particulate compounds with an aerodynamic diameter of less than or equal to 1 micrometers. "Standard_temperature_and_pressure" refer to a reference volume at 273.15 K temperature and 1013.25 hPa pressure. + + + + m-1 + + + The volume scattering/absorption/attenuation coefficient is the fractional change of radiative flux per unit path length due to the stated process. Coefficients with canonical units of m2 s-1 i.e. multiplied by density have standard names with specific_ instead of volume_. A scattering_angle should not be specified with this quantity. The scattering/absorption/attenuation coefficient is assumed to be an integral over all wavelengths, unless a coordinate of radiation_wavelength is included to specify the wavelength. Radiative flux is the sum of shortwave and longwave radiative fluxes. "Aerosol" means the system of suspended liquid or solid particles in air (except cloud droplets) and their carrier gas, the air itself. "Dried_aerosol_particles" means that the aerosol sample has been dried from the ambient state before sizing, but that the dry state (relative humidity less than 40 per cent) has not necessarily been reached. To specify the relative humidity at which the sample was measured, provide a scalar coordinate variable with the standard name of "relative_humidity". The specification of a physical process by the phrase "due_to_" process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. "Pm1 aerosol particles" means atmospheric particulate compounds with an aerodynamic diameter of less than or equal to 1 micrometers. + + + + m-1 + + + The volume scattering/absorption/attenuation coefficient is the fractional change of radiative flux per unit path length due to the stated process. Coefficients with canonical units of m2 s-1 i.e. multiplied by density have standard names with specific_ instead of volume_. A scattering_angle should not be specified with this quantity. The scattering/absorption/attenuation coefficient is assumed to be an integral over all wavelengths, unless a coordinate of radiation_wavelength is included to specify the wavelength. Radiative flux is the sum of shortwave and longwave radiative fluxes. "Aerosol" means the system of suspended liquid or solid particles in air (except cloud droplets) and their carrier gas, the air itself. "Dried_aerosol_particles" means that the aerosol sample has been dried from the ambient state before sizing, but that the dry state (relative humidity less than 40 per cent) has not necessarily been reached. To specify the relative humidity at which the sample was measured, provide a scalar coordinate variable with the standard name of "relative_humidity". The specification of a physical process by the phrase "due_to_" process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. "Pm1 aerosol particles" means atmospheric particulate compounds with an aerodynamic diameter of less than or equal to 1 micrometers. "Standard_temperature_and_pressure" refer to a reference volume at 273.15 K temperature and 1013.25 hPa pressure. + + + + m-1 + + + The volume scattering/absorption/attenuation coefficient is the fractional change of radiative flux per unit path length due to the stated process. Coefficients with canonical units of m2 s-1 i.e. multiplied by density have standard names with specific_ instead of volume_. A scattering_angle should not be specified with this quantity. The scattering/absorption/attenuation coefficient is assumed to be an integral over all wavelengths, unless a coordinate of radiation_wavelength is included to specify the wavelength. Radiative flux is the sum of shortwave and longwave radiative fluxes. "Aerosol" means the system of suspended liquid or solid particles in air (except cloud droplets) and their carrier gas, the air itself. Aerosol particles take up ambient water (a process known as hygroscopic growth) depending on the relative humidity and the composition of the particles. "Dry aerosol particles" means aerosol particles without any water uptake. The specification of a physical process by the phrase "due_to_" process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. "Pm1 aerosol particles" means atmospheric particulate compounds with an aerodynamic diameter of less than or equal to 1 micrometers. + + + + m-1 + + + The volume scattering/absorption/attenuation coefficient is the fractional change of radiative flux per unit path length due to the stated process. Coefficients with canonical units of m2 s-1 i.e. multiplied by density have standard names with specific_ instead of volume_. A scattering_angle should not be specified with this quantity. The scattering/absorption/attenuation coefficient is assumed to be an integral over all wavelengths, unless a coordinate of radiation_wavelength is included to specify the wavelength. Radiative flux is the sum of shortwave and longwave radiative fluxes. "Aerosol" means the system of suspended liquid or solid particles in air (except cloud droplets) and their carrier gas, the air itself. Aerosol particles take up ambient water (a process known as hygroscopic growth) depending on the relative humidity and the composition of the particles. "Dry aerosol particles" means aerosol particles without any water uptake. The specification of a physical process by the phrase "due_to_" process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. "Pm1 aerosol particles" means atmospheric particulate compounds with an aerodynamic diameter of less than or equal to 1 micrometers. "Standard_temperature_and_pressure" refer to a reference volume at 273.15 K temperature and 1013.25 hPa pressure. + + + + m-1 + + + The volume scattering coefficient is the fractional change of radiative flux per unit path length due to the stated process. Coefficients with canonical units of m2 s-1 i.e. multiplied by density have standard names with specific_ instead of volume_. A scattering_angle should not be specified with this quantity. The scattering/absorption/attenuation coefficient is assumed to be an integral over all wavelengths, unless a coordinate of radiation_wavelength is included to specify the wavelength. Radiative flux is the sum of shortwave and longwave radiative fluxes. "Aerosol" means the system of suspended liquid or solid particles in air (except cloud droplets) and their carrier gas, the air itself. To specify the relative humidity at which the sample was measured, provide a scalar coordinate variable with the standard name of "relative_humidity". The specification of a physical process by the phrase "due_to_" process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. "Pm2p5 aerosol particles" means atmospheric particulate compounds with an aerodynamic diameter of less than or equal to 2.5 micrometers. + + + + m-1 + + + The volume scattering/absorption/attenuation coefficient is the fractional change of radiative flux per unit path length due to the stated process. Coefficients with canonical units of m2 s-1 i.e. multiplied by density have standard names with specific_ instead of volume_. A scattering_angle should not be specified with this quantity. The scattering/absorption/attenuation coefficient is assumed to be an integral over all wavelengths, unless a coordinate of radiation_wavelength is included to specify the wavelength. "Aerosol" means the system of suspended liquid or solid particles in air (except cloud droplets) and their carrier gas, the air itself. To specify the relative humidity at which the sample was measured, provide a scalar coordinate variable with the standard name of "relative_humidity". The specification of a physical process by the phrase "due_to_" process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. "Pm10 aerosol particles" means atmospheric particulate compounds with an aerodynamic diameter of less than or equal to 2.5 micrometers. "Standard_temperature_and_pressure" refer to a reference volume at 273.15 K temperature and 1013.25 hPa pressure. + + + + m-1 + + + The volume scattering/absorption/attenuation coefficient is the fractional change of radiative flux per unit path length due to the stated process. Coefficients with canonical units of m2 s-1 i.e. multiplied by density have standard names with specific_ instead of volume_. A scattering_angle should not be specified with this quantity. The scattering/absorption/attenuation coefficient is assumed to be an integral over all wavelengths, unless a coordinate of radiation_wavelength is included to specify the wavelength. Radiative flux is the sum of shortwave and longwave radiative fluxes. "Aerosol" means the system of suspended liquid or solid particles in air (except cloud droplets) and their carrier gas, the air itself. "Ambient aerosol particles" are aerosol particles that have taken up ambient water through hygroscopic growth. The extent of hygroscopic growth depends on the relative humidity and the composition of the particles. To specify the relative humidity at which the sample was measured, provide a scalar coordinate variable with the standard name of "relative_humidity". The specification of a physical process by the phrase "due_to_" process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. "Pm2p5 aerosol particles" means atmospheric particulate compounds with an aerodynamic diameter of less than or equal to 2.5 micrometers. + + + + m-1 + + + The volume scattering/absorption/attenuation coefficient is the fractional change of radiative flux per unit path length due to the stated process. Coefficients with canonical units of m2 s-1 i.e. multiplied by density have standard names with specific_ instead of volume_. A scattering_angle should not be specified with this quantity. The scattering/absorption/attenuation coefficient is assumed to be an integral over all wavelengths, unless a coordinate of radiation_wavelength is included to specify the wavelength. Radiative flux is the sum of shortwave and longwave radiative fluxes. "Aerosol" means the system of suspended liquid or solid particles in air (except cloud droplets) and their carrier gas, the air itself. "Ambient aerosol particles" are aerosol particles that have taken up ambient water through hygroscopic growth. The extent of hygroscopic growth depends on the relative humidity and the composition of the particles. To specify the relative humidity at which the sample was measured, provide a scalar coordinate variable with the standard name of "relative_humidity". The specification of a physical process by the phrase "due_to_" process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. "Pm2p5 aerosol particles" means atmospheric particulate compounds with an aerodynamic diameter of less than or equal to 2.5 micrometers. "Standard_temperature_and_pressure" refer to a reference volume at 273.15 K temperature and 1013.25 hPa pressure. + + + + m-1 + + + The volume scattering/absorption/attenuation coefficient is the fractional change of radiative flux per unit path length due to the stated process. Coefficients with canonical units of m2 s-1 i.e. multiplied by density have standard names with specific_ instead of volume_. A scattering_angle should not be specified with this quantity. The scattering/absorption/attenuation coefficient is assumed to be an integral over all wavelengths, unless a coordinate of radiation_wavelength is included to specify the wavelength. Radiative flux is the sum of shortwave and longwave radiative fluxes. "Aerosol" means the system of suspended liquid or solid particles in air (except cloud droplets) and their carrier gas, the air itself. "Dried_aerosol_particles" means that the aerosol sample has been dried from the ambient state before sizing, but that the dry state (relative humidity less than 40 per cent) has not necessarily been reached. To specify the relative humidity at which the sample was measured, provide a scalar coordinate variable with the standard name of "relative_humidity". The specification of a physical process by the phrase "due_to_" process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. "Pm2p5 aerosol particles" means atmospheric particulate compounds with an aerodynamic diameter of less than or equal to 2.5 micrometers. + + + + m-1 + + + The volume scattering/absorption/attenuation coefficient is the fractional change of radiative flux per unit path length due to the stated process. Coefficients with canonical units of m2 s-1 i.e. multiplied by density have standard names with specific_ instead of volume_. A scattering_angle should not be specified with this quantity. The scattering/absorption/attenuation coefficient is assumed to be an integral over all wavelengths, unless a coordinate of radiation_wavelength is included to specify the wavelength. Radiative flux is the sum of shortwave and longwave radiative fluxes. "Aerosol" means the system of suspended liquid or solid particles in air (except cloud droplets) and their carrier gas, the air itself. "Dried_aerosol_particles" means that the aerosol sample has been dried from the ambient state before sizing, but that the dry state (relative humidity less than 40 per cent) has not necessarily been reached. To specify the relative humidity at which the sample was measured, provide a scalar coordinate variable with the standard name of "relative_humidity". The specification of a physical process by the phrase "due_to_" process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. "Pm2p5 aerosol particles" means atmospheric particulate compounds with an aerodynamic diameter of less than or equal to 2.5 micrometers. "Standard_temperature_and_pressure" refer to a reference volume at 273.15 K temperature and 1013.25 hPa pressure. + + + + m-1 + + + The volume scattering/absorption/attenuation coefficient is the fractional change of radiative flux per unit path length due to the stated process. Coefficients with canonical units of m2 s-1 i.e. multiplied by density have standard names with specific_ instead of volume_. A scattering_angle should not be specified with this quantity. The scattering/absorption/attenuation coefficient is assumed to be an integral over all wavelengths, unless a coordinate of radiation_wavelength is included to specify the wavelength. Radiative flux is the sum of shortwave and longwave radiative fluxes. "Aerosol" means the system of suspended liquid or solid particles in air (except cloud droplets) and their carrier gas, the air itself. Aerosol particles take up ambient water (a process known as hygroscopic growth) depending on the relative humidity and the composition of the particles. "Dry aerosol particles" means aerosol particles without any water uptake. The specification of a physical process by the phrase "due_to_" process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. "Pm2p5 aerosol particles" means atmospheric particulate compounds with an aerodynamic diameter of less than or equal to 2.5 micrometers. + + + + m-1 + + + The volume scattering/absorption/attenuation coefficient is the fractional change of radiative flux per unit path length due to the stated process. Coefficients with canonical units of m2 s-1 i.e. multiplied by density have standard names with specific_ instead of volume_. A scattering_angle should not be specified with this quantity. The scattering/absorption/attenuation coefficient is assumed to be an integral over all wavelengths, unless a coordinate of radiation_wavelength is included to specify the wavelength. Radiative flux is the sum of shortwave and longwave radiative fluxes. "Aerosol" means the system of suspended liquid or solid particles in air (except cloud droplets) and their carrier gas, the air itself. Aerosol particles take up ambient water (a process known as hygroscopic growth) depending on the relative humidity and the composition of the particles. "Dry aerosol particles" means aerosol particles without any water uptake. The specification of a physical process by the phrase "due_to_" process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. "Pm2p5 aerosol particles" means atmospheric particulate compounds with an aerodynamic diameter of less than or equal to 2.5 micrometers. "Standard_temperature_and_pressure" refer to a reference volume at 273.15 K temperature and 1013.25 hPa pressure. + + m-1 @@ -32410,6 +32908,34 @@ http://vocab.nerc.ac.uk/collection/P01/current/TPHSDSZZ/6/. Volume backwards scattering coefficient by ranging instrument is the fraction of radiative flux, per unit path length and per unit solid angle, scattered at 180 degrees angle respect to the incident radiation and obtained through ranging techniques like lidar and radar. Backwards scattering coefficient is assumed to be related to the same wavelength of incident radiation. "Ambient_aerosol" means that the aerosol is measured or modelled at the ambient state of pressure, temperature and relative humidity that exists in its immediate environment. "Ambient aerosol particles" are aerosol particles that have taken up ambient water through hygroscopic growth. The extent of hygroscopic growth depends on the relative humidity and the composition of the particles. The specification of a physical process by the phrase "due_to_" process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. + + m-1 + + + The volume scattering/absorption/attenuation coefficient is the fractional change of radiative flux per unit path length due to the stated process. Coefficients with canonical units of m2 s-1 i.e. multiplied by density have standard names with specific_ instead of volume_. Backwards scattering refers to the sum of scattering into all backward angles i.e. scattering_angle exceeds pi/2 radians. A scattering_angle should not be specified with this quantity. The scattering/absorption/attenuation coefficient is assumed to be an integral over all wavelengths, unless a coordinate of radiation_wavelength is included to specify the wavelength. Radiative flux is the sum of shortwave and longwave radiative fluxes. "Aerosol" means the system of suspended liquid or solid particles in air (except cloud droplets) and their carrier gas, the air itself. To specify the relative humidity at which the sample was measured, provide a scalar coordinate variable with the standard name of "relative_humidity". The specification of a physical process by the phrase "due_to_" process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. + + + + m-1 + + + The volume scattering/absorption/attenuation coefficient is the fractional change of radiative flux per unit path length due to the stated process. Coefficients with canonical units of m2 s-1 i.e. multiplied by density have standard names with specific_ instead of volume_. Backwards scattering refers to the sum of scattering into all backward angles i.e. scattering_angle exceeds pi/2 radians. A scattering_angle should not be specified with this quantity. The scattering/absorption/attenuation coefficient is assumed to be an integral over all wavelengths, unless a coordinate of radiation_wavelength is included to specify the wavelength. Radiative flux is the sum of shortwave and longwave radiative fluxes. "Aerosol" means the system of suspended liquid or solid particles in air (except cloud droplets) and their carrier gas, the air itself. To specify the relative humidity at which the sample was measured, provide a scalar coordinate variable with the standard name of "relative_humidity". The specification of a physical process by the phrase "due_to_" process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. "Standard_temperature_and_pressure" refer to a reference volume at 273.15 K temperature and 1013.25 hPa pressure. + + + + m-1 + + + The volume scattering/absorption/attenuation coefficient is the fractional change of radiative flux per unit path length due to the stated process. Coefficients with canonical units of m2 s-1 i.e. multiplied by density have standard names with specific_ instead of volume_. Backwards scattering refers to the sum of scattering into all backward angles i.e. scattering_angle exceeds pi/2 radians. A scattering_angle should not be specified with this quantity. The scattering/absorption/attenuation coefficient is assumed to be an integral over all wavelengths, unless a coordinate of radiation_wavelength is included to specify the wavelength. Radiative flux is the sum of shortwave and longwave radiative fluxes. "Aerosol" means the system of suspended liquid or solid particles in air (except cloud droplets) and their carrier gas, the air itself. "Ambient aerosol particles" are aerosol particles that have taken up ambient water through hygroscopic growth. The extent of hygroscopic growth depends on the relative humidity and the composition of the particles. To specify the relative humidity at which the sample was measured, provide a scalar coordinate variable with the standard name of "relative_humidity". The specification of a physical process by the phrase "due_to_" process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. + + + + m-1 + + + The volume scattering/absorption/attenuation coefficient is the fractional change of radiative flux per unit path length due to the stated process. Coefficients with canonical units of m2 s-1 i.e. multiplied by density have standard names with specific_ instead of volume_. Backwards scattering refers to the sum of scattering into all backward angles i.e. scattering_angle exceeds pi/2 radians. A scattering_angle should not be specified with this quantity. The scattering/absorption/attenuation coefficient is assumed to be an integral over all wavelengths, unless a coordinate of radiation_wavelength is included to specify the wavelength. Radiative flux is the sum of shortwave and longwave radiative fluxes. "Aerosol" means the system of suspended liquid or solid particles in air (except cloud droplets) and their carrier gas, the air itself. "Ambient aerosol particles" are aerosol particles that have taken up ambient water through hygroscopic growth. The extent of hygroscopic growth depends on the relative humidity and the composition of the particles. To specify the relative humidity at which the sample was measured, provide a scalar coordinate variable with the standard name of "relative_humidity". The specification of a physical process by the phrase "due_to_" process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. "Standard_temperature_and_pressure" refer to a reference volume at 273.15 K temperature and 1013.25 hPa pressure. + + m-1 @@ -32417,6 +32943,195 @@ http://vocab.nerc.ac.uk/collection/P01/current/TPHSDSZZ/6/. The volume scattering/absorption/attenuation coefficient is the fractional change of radiative flux per unit path length due to the stated process. Coefficients with canonical units of m2 s-1 i.e. multiplied by density have standard names with specific_ instead of volume_. Backwards scattering refers to the sum of scattering into all backward angles i.e. scattering_angle exceeds pi/2 radians. A scattering_angle should not be specified with this quantity. The scattering/absorption/attenuation coefficient is assumed to be an integral over all wavelengths, unless a coordinate of radiation_wavelength is included to specify the wavelength. Radiative flux is the sum of shortwave and longwave radiative fluxes. "Aerosol" means the system of suspended liquid or solid particles in air (except cloud droplets) and their carrier gas, the air itself. "Dried_aerosol" means that the aerosol sample has been dried from the ambient state, but that the dry state (relative humidity less than 40 per cent) has not necessarily been reached. To specify the relative humidity at which the sample was measured, provide a scalar coordinate variable with the standard name of "relative_humidity". The specification of a physical process by the phrase "due_to_" process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. + + m-1 + + + The volume scattering/absorption/attenuation coefficient is the fractional change of radiative flux per unit path length due to the stated process. Coefficients with canonical units of m2 s-1 i.e. multiplied by density have standard names with specific_ instead of volume_. Backwards scattering refers to the sum of scattering into all backward angles i.e. scattering_angle exceeds pi/2 radians. A scattering_angle should not be specified with this quantity. The scattering/absorption/attenuation coefficient is assumed to be an integral over all wavelengths, unless a coordinate of radiation_wavelength is included to specify the wavelength. Radiative flux is the sum of shortwave and longwave radiative fluxes. "Aerosol" means the system of suspended liquid or solid particles in air (except cloud droplets) and their carrier gas, the air itself. "Dried_aerosol_particles" means that the aerosol sample has been dried from the ambient state before sizing, but that the dry state (relative humidity less than 40 per cent) has not necessarily been reached. To specify the relative humidity at which the sample was measured, provide a scalar coordinate variable with the standard name of "relative_humidity". The specification of a physical process by the phrase "due_to_" process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. "Standard_temperature_and_pressure" refer to a reference volume at 273.15 K temperature and 1013.25 hPa pressure. + + + + m-1 + + + The volume scattering/absorption/attenuation coefficient is the fractional change of radiative flux per unit path length due to the stated process. Coefficients with canonical units of m2 s-1 i.e. multiplied by density have standard names with specific_ instead of volume_. Backwards scattering refers to the sum of scattering into all backward angles i.e. scattering_angle exceeds pi/2 radians. A scattering_angle should not be specified with this quantity. The scattering/absorption/attenuation coefficient is assumed to be an integral over all wavelengths, unless a coordinate of radiation_wavelength is included to specify the wavelength. Radiative flux is the sum of shortwave and longwave radiative fluxes. "Aerosol" means the system of suspended liquid or solid particles in air (except cloud droplets) and their carrier gas, the air itself. Aerosol particles take up ambient water (a process known as hygroscopic growth) depending on the relative humidity and the composition of the particles. "Dry aerosol particles" means aerosol particles without any water uptake. The specification of a physical process by the phrase "due_to_" process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. + + + + m-1 + + + The volume scattering/absorption/attenuation coefficient is the fractional change of radiative flux per unit path length due to the stated process. Coefficients with canonical units of m2 s-1 i.e. multiplied by density have standard names with specific_ instead of volume_. Backwards scattering refers to the sum of scattering into all backward angles i.e. scattering_angle exceeds pi/2 radians. A scattering_angle should not be specified with this quantity. The scattering/absorption/attenuation coefficient is assumed to be an integral over all wavelengths, unless a coordinate of radiation_wavelength is included to specify the wavelength. Radiative flux is the sum of shortwave and longwave radiative fluxes. "Aerosol" means the system of suspended liquid or solid particles in air (except cloud droplets) and their carrier gas, the air itself. Aerosol particles take up ambient water (a process known as hygroscopic growth) depending on the relative humidity and the composition of the particles. "Dry aerosol particles" means aerosol particles without any water uptake. The specification of a physical process by the phrase "due_to_" process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. "Standard_temperature_and_pressure" refer to a reference volume at 273.15 K temperature and 1013.25 hPa pressure. + + + + m-1 + + + The volume scattering/absorption/attenuation coefficient is the fractional change of radiative flux per unit path length due to the stated process. Coefficients with canonical units of m2 s-1 i.e. multiplied by density have standard names with specific_ instead of volume_. Backwards scattering refers to the sum of scattering into all backward angles i.e. scattering_angle exceeds pi/2 radians. A scattering_angle should not be specified with this quantity. The scattering/absorption/attenuation coefficient is assumed to be an integral over all wavelengths, unless a coordinate of radiation_wavelength is included to specify the wavelength. Radiative flux is the sum of shortwave and longwave radiative fluxes. "Aerosol" means the system of suspended liquid or solid particles in air (except cloud droplets) and their carrier gas, the air itself. To specify the relative humidity at which the sample was measured, provide a scalar coordinate variable with the standard name of "relative_humidity". The specification of a physical process by the phrase "due_to_" process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. "Pm10 aerosol particles" means atmospheric particulate compounds with an aerodynamic diameter of less than or equal to 10 micrometers. + + + + m-1 + + + The volume scattering/absorption/attenuation coefficient is the fractional change of radiative flux per unit path length due to the stated process. Coefficients with canonical units of m2 s-1 i.e. multiplied by density have standard names with specific_ instead of volume_. Backwards scattering refers to the sum of scattering into all backward angles i.e. scattering_angle exceeds pi/2 radians. A scattering_angle should not be specified with this quantity. The scattering/absorption/attenuation coefficient is assumed to be an integral over all wavelengths, unless a coordinate of radiation_wavelength is included to specify the wavelength. Radiative flux is the sum of shortwave and longwave radiative fluxes. "Aerosol" means the system of suspended liquid or solid particles in air (except cloud droplets) and their carrier gas, the air itself. To specify the relative humidity at which the sample was measured, provide a scalar coordinate variable with the standard name of "relative_humidity". The specification of a physical process by the phrase "due_to_" process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. "Pm10 aerosol particles" means atmospheric particulate compounds with an aerodynamic diameter of less than or equal to 10 micrometers. "Standard_temperature_and_pressure" refer to a reference volume at 273.15 K temperature and 1013.25 hPa pressure. + + + + m-1 + + + The volume scattering/absorption/attenuation coefficient is the fractional change of radiative flux per unit path length due to the stated process. Coefficients with canonical units of m2 s-1 i.e. multiplied by density have standard names with specific_ instead of volume_. Backwards scattering refers to the sum of scattering into all backward angles i.e. scattering_angle exceeds pi/2 radians. A scattering_angle should not be specified with this quantity. The scattering/absorption/attenuation coefficient is assumed to be an integral over all wavelengths, unless a coordinate of radiation_wavelength is included to specify the wavelength. Radiative flux is the sum of shortwave and longwave radiative fluxes. "Aerosol" means the system of suspended liquid or solid particles in air (except cloud droplets) and their carrier gas, the air itself. "Ambient_aerosol" means that the aerosol is measured or modelled at the ambient state of pressure, temperature and relative humidity that exists in its immediate environment. "Ambient aerosol particles" are aerosol particles that have taken up ambient water through hygroscopic growth. The extent of hygroscopic growth depends on the relative humidity and the composition of the particles. To specify the relative humidity at which the sample was measured, provide a scalar coordinate variable with the standard name of "relative_humidity". The specification of a physical process by the phrase "due_to_" process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. "Pm10 aerosol particles" means atmospheric particulate compounds with an aerodynamic diameter of less than or equal to 10 micrometers. + + + + m-1 + + + The volume scattering/absorption/attenuation coefficient is the fractional change of radiative flux per unit path length due to the stated process. Coefficients with canonical units of m2 s-1 i.e. multiplied by density have standard names with specific_ instead of volume_. Backwards scattering refers to the sum of scattering into all backward angles i.e. scattering_angle exceeds pi/2 radians. A scattering_angle should not be specified with this quantity. The scattering/absorption/attenuation coefficient is assumed to be an integral over all wavelengths, unless a coordinate of radiation_wavelength is included to specify the wavelength. Radiative flux is the sum of shortwave and longwave radiative fluxes. "Aerosol" means the system of suspended liquid or solid particles in air (except cloud droplets) and their carrier gas, the air itself. "Ambient aerosol particles" are aerosol particles that have taken up ambient water through hygroscopic growth. The extent of hygroscopic growth depends on the relative humidity and the composition of the particles. To specify the relative humidity at which the sample was measured, provide a scalar coordinate variable with the standard name of "relative_humidity". The specification of a physical process by the phrase "due_to_" process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. "Pm10 aerosol particles" means atmospheric particulate compounds with an aerodynamic diameter of less than or equal to 10 micrometers. "Standard_temperature_and_pressure" refer to a reference volume at 273.15 K temperature and 1013.25 hPa pressure. + + + + m-1 + + + The volume scattering/absorption/attenuation coefficient is the fractional change of radiative flux per unit path length due to the stated process. Coefficients with canonical units of m2 s-1 i.e. multiplied by density have standard names with specific_ instead of volume_. Backwards scattering refers to the sum of scattering into all backward angles i.e. scattering_angle exceeds pi/2 radians. A scattering_angle should not be specified with this quantity. The scattering/absorption/attenuation coefficient is assumed to be an integral over all wavelengths, unless a coordinate of radiation_wavelength is included to specify the wavelength. Radiative flux is the sum of shortwave and longwave radiative fluxes. "Aerosol" means the system of suspended liquid or solid particles in air (except cloud droplets) and their carrier gas, the air itself. "Dried_aerosol_particles" means that the aerosol sample has been dried from the ambient state before sizing, but that the dry state (relative humidity less than 40 per cent) has not necessarily been reached. To specify the relative humidity at which the sample was measured, provide a scalar coordinate variable with the standard name of "relative_humidity". The specification of a physical process by the phrase "due_to_" process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. "Pm10 aerosol particles" means atmospheric particulate compounds with an aerodynamic diameter of less than or equal to 10 micrometers. + + + + m-1 + + + The volume scattering/absorption/attenuation coefficient is the fractional change of radiative flux per unit path length due to the stated process. Coefficients with canonical units of m2 s-1 i.e. multiplied by density have standard names with specific_ instead of volume_. Backwards scattering refers to the sum of scattering into all backward angles i.e. scattering_angle exceeds pi/2 radians. A scattering_angle should not be specified with this quantity. The scattering/absorption/attenuation coefficient is assumed to be an integral over all wavelengths, unless a coordinate of radiation_wavelength is included to specify the wavelength. "Aerosol" means the system of suspended liquid or solid particles in air (except cloud droplets) and their carrier gas, the air itself. "Dried_aerosol_particles" means that the aerosol sample has been dried from the ambient state before sizing, but that the dry state (relative humidity less than 40 per cent) has not necessarily been reached. To specify the relative humidity at which the sample was measured, provide a scalar coordinate variable with the standard name of "relative_humidity". The specification of a physical process by the phrase "due_to_" process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. "Pm10 aerosol particles" means atmospheric particulate compounds with an aerodynamic diameter of less than or equal to 10 micrometers. "Standard_temperature_and_pressure" refer to a reference volume at 273.15 K temperature and 1013.25 hPa pressure. + + + + m-1 + + + The volume scattering/absorption/attenuation coefficient is the fractional change of radiative flux per unit path length due to the stated process. Coefficients with canonical units of m2 s-1 i.e. multiplied by density have standard names with specific_ instead of volume_. Backwards scattering refers to the sum of scattering into all backward angles i.e. scattering_angle exceeds pi/2 radians. A scattering_angle should not be specified with this quantity. The scattering/absorption/attenuation coefficient is assumed to be an integral over all wavelengths, unless a coordinate of radiation_wavelength is included to specify the wavelength. Radiative flux is the sum of shortwave and longwave radiative fluxes. "Aerosol" means the system of suspended liquid or solid particles in air (except cloud droplets) and their carrier gas, the air itself. Aerosol particles take up ambient water (a process known as hygroscopic growth) depending on the relative humidity and the composition of the particles. "Dry aerosol particles" means aerosol particles without any water uptake. The specification of a physical process by the phrase "due_to_" process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. "Pm10 aerosol particles" means atmospheric particulate compounds with an aerodynamic diameter of less than or equal to 10 micrometers. + + + + m-1 + + + The volume scattering/absorption/attenuation coefficient is the fractional change of radiative flux per unit path length due to the stated process. Coefficients with canonical units of m2 s-1 i.e. multiplied by density have standard names with specific_ instead of volume_. Backwards scattering refers to the sum of scattering into all backward angles i.e. scattering_angle exceeds pi/2 radians. A scattering_angle should not be specified with this quantity. The scattering/absorption/attenuation coefficient is assumed to be an integral over all wavelengths, unless a coordinate of radiation_wavelength is included to specify the wavelength. Radiative flux is the sum of shortwave and longwave radiative fluxes. "Aerosol" means the system of suspended liquid or solid particles in air (except cloud droplets) and their carrier gas, the air itself. Aerosol particles take up ambient water (a process known as hygroscopic growth) depending on the relative humidity and the composition of the particles. "Dry aerosol particles" means aerosol particles without any water uptake. The specification of a physical process by the phrase "due_to_" process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. "Pm10 aerosol particles" means atmospheric particulate compounds with an aerodynamic diameter of less than or equal to 10 micrometers. "Standard_temperature_and_pressure" refer to a reference volume at 273.15 K temperature and 1013.25 hPa pressure. + + + + m-1 + + + The volume scattering/absorption/attenuation coefficient is the fractional change of radiative flux per unit path length due to the stated process. Coefficients with canonical units of m2 s-1 i.e. multiplied by density have standard names with specific_ instead of volume_. Backwards scattering refers to the sum of scattering into all backward angles i.e. scattering_angle exceeds pi/2 radians. A scattering_angle should not be specified with this quantity. The scattering/absorption/attenuation coefficient is assumed to be an integral over all wavelengths, unless a coordinate of radiation_wavelength is included to specify the wavelength. Radiative flux is the sum of shortwave and longwave radiative fluxes. "Aerosol" means the system of suspended liquid or solid particles in air (except cloud droplets) and their carrier gas, the air itself. To specify the relative humidity at which the sample was measured, provide a scalar coordinate variable with the standard name of "relative_humidity". The specification of a physical process by the phrase "due_to_" process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. "Pm1 aerosol particles" means atmospheric particulate compounds with an aerodynamic diameter of less than or equal to 1 micrometers. + + + + m-1 + + + The volume scattering/absorption/attenuation coefficient is the fractional change of radiative flux per unit path length due to the stated process. Coefficients with canonical units of m2 s-1 i.e. multiplied by density have standard names with specific_ instead of volume_. Backwards scattering refers to the sum of scattering into all backward angles i.e. scattering_angle exceeds pi/2 radians. A scattering_angle should not be specified with this quantity. The scattering/absorption/attenuation coefficient is assumed to be an integral over all wavelengths, unless a coordinate of radiation_wavelength is included to specify the wavelength. Radiative flux is the sum of shortwave and longwave radiative fluxes. "Aerosol" means the system of suspended liquid or solid particles in air (except cloud droplets) and their carrier gas, the air itself. To specify the relative humidity at which the sample was measured, provide a scalar coordinate variable with the standard name of "relative_humidity". The specification of a physical process by the phrase "due_to_" process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. "Pm1 aerosol particles" means atmospheric particulate compounds with an aerodynamic diameter of less than or equal to 1 micrometers. "Standard_temperature_and_pressure" refer to a reference volume at 273.15 K temperature and 1013.25 hPa pressure. + + + + m-1 + + + The volume scattering/absorption/attenuation coefficient is the fractional change of radiative flux per unit path length due to the stated process. Coefficients with canonical units of m2 s-1 i.e. multiplied by density have standard names with specific_ instead of volume_. Backwards scattering refers to the sum of scattering into all backward angles i.e. scattering_angle exceeds pi/2 radians. A scattering_angle should not be specified with this quantity. The scattering/absorption/attenuation coefficient is assumed to be an integral over all wavelengths, unless a coordinate of radiation_wavelength is included to specify the wavelength. Radiative flux is the sum of shortwave and longwave radiative fluxes. "Aerosol" means the system of suspended liquid or solid particles in air (except cloud droplets) and their carrier gas, the air itself. "Ambient aerosol particles" are aerosol particles that have taken up ambient water through hygroscopic growth. The extent of hygroscopic growth depends on the relative humidity and the composition of the particles. To specify the relative humidity at which the sample was measured, provide a scalar coordinate variable with the standard name of "relative_humidity". The specification of a physical process by the phrase "due_to_" process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. "Pm1 aerosol particles" means atmospheric particulate compounds with an aerodynamic diameter of less than or equal to 1 micrometers. + + + + m-1 + + + The volume scattering/absorption/attenuation coefficient is the fractional change of radiative flux per unit path length due to the stated process. Coefficients with canonical units of m2 s-1 i.e. multiplied by density have standard names with specific_ instead of volume_. Backwards scattering refers to the sum of scattering into all backward angles i.e. scattering_angle exceeds pi/2 radians. A scattering_angle should not be specified with this quantity. The scattering/absorption/attenuation coefficient is assumed to be an integral over all wavelengths, unless a coordinate of radiation_wavelength is included to specify the wavelength. Radiative flux is the sum of shortwave and longwave radiative fluxes. "Aerosol" means the system of suspended liquid or solid particles in air (except cloud droplets) and their carrier gas, the air itself. "Ambient aerosol particles" are aerosol particles that have taken up ambient water through hygroscopic growth. The extent of hygroscopic growth depends on the relative humidity and the composition of the particles. To specify the relative humidity at which the sample was measured, provide a scalar coordinate variable with the standard name of "relative_humidity". The specification of a physical process by the phrase "due_to_" process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. "Pm1 aerosol particles" means atmospheric particulate compounds with an aerodynamic diameter of less than or equal to 1 micrometers. "Standard_temperature_and_pressure" refer to a reference volume at 273.15 K temperature and 1013.25 hPa pressure. + + + + m-1 + + + The volume scattering/absorption/attenuation coefficient is the fractional change of radiative flux per unit path length due to the stated process. Coefficients with canonical units of m2 s-1 i.e. multiplied by density have standard names with specific_ instead of volume_. Backwards scattering refers to the sum of scattering into all backward angles i.e. scattering_angle exceeds pi/2 radians. A scattering_angle should not be specified with this quantity. The scattering/absorption/attenuation coefficient is assumed to be an integral over all wavelengths, unless a coordinate of radiation_wavelength is included to specify the wavelength. Radiative flux is the sum of shortwave and longwave radiative fluxes. "Aerosol" means the system of suspended liquid or solid particles in air (except cloud droplets) and their carrier gas, the air itself. "Dried_aerosol_particles" means that the aerosol sample has been dried from the ambient state before sizing, but that the dry state (relative humidity less than 40 per cent) has not necessarily been reached. To specify the relative humidity at which the sample was measured, provide a scalar coordinate variable with the standard name of "relative_humidity". The specification of a physical process by the phrase "due_to_" process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. "Pm1 aerosol particles" means atmospheric particulate compounds with an aerodynamic diameter of less than or equal to 1 micrometers. + + + + m-1 + + + The volume scattering/absorption/attenuation coefficient is the fractional change of radiative flux per unit path length due to the stated process. Coefficients with canonical units of m2 s-1 i.e. multiplied by density have standard names with specific_ instead of volume_. Backwards scattering refers to the sum of scattering into all backward angles i.e. scattering_angle exceeds pi/2 radians. A scattering_angle should not be specified with this quantity. The scattering/absorption/attenuation coefficient is assumed to be an integral over all wavelengths, unless a coordinate of radiation_wavelength is included to specify the wavelength. Radiative flux is the sum of shortwave and longwave radiative fluxes. "Aerosol" means the system of suspended liquid or solid particles in air (except cloud droplets) and their carrier gas, the air itself. "Dried_aerosol_particles" means that the aerosol sample has been dried from the ambient state before sizing, but that the dry state (relative humidity less than 40 per cent) has not necessarily been reached. To specify the relative humidity at which the sample was measured, provide a scalar coordinate variable with the standard name of "relative_humidity". The specification of a physical process by the phrase "due_to_" process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. "Pm1 aerosol particles" means atmospheric particulate compounds with an aerodynamic diameter of less than or equal to 1 micrometers. "Standard_temperature_and_pressure" refer to a reference volume at 273.15 K temperature and 1013.25 hPa pressure. + + + + m-1 + + + The volume scattering/absorption/attenuation coefficient is the fractional change of radiative flux per unit path length due to the stated process. Coefficients with canonical units of m2 s-1 i.e. multiplied by density have standard names with specific_ instead of volume_. Backwards scattering refers to the sum of scattering into all backward angles i.e. scattering_angle exceeds pi/2 radians. A scattering_angle should not be specified with this quantity. The scattering/absorption/attenuation coefficient is assumed to be an integral over all wavelengths, unless a coordinate of radiation_wavelength is included to specify the wavelength. Radiative flux is the sum of shortwave and longwave radiative fluxes. "Aerosol" means the system of suspended liquid or solid particles in air (except cloud droplets) and their carrier gas, the air itself. Aerosol particles take up ambient water (a process known as hygroscopic growth) depending on the relative humidity and the composition of the particles. "Dry aerosol particles" means aerosol particles without any water uptake. The specification of a physical process by the phrase "due_to_" process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. "Pm1 aerosol particles" means atmospheric particulate compounds with an aerodynamic diameter of less than or equal to 1 micrometers. + + + + m-1 + + + The volume scattering/absorption/attenuation coefficient is the fractional change of radiative flux per unit path length due to the stated process. Coefficients with canonical units of m2 s-1 i.e. multiplied by density have standard names with specific_ instead of volume_. Backwards scattering refers to the sum of scattering into all backward angles i.e. scattering_angle exceeds pi/2 radians. A scattering_angle should not be specified with this quantity. The scattering/absorption/attenuation coefficient is assumed to be an integral over all wavelengths, unless a coordinate of radiation_wavelength is included to specify the wavelength. Radiative flux is the sum of shortwave and longwave radiative fluxes. "Aerosol" means the system of suspended liquid or solid particles in air (except cloud droplets) and their carrier gas, the air itself. Aerosol particles take up ambient water (a process known as hygroscopic growth) depending on the relative humidity and the composition of the particles. "Dry aerosol particles" means aerosol particles without any water uptake. The specification of a physical process by the phrase "due_to_" process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. "Pm1 aerosol particles" means atmospheric particulate compounds with an aerodynamic diameter of less than or equal to 1 micrometers. "Standard_temperature_and_pressure" refer to a reference volume at 273.15 K temperature and 1013.25 hPa pressure. + + + + m-1 + + + The volume scattering/absorption/attenuation coefficient is the fractional change of radiative flux per unit path length due to the stated process. Coefficients with canonical units of m2 s-1 i.e. multiplied by density have standard names with specific_ instead of volume_. Backwards scattering refers to the sum of scattering into all backward angles i.e. scattering_angle exceeds pi/2 radians. A scattering_angle should not be specified with this quantity. The scattering/absorption/attenuation coefficient is assumed to be an integral over all wavelengths, unless a coordinate of radiation_wavelength is included to specify the wavelength. Radiative flux is the sum of shortwave and longwave radiative fluxes. "Aerosol" means the system of suspended liquid or solid particles in air (except cloud droplets) and their carrier gas, the air itself. To specify the relative humidity at which the sample was measured, provide a scalar coordinate variable with the standard name of "relative_humidity". The specification of a physical process by the phrase "due_to_" process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. "Pm1 aerosol particles" means atmospheric particulate compounds with an aerodynamic diameter of less than or equal to 1 micrometers. + + + + m-1 + + + The volume scattering/absorption/attenuation coefficient is the fractional change of radiative flux per unit path length due to the stated process. Coefficients with canonical units of m2 s-1 i.e. multiplied by density have standard names with specific_ instead of volume_. Backwards scattering refers to the sum of scattering into all backward angles i.e. scattering_angle exceeds pi/2 radians. A scattering_angle should not be specified with this quantity. The scattering/absorption/attenuation coefficient is assumed to be an integral over all wavelengths, unless a coordinate of radiation_wavelength is included to specify the wavelength. Radiative flux is the sum of shortwave and longwave radiative fluxes. "Aerosol" means the system of suspended liquid or solid particles in air (except cloud droplets) and their carrier gas, the air itself. To specify the relative humidity at which the sample was measured, provide a scalar coordinate variable with the standard name of "relative_humidity". The specification of a physical process by the phrase "due_to_" process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. "Pm2p5 aerosol particles" means atmospheric particulate compounds with an aerodynamic diameter of less than or equal to 2.5 micrometers. "Standard_temperature_and_pressure" refer to a reference volume at 273.15 K temperature and 1013.25 hPa pressure. + + + + m-1 + + + The volume scattering/absorption/attenuation coefficient is the fractional change of radiative flux per unit path length due to the stated process. Coefficients with canonical units of m2 s-1 i.e. multiplied by density have standard names with specific_ instead of volume_. Backwards scattering refers to the sum of scattering into all backward angles i.e. scattering_angle exceeds pi/2 radians. A scattering_angle should not be specified with this quantity. The scattering/absorption/attenuation coefficient is assumed to be an integral over all wavelengths, unless a coordinate of radiation_wavelength is included to specify the wavelength. Radiative flux is the sum of shortwave and longwave radiative fluxes. "Aerosol" means the system of suspended liquid or solid particles in air (except cloud droplets) and their carrier gas, the air itself. "Ambient_aerosol" means that the aerosol is measured or modelled at the ambient state of pressure, temperature and relative humidity that exists in its immediate environment. "Ambient aerosol particles" are aerosol particles that have taken up ambient water through hygroscopic growth. The extent of hygroscopic growth depends on the relative humidity and the composition of the particles. To specify the relative humidity at which the sample was measured, provide a scalar coordinate variable with the standard name of "relative_humidity". The specification of a physical process by the phrase "due_to_" process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. "Pm2p5 aerosol particles" means atmospheric particulate compounds with an aerodynamic diameter of less than or equal to 2.5 micrometers. + + + + m-1 + + + The volume scattering/absorption/attenuation coefficient is the fractional change of radiative flux per unit path length due to the stated process. Coefficients with canonical units of m2 s-1 i.e. multiplied by density have standard names with specific_ instead of volume_. Backwards scattering refers to the sum of scattering into all backward angles i.e. scattering_angle exceeds pi/2 radians. A scattering_angle should not be specified with this quantity. The scattering/absorption/attenuation coefficient is assumed to be an integral over all wavelengths, unless a coordinate of radiation_wavelength is included to specify the wavelength. Radiative flux is the sum of shortwave and longwave radiative fluxes. "Aerosol" means the system of suspended liquid or solid particles in air (except cloud droplets) and their carrier gas, the air itself. "Ambient aerosol particles" are aerosol particles that have taken up ambient water through hygroscopic growth. The extent of hygroscopic growth depends on the relative humidity and the composition of the particles. To specify the relative humidity at which the sample was measured, provide a scalar coordinate variable with the standard name of "relative_humidity". The specification of a physical process by the phrase "due_to_" process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. "Pm2p5 aerosol particles" means atmospheric particulate compounds with an aerodynamic diameter of less than or equal to 2.5 micrometers. "Standard_temperature_and_pressure" refer to a reference volume at 273.15 K temperature and 1013.25 hPa pressure. + + + + m-1 + + + The volume scattering/absorption/attenuation coefficient is the fractional change of radiative flux per unit path length due to the stated process. Coefficients with canonical units of m2 s-1 i.e. multiplied by density have standard names with specific_ instead of volume_. Backwards scattering refers to the sum of scattering into all backward angles i.e. scattering_angle exceeds pi/2 radians. A scattering_angle should not be specified with this quantity. The scattering/absorption/attenuation coefficient is assumed to be an integral over all wavelengths, unless a coordinate of radiation_wavelength is included to specify the wavelength. Radiative flux is the sum of shortwave and longwave radiative fluxes. Radiative flux is the sum of shortwave and longwave radiative fluxes. "Aerosol" means the system of suspended liquid or solid particles in air (except cloud droplets) and their carrier gas, the air itself. "Dried_aerosol_particles" means that the aerosol sample has been dried from the ambient state before sizing, but that the dry state (relative humidity less than 40 per cent) has not necessarily been reached. To specify the relative humidity at which the sample was measured, provide a scalar coordinate variable with the standard name of "relative_humidity". The specification of a physical process by the phrase "due_to_" process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. "Pm2p5 aerosol particles" means atmospheric particulate compounds with an aerodynamic diameter of less than or equal to 2.5 micrometers. + + + + m-1 + + + The volume scattering/absorption/attenuation coefficient is the fractional change of radiative flux per unit path length due to the stated process. Coefficients with canonical units of m2 s-1 i.e. multiplied by density have standard names with specific_ instead of volume_. Backwards scattering refers to the sum of scattering into all backward angles i.e. scattering_angle exceeds pi/2 radians. A scattering_angle should not be specified with this quantity. The scattering/absorption/attenuation coefficient is assumed to be an integral over all wavelengths, unless a coordinate of radiation_wavelength is included to specify the wavelength. Radiative flux is the sum of shortwave and longwave radiative fluxes. "Aerosol" means the system of suspended liquid or solid particles in air (except cloud droplets) and their carrier gas, the air itself. "Dried_aerosol_particles" means that the aerosol sample has been dried from the ambient state before sizing, but that the dry state (relative humidity less than 40 per cent) has not necessarily been reached. To specify the relative humidity at which the sample was measured, provide a scalar coordinate variable with the standard name of "relative_humidity". The specification of a physical process by the phrase "due_to_" process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. "Pm2p5 aerosol particles" means atmospheric particulate compounds with an aerodynamic diameter of less than or equal to 2.5 micrometers. "Standard_temperature_and_pressure" refer to a reference volume at 273.15 K temperature and 1013.25 hPa pressure. + + + + m-1 + + + The volume scattering/absorption/attenuation coefficient is the fractional change of radiative flux per unit path length due to the stated process. Coefficients with canonical units of m2 s-1 i.e. multiplied by density have standard names with specific_ instead of volume_. Backwards scattering refers to the sum of scattering into all backward angles i.e. scattering_angle exceeds pi/2 radians. A scattering_angle should not be specified with this quantity. The scattering/absorption/attenuation coefficient is assumed to be an integral over all wavelengths, unless a coordinate of radiation_wavelength is included to specify the wavelength. Radiative flux is the sum of shortwave and longwave radiative fluxes. "Aerosol" means the system of suspended liquid or solid particles in air (except cloud droplets) and their carrier gas, the air itself. Aerosol particles take up ambient water (a process known as hygroscopic growth) depending on the relative humidity and the composition of the particles. "Dry aerosol particles" means aerosol particles without any water uptake. The specification of a physical process by the phrase "due_to_" process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. "Pm2p5 aerosol particles" means atmospheric particulate compounds with an aerodynamic diameter of less than or equal to 2.5 micrometers. + + + + m-1 + + + The volume scattering/absorption/attenuation coefficient is the fractional change of radiative flux per unit path length due to the stated process. Coefficients with canonical units of m2 s-1 i.e. multiplied by density have standard names with specific_ instead of volume_. Backwards scattering refers to the sum of scattering into all backward angles i.e. scattering_angle exceeds pi/2 radians. A scattering_angle should not be specified with this quantity. The scattering/absorption/attenuation coefficient is assumed to be an integral over all wavelengths, unless a coordinate of radiation_wavelength is included to specify the wavelength. Radiative flux is the sum of shortwave and longwave radiative fluxes. "Aerosol" means the system of suspended liquid or solid particles in air (except cloud droplets) and their carrier gas, the air itself. Aerosol particles take up ambient water (a process known as hygroscopic growth) depending on the relative humidity and the composition of the particles. "Dry aerosol particles" means aerosol particles without any water uptake. The specification of a physical process by the phrase "due_to_" process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. "Pm2p5 aerosol particles" means atmospheric particulate compounds with an aerodynamic diameter of less than or equal to 2.5 micrometers. "Standard_temperature_and_pressure" refer to a reference volume at 273.15 K temperature and 1013.25 hPa pressure. + + m-1 @@ -32445,18 +33160,18 @@ http://vocab.nerc.ac.uk/collection/P01/current/TPHSDSZZ/6/. The volume extinction Angstrom exponent is the Angstrom exponent obtained for the aerosol extinction instead that for the aerosol optical thickness. It is alpha in the following equation relating aerosol extinction (ext) at the wavelength lambda to aerosol extinction at a different wavelength lambda0: ext(lambda) = ext(lambda0) * [lambda/lambda0] ** (-1 * alpha). "Ambient_aerosol" means that the aerosol is measured or modelled at the ambient state of pressure, temperature and relative humidity that exists in its immediate environment. "Ambient aerosol particles" are aerosol particles that have taken up ambient water through hygroscopic growth. The extent of hygroscopic growth depends on the relative humidity and the composition of the particles. The specification of a physical process by the phrase "due_to_" process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. - + m-1 - The volume extinction coefficient is the fractional change of radiative flux per unit path length. Extinction is the sum of absorption and scattering, sometimes called "attenuation". "Extinction" is the term most commonly used at optical wavelengths whereas "attenuation" is more often used at radio and radar wavelengths. "Aerosol" means the system of suspended liquid or solid particles in air (except cloud droplets) and their carrier gas, the air itself. "Ambient_aerosol" means that the aerosol is measured or modelled at the ambient state of pressure, temperature and relative humidity that exists in its immediate environment. "Ambient aerosol particles" are aerosol particles that have taken up ambient water through hygroscopic growth. The extent of hygroscopic growth depends on the relative humidity and the composition of the particles. The specification of a physical process by the phrase "due_to_" process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. + The volume extinction coefficient is the fractional change of radiative flux per unit path length. Extinction is the sum of absorption and scattering, sometimes called "attenuation". "Extinction" is the term most commonly used at optical wavelengths whereas "attenuation" is more often used at radio and radar wavelengths. Radiative flux is the sum of shortwave and longwave radiative fluxes. "Aerosol" means the system of suspended liquid or solid particles in air (except cloud droplets) and their carrier gas, the air itself. "Ambient_aerosol" means that the aerosol is measured or modelled at the ambient state of pressure, temperature and relative humidity that exists in its immediate environment. "Ambient aerosol particles" are aerosol particles that have taken up ambient water through hygroscopic growth. The extent of hygroscopic growth depends on the relative humidity and the composition of the particles. The specification of a physical process by the phrase "due_to_" process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. - + m-1 - The volume extinction coefficient is the fractional change of radiative flux per unit path length. Extinction is the sum of absorption and scattering, sometimes called "attenuation". "Extinction" is the term most commonly used at optical wavelengths whereas "attenuation" is more often used at radio and radar wavelengths. The specification of a physical process by the phrase "due_to_" process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. "Cloud particles" means suspended liquid or ice water droplets. A coordinate of radiation_wavelength or radiation_frequency should be included to specify either the wavelength or frequency. + The volume extinction coefficient is the fractional change of radiative flux per unit path length. Extinction is the sum of absorption and scattering, sometimes called "attenuation". "Extinction" is the term most commonly used at optical wavelengths whereas "attenuation" is more often used at radio and radar wavelengths. Radiative flux is the sum of shortwave and longwave radiative fluxes. The specification of a physical process by the phrase "due_to_" process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. "Cloud particles" means suspended liquid or ice water droplets. A coordinate of radiation_wavelength or radiation_frequency should be included to specify either the wavelength or frequency. @@ -32550,6 +33265,20 @@ http://vocab.nerc.ac.uk/collection/P01/current/TPHSDSZZ/6/. "ratio_of_X_to_Y" means X/Y. "stp" means standard temperature (0 degC) and pressure (101325 Pa). + + m-1 + + + The volume scattering/absorption/attenuation coefficient is the fractional change of radiative flux per unit path length due to the stated process. Coefficients with canonical units of m2 s-1 i.e. multiplied by density have standard names with specific_ instead of volume_. A scattering_angle should not be specified with this quantity. The scattering/absorption/attenuation coefficient is assumed to be an integral over all wavelengths, unless a coordinate of radiation_wavelength is included to specify the wavelength. Radiative flux is the sum of shortwave and longwave radiative fluxes. "Aerosol" means the system of suspended liquid or solid particles in air (except cloud droplets) and their carrier gas, the air itself. To specify the relative humidity at which the sample was measured, provide a scalar coordinate variable with the standard name of "relative_humidity". The specification of a physical process by the phrase "due_to_" process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. + + + + m-1 + + + The volume scattering/absorption/attenuation coefficient is the fractional change of radiative flux per unit path length due to the stated process. Coefficients with canonical units of m2 s-1 i.e. multiplied by density have standard names with specific_ instead of volume_. A scattering_angle should not be specified with this quantity. The scattering/absorption/attenuation coefficient is assumed to be an integral over all wavelengths, unless a coordinate of radiation_wavelength is included to specify the wavelength. Radiative flux is the sum of shortwave and longwave radiative fluxes. "Aerosol" means the system of suspended liquid or solid particles in air (except cloud droplets) and their carrier gas, the air itself. To specify the relative humidity at which the sample was measured, provide a scalar coordinate variable with the standard name of "relative_humidity". The specification of a physical process by the phrase "due_to_" process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. "Standard_temperature_and_pressure" refer to a reference volume at 273.15 K temperature and 1013.25 hPa pressure. + + m-1 @@ -32557,6 +33286,13 @@ http://vocab.nerc.ac.uk/collection/P01/current/TPHSDSZZ/6/. Radiative flux is the sum of shortwave and longwave radiative fluxes. Scattering of radiation is its deflection from its incident path without loss of energy. The volume scattering/absorption/attenuation coefficient is the fractional change of radiative flux per unit path length due to the stated process. Coefficients with canonical units of m2 s-1 i.e. multiplied by density have standard names with "specific_" instead of "volume_". The scattering/absorption/attenuation coefficient is assumed to be an integral over all wavelengths unless a coordinate of "radiation_wavelength" or "radiation_frequency" is included to specify the wavelength. The specification of a physical process by the phrase "due_to_" process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. "Aerosol" means the system of suspended liquid or solid particles in air (except cloud droplets) and their carrier gas, the air itself. "Ambient_aerosol" means that the aerosol is measured or modelled at the ambient state of pressure, temperature and relative humidity that exist in its immediate environment. "Ambient aerosol particles" are aerosol particles that have taken up ambient water through hygroscopic growth. The extent of hygroscopic growth depends on the relative humidity and the composition of the particles. To specify the relative humidity at which the quantity described by the standard name applies, provide a scalar coordinate variable with the standard name of "relative_humidity". + + m-1 + + + The volume scattering/absorption/attenuation coefficient is the fractional change of radiative flux per unit path length due to the stated process. Coefficients with canonical units of m2 s-1 i.e. multiplied by density have standard names with specific_ instead of volume_. A scattering_angle should not be specified with this quantity. The scattering/absorption/attenuation coefficient is assumed to be an integral over all wavelengths, unless a coordinate of radiation_wavelength is included to specify the wavelength. Radiative flux is the sum of shortwave and longwave radiative fluxes. "Aerosol" means the system of suspended liquid or solid particles in air (except cloud droplets) and their carrier gas, the air itself. "Ambient aerosol particles" are aerosol particles that have taken up ambient water through hygroscopic growth. The extent of hygroscopic growth depends on the relative humidity and the composition of the particles. To specify the relative humidity at which the sample was measured, provide a scalar coordinate variable with the standard name of "relative_humidity". The specification of a physical process by the phrase "due_to_" process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. "Standard_temperature_and_pressure" refer to a reference volume at 273.15 K temperature and 1013.25 hPa pressure. + + m-1 @@ -32564,6 +33300,195 @@ http://vocab.nerc.ac.uk/collection/P01/current/TPHSDSZZ/6/. Radiative flux is the sum of shortwave and longwave radiative fluxes. Scattering of radiation is its deflection from its incident path without loss of energy. The volume scattering/absorption/attenuation coefficient is the fractional change of radiative flux per unit path length due to the stated process. Coefficients with canonical units of m2 s-1 i.e. multiplied by density have standard names with "specific_" instead of "volume_". The scattering/absorption/attenuation coefficient is assumed to be an integral over all wavelengths unless a coordinate of "radiation_wavelength" or "radiation_frequency" is included to specify the wavelength. The specification of a physical process by the phrase "due_to_" process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. "Aerosol" means the system of suspended liquid or solid particles in air (except cloud droplets) and their carrier gas, the air itself. "Dried_aerosol" means that the aerosol sample has been dried from the ambient state before sizing, but that the dry state (relative humidity less than 40 per cent) has not necessarily been reached. To specify the relative humidity at which the sample was measured, provide a scalar coordinate variable with the standard name of "relative_humidity". + + m-1 + + + The volume scattering/absorption/attenuation coefficient is the fractional change of radiative flux per unit path length due to the stated process. Coefficients with canonical units of m2 s-1 i.e. multiplied by density have standard names with specific_ instead of volume_. A scattering_angle should not be specified with this quantity. The scattering/absorption/attenuation coefficient is assumed to be an integral over all wavelengths, unless a coordinate of radiation_wavelength is included to specify the wavelength. Radiative flux is the sum of shortwave and longwave radiative fluxes. "Aerosol" means the system of suspended liquid or solid particles in air (except cloud droplets) and their carrier gas, the air itself. "Dried_aerosol_particles" means that the aerosol sample has been dried from the ambient state before sizing, but that the dry state (relative humidity less than 40 per cent) has not necessarily been reached. To specify the relative humidity at which the sample was measured, provide a scalar coordinate variable with the standard name of "relative_humidity". The specification of a physical process by the phrase "due_to_" process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. "Standard_temperature_and_pressure" refer to a reference volume at 273.15 K temperature and 1013.25 hPa pressure. + + + + m-1 + + + The volume scattering/absorption/attenuation coefficient is the fractional change of radiative flux per unit path length due to the stated process. Coefficients with canonical units of m2 s-1 i.e. multiplied by density have standard names with specific_ instead of volume_. A scattering_angle should not be specified with this quantity. The scattering/absorption/attenuation coefficient is assumed to be an integral over all wavelengths, unless a coordinate of radiation_wavelength is included to specify the wavelength. Radiative flux is the sum of shortwave and longwave radiative fluxes. "Aerosol" means the system of suspended liquid or solid particles in air (except cloud droplets) and their carrier gas, the air itself. Aerosol particles take up ambient water (a process known as hygroscopic growth) depending on the relative humidity and the composition of the particles. "Dry aerosol particles" means aerosol particles without any water uptake. The specification of a physical process by the phrase "due_to_" process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. + + + + m-1 + + + The volume scattering/absorption/attenuation coefficient is the fractional change of radiative flux per unit path length due to the stated process. Coefficients with canonical units of m2 s-1 i.e. multiplied by density have standard names with specific_ instead of volume_. A scattering_angle should not be specified with this quantity. The scattering/absorption/attenuation coefficient is assumed to be an integral over all wavelengths, unless a coordinate of radiation_wavelength is included to specify the wavelength. Radiative flux is the sum of shortwave and longwave radiative fluxes. "Aerosol" means the system of suspended liquid or solid particles in air (except cloud droplets) and their carrier gas, the air itself. Aerosol particles take up ambient water (a process known as hygroscopic growth) depending on the relative humidity and the composition of the particles. "Dry aerosol particles" means aerosol particles without any water uptake. The specification of a physical process by the phrase "due_to_" process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. "Standard_temperature_and_pressure" refer to a reference volume at 273.15 K temperature and 1013.25 hPa pressure. + + + + m-1 + + + The volume scattering/absorption/attenuation coefficient is the fractional change of radiative flux per unit path length due to the stated process. Coefficients with canonical units of m2 s-1 i.e. multiplied by density have standard names with specific_ instead of volume_. A scattering_angle should not be specified with this quantity. The scattering/absorption/attenuation coefficient is assumed to be an integral over all wavelengths, unless a coordinate of radiation_wavelength is included to specify the wavelength. Radiative flux is the sum of shortwave and longwave radiative fluxes. "Aerosol" means the system of suspended liquid or solid particles in air (except cloud droplets) and their carrier gas, the air itself. To specify the relative humidity at which the sample was measured, provide a scalar coordinate variable with the standard name of "relative_humidity". The specification of a physical process by the phrase "due_to_" process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. "Pm10 aerosol particles" means atmospheric particulate compounds with an aerodynamic diameter of less than or equal to 10 micrometers. + + + + m-1 + + + The volume scattering/absorption/attenuation coefficient is the fractional change of radiative flux per unit path length due to the stated process. Coefficients with canonical units of m2 s-1 i.e. multiplied by density have standard names with specific_ instead of volume_. A scattering_angle should not be specified with this quantity. The scattering/absorption/attenuation coefficient is assumed to be an integral over all wavelengths, unless a coordinate of radiation_wavelength is included to specify the wavelength. Radiative flux is the sum of shortwave and longwave radiative fluxes. "Aerosol" means the system of suspended liquid or solid particles in air (except cloud droplets) and their carrier gas, the air itself. To specify the relative humidity at which the sample was measured, provide a scalar coordinate variable with the standard name of "relative_humidity". The specification of a physical process by the phrase "due_to_" process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. "Pm10 aerosol particles" means atmospheric particulate compounds with an aerodynamic diameter of less than or equal to 10 micrometers. "Standard_temperature_and_pressure" refer to a reference volume at 273.15 K temperature and 1013.25 hPa pressure. + + + + m-1 + + + The volume scattering/absorption/attenuation coefficient is the fractional change of radiative flux per unit path length due to the stated process. Coefficients with canonical units of m2 s-1 i.e. multiplied by density have standard names with specific_ instead of volume_. A scattering_angle should not be specified with this quantity. The scattering/absorption/attenuation coefficient is assumed to be an integral over all wavelengths, unless a coordinate of radiation_wavelength is included to specify the wavelength. Radiative flux is the sum of shortwave and longwave radiative fluxes. "Aerosol" means the system of suspended liquid or solid particles in air (except cloud droplets) and their carrier gas, the air itself. "Ambient aerosol particles" are aerosol particles that have taken up ambient water through hygroscopic growth. The extent of hygroscopic growth depends on the relative humidity and the composition of the particles. To specify the relative humidity at which the sample was measured, provide a scalar coordinate variable with the standard name of "relative_humidity". The specification of a physical process by the phrase "due_to_" process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. "Pm10 aerosol particles" means atmospheric particulate compounds with an aerodynamic diameter of less than or equal to 10 micrometers. + + + + m-1 + + + The volume scattering/absorption/attenuation coefficient is the fractional change of radiative flux per unit path length due to the stated process. Coefficients with canonical units of m2 s-1 i.e. multiplied by density have standard names with specific_ instead of volume_. A scattering_angle should not be specified with this quantity. The scattering/absorption/attenuation coefficient is assumed to be an integral over all wavelengths, unless a coordinate of radiation_wavelength is included to specify the wavelength. Radiative flux is the sum of shortwave and longwave radiative fluxes. "Aerosol" means the system of suspended liquid or solid particles in air (except cloud droplets) and their carrier gas, the air itself. "Ambient aerosol particles" are aerosol particles that have taken up ambient water through hygroscopic growth. The extent of hygroscopic growth depends on the relative humidity and the composition of the particles. To specify the relative humidity at which the sample was measured, provide a scalar coordinate variable with the standard name of "relative_humidity". The specification of a physical process by the phrase "due_to_" process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. "Pm10 aerosol particles" means atmospheric particulate compounds with an aerodynamic diameter of less than or equal to 10 micrometers. "Standard_temperature_and_pressure" refer to a reference volume at 273.15 K temperature and 1013.25 hPa pressure. + + + + m-1 + + + The volume scattering/absorption/attenuation coefficient is the fractional change of radiative flux per unit path length due to the stated process. Coefficients with canonical units of m2 s-1 i.e. multiplied by density have standard names with specific_ instead of volume_. A scattering_angle should not be specified with this quantity. The scattering/absorption/attenuation coefficient is assumed to be an integral over all wavelengths, unless a coordinate of radiation_wavelength is included to specify the wavelength. Radiative flux is the sum of shortwave and longwave radiative fluxes. "Aerosol" means the system of suspended liquid or solid particles in air (except cloud droplets) and their carrier gas, the air itself. "Dried_aerosol_particles" means that the aerosol sample has been dried from the ambient state before sizing, but that the dry state (relative humidity less than 40 per cent) has not necessarily been reached. To specify the relative humidity at which the sample was measured, provide a scalar coordinate variable with the standard name of "relative_humidity". The specification of a physical process by the phrase "due_to_" process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. "Pm10 aerosol particles" means atmospheric particulate compounds with an aerodynamic diameter of less than or equal to 10 micrometers. + + + + m-1 + + + The volume scattering/absorption/attenuation coefficient is the fractional change of radiative flux per unit path length due to the stated process. Coefficients with canonical units of m2 s-1 i.e. multiplied by density have standard names with specific_ instead of volume_. A scattering_angle should not be specified with this quantity. The scattering/absorption/attenuation coefficient is assumed to be an integral over all wavelengths, unless a coordinate of radiation_wavelength is included to specify the wavelength. Radiative flux is the sum of shortwave and longwave radiative fluxes. "Aerosol" means the system of suspended liquid or solid particles in air (except cloud droplets) and their carrier gas, the air itself. "Dried_aerosol_particles" means that the aerosol sample has been dried from the ambient state before sizing, but that the dry state (relative humidity less than 40 per cent) has not necessarily been reached. To specify the relative humidity at which the sample was measured, provide a scalar coordinate variable with the standard name of "relative_humidity". The specification of a physical process by the phrase "due_to_" process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. "Pm10 aerosol particles" means atmospheric particulate compounds with an aerodynamic diameter of less than or equal to 10 micrometers. "Standard_temperature_and_pressure" refer to a reference volume at 273.15 K temperature and 1013.25 hPa pressure. + + + + m-1 + + + The volume scattering/absorption/attenuation coefficient is the fractional change of radiative flux per unit path length due to the stated process. Coefficients with canonical units of m2 s-1 i.e. multiplied by density have standard names with specific_ instead of volume_. A scattering_angle should not be specified with this quantity. The scattering/absorption/attenuation coefficient is assumed to be an integral over all wavelengths, unless a coordinate of radiation_wavelength is included to specify the wavelength. Radiative flux is the sum of shortwave and longwave radiative fluxes. "Aerosol" means the system of suspended liquid or solid particles in air (except cloud droplets) and their carrier gas, the air itself. Aerosol particles take up ambient water (a process known as hygroscopic growth) depending on the relative humidity and the composition of the particles. "Dry aerosol particles" means aerosol particles without any water uptake. The specification of a physical process by the phrase "due_to_" process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. "Pm10 aerosol particles" means atmospheric particulate compounds with an aerodynamic diameter of less than or equal to 10 micrometers. + + + + m-1 + + + The volume scattering/absorption/attenuation coefficient is the fractional change of radiative flux per unit path length due to the stated process. Coefficients with canonical units of m2 s-1 i.e. multiplied by density have standard names with specific_ instead of volume_. A scattering_angle should not be specified with this quantity. The scattering/absorption/attenuation coefficient is assumed to be an integral over all wavelengths, unless a coordinate of radiation_wavelength is included to specify the wavelength. Radiative flux is the sum of shortwave and longwave radiative fluxes. "Aerosol" means the system of suspended liquid or solid particles in air (except cloud droplets) and their carrier gas, the air itself. Aerosol particles take up ambient water (a process known as hygroscopic growth) depending on the relative humidity and the composition of the particles. "Dry aerosol particles" means aerosol particles without any water uptake. The specification of a physical process by the phrase "due_to_" process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. "Pm10 aerosol particles" means atmospheric particulate compounds with an aerodynamic diameter of less than or equal to 10 micrometers. "Standard_temperature_and_pressure" refer to a reference volume at 273.15 K temperature and 1013.25 hPa pressure. + + + + m-1 + + + The volume scattering/absorption/attenuation coefficient is the fractional change of radiative flux per unit path length due to the stated process. Coefficients with canonical units of m2 s-1 i.e. multiplied by density have standard names with specific_ instead of volume_. A scattering_angle should not be specified with this quantity. The scattering/absorption/attenuation coefficient is assumed to be an integral over all wavelengths, unless a coordinate of radiation_wavelength is included to specify the wavelength. Radiative flux is the sum of shortwave and longwave radiative fluxes. "Aerosol" means the system of suspended liquid or solid particles in air (except cloud droplets) and their carrier gas, the air itself. To specify the relative humidity at which the sample was measured, provide a scalar coordinate variable with the standard name of "relative_humidity". The specification of a physical process by the phrase "due_to_" process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. "Pm1 aerosol particles" means atmospheric particulate compounds with an aerodynamic diameter of less than or equal to 1 micrometers. + + + + m-1 + + + The volume scattering/absorption/attenuation coefficient is the fractional change of radiative flux per unit path length due to the stated process. Coefficients with canonical units of m2 s-1 i.e. multiplied by density have standard names with specific_ instead of volume_. A scattering_angle should not be specified with this quantity. The scattering/absorption/attenuation coefficient is assumed to be an integral over all wavelengths, unless a coordinate of radiation_wavelength is included to specify the wavelength. Radiative flux is the sum of shortwave and longwave radiative fluxes. "Aerosol" means the system of suspended liquid or solid particles in air (except cloud droplets) and their carrier gas, the air itself. To specify the relative humidity at which the sample was measured, provide a scalar coordinate variable with the standard name of "relative_humidity". The specification of a physical process by the phrase "due_to_" process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. "Pm1 aerosol particles" means atmospheric particulate compounds with an aerodynamic diameter of less than or equal to 1 micrometers. "Standard_temperature_and_pressure" refer to a reference volume at 273.15 K temperature and 1013.25 hPa pressure. + + + + m-1 + + + The volume scattering/absorption/attenuation coefficient is the fractional change of radiative flux per unit path length due to the stated process. Coefficients with canonical units of m2 s-1 i.e. multiplied by density have standard names with specific_ instead of volume_. A scattering_angle should not be specified with this quantity. The scattering/absorption/attenuation coefficient is assumed to be an integral over all wavelengths, unless a coordinate of radiation_wavelength is included to specify the wavelength. Radiative flux is the sum of shortwave and longwave radiative fluxes. "Aerosol" means the system of suspended liquid or solid particles in air (except cloud droplets) and their carrier gas, the air itself. "Ambient aerosol particles" are aerosol particles that have taken up ambient water through hygroscopic growth. The extent of hygroscopic growth depends on the relative humidity and the composition of the particles. To specify the relative humidity at which the sample was measured, provide a scalar coordinate variable with the standard name of "relative_humidity". The specification of a physical process by the phrase "due_to_" process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. "Pm1 aerosol particles" means atmospheric particulate compounds with an aerodynamic diameter of less than or equal to 1 micrometers. + + + + m-1 + + + The volume scattering/absorption/attenuation coefficient is the fractional change of radiative flux per unit path length due to the stated process. Coefficients with canonical units of m2 s-1 i.e. multiplied by density have standard names with specific_ instead of volume_. A scattering_angle should not be specified with this quantity. The scattering/absorption/attenuation coefficient is assumed to be an integral over all wavelengths, unless a coordinate of radiation_wavelength is included to specify the wavelength. Radiative flux is the sum of shortwave and longwave radiative fluxes. Radiative flux is the sum of shortwave and longwave radiative fluxes. "Aerosol" means the system of suspended liquid or solid particles in air (except cloud droplets) and their carrier gas, the air itself. "Ambient aerosol particles" are aerosol particles that have taken up ambient water through hygroscopic growth. The extent of hygroscopic growth depends on the relative humidity and the composition of the particles. To specify the relative humidity at which the sample was measured, provide a scalar coordinate variable with the standard name of "relative_humidity". The specification of a physical process by the phrase "due_to_" process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. "Pm1 aerosol particles" means atmospheric particulate compounds with an aerodynamic diameter of less than or equal to 1 micrometers. "Standard_temperature_and_pressure" refer to a reference volume at 273.15 K temperature and 1013.25 hPa pressure. + + + + m-1 + + + The volume scattering/absorption/attenuation coefficient is the fractional change of radiative flux per unit path length due to the stated process. Coefficients with canonical units of m2 s-1 i.e. multiplied by density have standard names with specific_ instead of volume_. A scattering_angle should not be specified with this quantity. The scattering/absorption/attenuation coefficient is assumed to be an integral over all wavelengths, unless a coordinate of radiation_wavelength is included to specify the wavelength. Radiative flux is the sum of shortwave and longwave radiative fluxes. Radiative flux is the sum of shortwave and longwave radiative fluxes. "Aerosol" means the system of suspended liquid or solid particles in air (except cloud droplets) and their carrier gas, the air itself. "Dried_aerosol_particles" means that the aerosol sample has been dried from the ambient state before sizing, but that the dry state (relative humidity less than 40 per cent) has not necessarily been reached. To specify the relative humidity at which the sample was measured, provide a scalar coordinate variable with the standard name of "relative_humidity". The specification of a physical process by the phrase "due_to_" process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. "Pm1 aerosol particles" means atmospheric particulate compounds with an aerodynamic diameter of less than or equal to 1 micrometers. + + + + m-1 + + + The volume scattering/absorption/attenuation coefficient is the fractional change of radiative flux per unit path length due to the stated process. Coefficients with canonical units of m2 s-1 i.e. multiplied by density have standard names with specific_ instead of volume_. A scattering_angle should not be specified with this quantity. The scattering/absorption/attenuation coefficient is assumed to be an integral over all wavelengths, unless a coordinate of radiation_wavelength is included to specify the wavelength. Radiative flux is the sum of shortwave and longwave radiative fluxes. "Aerosol" means the system of suspended liquid or solid particles in air (except cloud droplets) and their carrier gas, the air itself. "Dried_aerosol_particles" means that the aerosol sample has been dried from the ambient state before sizing, but that the dry state (relative humidity less than 40 per cent) has not necessarily been reached. To specify the relative humidity at which the sample was measured, provide a scalar coordinate variable with the standard name of "relative_humidity". The specification of a physical process by the phrase "due_to_" process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. "Pm1 aerosol particles" means atmospheric particulate compounds with an aerodynamic diameter of less than or equal to 1 micrometers. "Standard_temperature_and_pressure" refer to a reference volume at 273.15 K temperature and 1013.25 hPa pressure. + + + + m-1 + + + The volume scattering/absorption/attenuation coefficient is the fractional change of radiative flux per unit path length due to the stated process. Coefficients with canonical units of m2 s-1 i.e. multiplied by density have standard names with specific_ instead of volume_. A scattering_angle should not be specified with this quantity. The scattering/absorption/attenuation coefficient is assumed to be an integral over all wavelengths, unless a coordinate of radiation_wavelength is included to specify the wavelength. Radiative flux is the sum of shortwave and longwave radiative fluxes. "Aerosol" means the system of suspended liquid or solid particles in air (except cloud droplets) and their carrier gas, the air itself. Aerosol particles take up ambient water (a process known as hygroscopic growth) depending on the relative humidity and the composition of the particles. "Dry aerosol particles" means aerosol particles without any water uptake. The specification of a physical process by the phrase "due_to_" process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. "Pm1 aerosol particles" means atmospheric particulate compounds with an aerodynamic diameter of less than or equal to 1 micrometers. + + + + m-1 + + + The volume scattering/absorption/attenuation coefficient is the fractional change of radiative flux per unit path length due to the stated process. Coefficients with canonical units of m2 s-1 i.e. multiplied by density have standard names with specific_ instead of volume_. A scattering_angle should not be specified with this quantity. The scattering/absorption/attenuation coefficient is assumed to be an integral over all wavelengths, unless a coordinate of radiation_wavelength is included to specify the wavelength. Radiative flux is the sum of shortwave and longwave radiative fluxes. "Aerosol" means the system of suspended liquid or solid particles in air (except cloud droplets) and their carrier gas, the air itself. Aerosol particles take up ambient water (a process known as hygroscopic growth) depending on the relative humidity and the composition of the particles. "Dry aerosol particles" means aerosol particles without any water uptake. The specification of a physical process by the phrase "due_to_" process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. "Pm1 aerosol particles" means atmospheric particulate compounds with an aerodynamic diameter of less than or equal to 1 micrometers. "Standard_temperature_and_pressure" refer to a reference volume at 273.15 K temperature and 1013.25 hPa pressure. + + + + m-1 + + + The volume scattering/absorption/attenuation coefficient is the fractional change of radiative flux per unit path length due to the stated process. Coefficients with canonical units of m2 s-1 i.e. multiplied by density have standard names with specific_ instead of volume_. A scattering_angle should not be specified with this quantity. The scattering/absorption/attenuation coefficient is assumed to be an integral over all wavelengths, unless a coordinate of radiation_wavelength is included to specify the wavelength. Radiative flux is the sum of shortwave and longwave radiative fluxes. "Aerosol" means the system of suspended liquid or solid particles in air (except cloud droplets) and their carrier gas, the air itself. To specify the relative humidity at which the sample was measured, provide a scalar coordinate variable with the standard name of "relative_humidity". The specification of a physical process by the phrase "due_to_" process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. "Pm2p5 aerosol particles" means atmospheric particulate compounds with an aerodynamic diameter of less than or equal to 2.5 micrometers. + + + + m-1 + + + The volume scattering/absorption/attenuation coefficient is the fractional change of radiative flux per unit path length due to the stated process. Coefficients with canonical units of m2 s-1 i.e. multiplied by density have standard names with specific_ instead of volume_. A scattering_angle should not be specified with this quantity. The scattering/absorption/attenuation coefficient is assumed to be an integral over all wavelengths, unless a coordinate of radiation_wavelength is included to specify the wavelength. Radiative flux is the sum of shortwave and longwave radiative fluxes. "Aerosol" means the system of suspended liquid or solid particles in air (except cloud droplets) and their carrier gas, the air itself. To specify the relative humidity at which the sample was measured, provide a scalar coordinate variable with the standard name of "relative_humidity". The specification of a physical process by the phrase "due_to_" process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. "Pm2p5 aerosol particles" means atmospheric particulate compounds with an aerodynamic diameter of less than or equal to 2.5 micrometers. "Standard_temperature_and_pressure" refer to a reference volume at 273.15 K temperature and 1013.25 hPa pressure. + + + + m-1 + + + The volume scattering/absorption/attenuation coefficient is the fractional change of radiative flux per unit path length due to the stated process. Coefficients with canonical units of m2 s-1 i.e. multiplied by density have standard names with specific_ instead of volume_. A scattering_angle should not be specified with this quantity. The scattering/absorption/attenuation coefficient is assumed to be an integral over all wavelengths, unless a coordinate of radiation_wavelength is included to specify the wavelength. Radiative flux is the sum of shortwave and longwave radiative fluxes. "Aerosol" means the system of suspended liquid or solid particles in air (except cloud droplets) and their carrier gas, the air itself. "Ambient aerosol particles" are aerosol particles that have taken up ambient water through hygroscopic growth. The extent of hygroscopic growth depends on the relative humidity and the composition of the particles. To specify the relative humidity at which the sample was measured, provide a scalar coordinate variable with the standard name of "relative_humidity". The specification of a physical process by the phrase "due_to_" process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. "Pm2p5 aerosol particles" means atmospheric particulate compounds with an aerodynamic diameter of less than or equal to 2.5 micrometers. + + + + m-1 + + + The volume scattering/absorption/attenuation coefficient is the fractional change of radiative flux per unit path length due to the stated process. Coefficients with canonical units of m2 s-1 i.e. multiplied by density have standard names with specific_ instead of volume_. A scattering_angle should not be specified with this quantity. The scattering/absorption/attenuation coefficient is assumed to be an integral over all wavelengths, unless a coordinate of radiation_wavelength is included to specify the wavelength. Radiative flux is the sum of shortwave and longwave radiative fluxes. "Aerosol" means the system of suspended liquid or solid particles in air (except cloud droplets) and their carrier gas, the air itself. "Ambient aerosol particles" are aerosol particles that have taken up ambient water through hygroscopic growth. The extent of hygroscopic growth depends on the relative humidity and the composition of the particles. To specify the relative humidity at which the sample was measured, provide a scalar coordinate variable with the standard name of "relative_humidity". The specification of a physical process by the phrase "due_to_" process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. "Pm2p5 aerosol particles" means atmospheric particulate compounds with an aerodynamic diameter of less than or equal to 2.5 micrometers. "Standard_temperature_and_pressure" refer to a reference volume at 273.15 K temperature and 1013.25 hPa pressure. + + + + m-1 + + + The volume scattering/absorption/attenuation coefficient is the fractional change of radiative flux per unit path length due to the stated process. Coefficients with canonical units of m2 s-1 i.e. multiplied by density have standard names with specific_ instead of volume_. A scattering_angle should not be specified with this quantity. The scattering/absorption/attenuation coefficient is assumed to be an integral over all wavelengths, unless a coordinate of radiation_wavelength is included to specify the wavelength. Radiative flux is the sum of shortwave and longwave radiative fluxes. "Aerosol" means the system of suspended liquid or solid particles in air (except cloud droplets) and their carrier gas, the air itself. "Dried_aerosol_particles" means that the aerosol sample has been dried from the ambient state before sizing, but that the dry state (relative humidity less than 40 per cent) has not necessarily been reached. To specify the relative humidity at which the sample was measured, provide a scalar coordinate variable with the standard name of "relative_humidity". The specification of a physical process by the phrase "due_to_" process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. "Pm10 aerosol particles" means atmospheric particulate compounds with an aerodynamic diameter of less than or equal to 10 micrometers. + + + + m-1 + + + The volume scattering/absorption/attenuation coefficient is the fractional change of radiative flux per unit path length due to the stated process. Coefficients with canonical units of m2 s-1 i.e. multiplied by density have standard names with specific_ instead of volume_. A scattering_angle should not be specified with this quantity. The scattering/absorption/attenuation coefficient is assumed to be an integral over all wavelengths, unless a coordinate of radiation_wavelength is included to specify the wavelength. Radiative flux is the sum of shortwave and longwave radiative fluxes. "Aerosol" means the system of suspended liquid or solid particles in air (except cloud droplets) and their carrier gas, the air itself. "Dried_aerosol_particles" means that the aerosol sample has been dried from the ambient state before sizing, but that the dry state (relative humidity less than 40 per cent) has not necessarily been reached. To specify the relative humidity at which the sample was measured, provide a scalar coordinate variable with the standard name of "relative_humidity". The specification of a physical process by the phrase "due_to_" process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. "Pm2p5 aerosol particles" means atmospheric particulate compounds with an aerodynamic diameter of less than or equal to 2.5 micrometers. "Standard_temperature_and_pressure" refer to a reference volume at 273.15 K temperature and 1013.25 hPa pressure. + + + + m-1 + + + The volume scattering/absorption/attenuation coefficient is the fractional change of radiative flux per unit path length due to the stated process. Coefficients with canonical units of m2 s-1 i.e. multiplied by density have standard names with specific_ instead of volume_. A scattering_angle should not be specified with this quantity. The scattering/absorption/attenuation coefficient is assumed to be an integral over all wavelengths, unless a coordinate of radiation_wavelength is included to specify the wavelength. Radiative flux is the sum of shortwave and longwave radiative fluxes. "Aerosol" means the system of suspended liquid or solid particles in air (except cloud droplets) and their carrier gas, the air itself. Aerosol particles take up ambient water (a process known as hygroscopic growth) depending on the relative humidity and the composition of the particles. "Dry aerosol particles" means aerosol particles without any water uptake. The specification of a physical process by the phrase "due_to_" process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. "Pm2p5 aerosol particles" means atmospheric particulate compounds with an aerodynamic diameter of less than or equal to 2.5 micrometers. + + + + m-1 + + + The volume scattering/absorption/attenuation coefficient is the fractional change of radiative flux per unit path length due to the stated process. Coefficients with canonical units of m2 s-1 i.e. multiplied by density have standard names with specific_ instead of volume_. A scattering_angle should not be specified with this quantity. The scattering/absorption/attenuation coefficient is assumed to be an integral over all wavelengths, unless a coordinate of radiation_wavelength is included to specify the wavelength. Radiative flux is the sum of shortwave and longwave radiative fluxes. "Aerosol" means the system of suspended liquid or solid particles in air (except cloud droplets) and their carrier gas, the air itself. Aerosol particles take up ambient water (a process known as hygroscopic growth) depending on the relative humidity and the composition of the particles. "Dry aerosol particles" means aerosol particles without any water uptake. The specification of a physical process by the phrase "due_to_" process means that the quantity named is a single term in a sum of terms which together compose the general quantity named by omitting the phrase. "Pm2p5 aerosol particles" means atmospheric particulate compounds with an aerodynamic diameter of less than or equal to 2.5 micrometers. "Standard_temperature_and_pressure" refer to a reference volume at 273.15 K temperature and 1013.25 hPa pressure. + + m-1 @@ -32820,21 +33745,21 @@ http://vocab.nerc.ac.uk/collection/P01/current/TPHSDSZZ/6/. K - Wet bulb potential temperature is the temperature a parcel of air would have if moved dry adiabatically until it reaches saturation and thereafter moist adiabatically to sea level pressure. + Wet bulb potential temperature is the temperature a parcel of air would have if moved dry adiabatically until it reaches saturation and thereafter moist adiabatically to sea level pressure. It is strongly recommended that a variable with this standard name should have a units_metadata attribute, with one of the values "on-scale" or "difference", whichever is appropriate for the data, because it is essential to know whether the temperature is on-scale (meaning relative to the origin of the scale indicated by the units) or refers to temperature differences (implying that the origin of the temperature scale is irrevelant), in order to convert the units correctly (cf. https://cfconventions.org/cf-conventions/cf-conventions.html#temperature-units). K - + It is strongly recommended that a variable with this standard name should have a units_metadata attribute, with one of the values "on-scale" or "difference", whichever is appropriate for the data, because it is essential to know whether the temperature is on-scale (meaning relative to the origin of the scale indicated by the units) or refers to temperature differences (implying that the origin of the temperature scale is irrevelant), in order to convert the units correctly (cf. https://cfconventions.org/cf-conventions/cf-conventions.html#temperature-units). K - Air temperature is the bulk temperature of the air, not the surface (skin) temperature. The quantity with standard name wind_chill_of_air_temperature is the perceived air temperature when wind is factored in with the ambient air temperature (which makes it feel colder than the actual air temperature). Wind chill is based on the rate of heat loss from exposed skin caused by wind and cold. Wind chill temperature is only defined for ambient temperatures at or below 283.1 K and wind speeds above 1.34 m s-1. References: https://www.weather.gov/safety/cold-wind-chill-chart; WMO codes registry entry http://codes.wmo.int/grib2/codeflag/4.2/0-0-13. + Air temperature is the bulk temperature of the air, not the surface (skin) temperature. The quantity with standard name wind_chill_of_air_temperature is the perceived air temperature when wind is factored in with the ambient air temperature (which makes it feel colder than the actual air temperature). Wind chill is based on the rate of heat loss from exposed skin caused by wind and cold. Wind chill temperature is only defined for ambient temperatures at or below 283.1 K and wind speeds above 1.34 m s-1. References: https://www.weather.gov/safety/cold-wind-chill-chart; WMO codes registry entry http://codes.wmo.int/grib2/codeflag/4.2/0-0-13. It is strongly recommended that a variable with this standard name should have a units_metadata attribute, with one of the values "on-scale" or "difference", whichever is appropriate for the data, because it is essential to know whether the temperature is on-scale (meaning relative to the origin of the scale indicated by the units) or refers to temperature differences (implying that the origin of the temperature scale is irrevelant), in order to convert the units correctly (cf. https://cfconventions.org/cf-conventions/cf-conventions.html#temperature-units). @@ -33066,34 +33991,10 @@ http://vocab.nerc.ac.uk/collection/P01/current/TPHSDSZZ/6/. wind_mixing_energy_flux_into_sea_water - - mole_fraction_of_chlorine_dioxide_in_air - - - - mole_fraction_of_chlorine_monoxide_in_air - - - - mole_fraction_of_hypochlorous_acid_in_air - - surface_net_downward_radiative_flux - - surface_temperature - - - - surface_temperature - - - - surface_temperature - - surface_upward_sensible_heat_flux @@ -33268,9 +34169,6 @@ http://vocab.nerc.ac.uk/collection/P01/current/TPHSDSZZ/6/. surface_downward_mole_flux_of_carbon_dioxide - - - surface_upward_mole_flux_of_carbon_dioxide @@ -34166,14 +35064,6 @@ http://vocab.nerc.ac.uk/collection/P01/current/TPHSDSZZ/6/. tendency_of_mass_concentration_of_elemental_carbon_dry_aerosol_particles_in_air_due_to_emission_from_aviation - - integral_wrt_time_of_air_temperature_deficit - - - - integral_wrt_time_of_air_temperature_excess - - integral_wrt_time_of_surface_downward_latent_heat_flux @@ -34234,10 +35124,6 @@ http://vocab.nerc.ac.uk/collection/P01/current/TPHSDSZZ/6/. tendency_of_sea_water_salinity_due_to_parameterized_eddy_advection - - tendency_of_sea_water_temperature_due_to_parameterized_eddy_advection - - northward_sea_water_velocity_due_to_parameterized_mesoscale_eddies @@ -34386,22 +35272,6 @@ http://vocab.nerc.ac.uk/collection/P01/current/TPHSDSZZ/6/. integral_wrt_depth_of_sea_water_practical_salinity - - integral_wrt_depth_of_sea_water_temperature - - - - integral_wrt_depth_of_sea_water_temperature - - - - integral_wrt_depth_of_sea_water_temperature - - - - integral_wrt_depth_of_sea_water_temperature - - integral_wrt_height_of_product_of_eastward_wind_and_specific_humidity @@ -34642,10 +35512,6 @@ http://vocab.nerc.ac.uk/collection/P01/current/TPHSDSZZ/6/. stratiform_precipitation_flux - - tendency_of_air_temperature_due_to_stratiform_precipitation - - tendency_of_specific_humidity_due_to_stratiform_precipitation @@ -34674,10 +35540,6 @@ http://vocab.nerc.ac.uk/collection/P01/current/TPHSDSZZ/6/. water_vapor_partial_pressure_in_air - - volume_extinction_coefficient_in_air_due_to_ambient_aerosol_particles - - tendency_of_atmosphere_mole_concentration_of_carbon_monoxide_due_to_chemical_destruction @@ -34746,10 +35608,6 @@ http://vocab.nerc.ac.uk/collection/P01/current/TPHSDSZZ/6/. net_primary_mole_productivity_of_biomass_expressed_as_carbon_by_miscellaneous_phytoplankton - - rate_of_hydroxyl_radical_destruction_due_to_reaction_with_nmvoc - - tendency_of_atmosphere_moles_of_methane @@ -34758,10 +35616,6 @@ http://vocab.nerc.ac.uk/collection/P01/current/TPHSDSZZ/6/. mole_fraction_of_noy_expressed_as_nitrogen_in_air - - mole_fraction_of_dichlorine_peroxide_in_air - - mole_fraction_of_methylglyoxal_in_air @@ -34982,14 +35836,6 @@ http://vocab.nerc.ac.uk/collection/P01/current/TPHSDSZZ/6/. tendency_of_sea_water_salinity_expressed_as_salt_content_due_to_parameterized_dianeutral_mixing - - product_of_lagrangian_tendency_of_air_pressure_and_air_temperature - - - - product_of_lagrangian_tendency_of_air_pressure_and_air_temperature - - product_of_lagrangian_tendency_of_air_pressure_and_geopotential_height @@ -35018,14 +35864,6 @@ http://vocab.nerc.ac.uk/collection/P01/current/TPHSDSZZ/6/. volume_fraction_of_condensed_water_in_soil_at_wilting_point - - integral_wrt_depth_of_product_of_potential_temperature_and_sea_water_density - - - - integral_wrt_depth_of_product_of_conservative_temperature_and_sea_water_density - - integral_wrt_depth_of_product_of_salinity_and_sea_water_density @@ -35074,22 +35912,10 @@ http://vocab.nerc.ac.uk/collection/P01/current/TPHSDSZZ/6/. number_concentration_of_stratiform_cloud_liquid_water_particles_at_stratiform_liquid_water_cloud_top - - air_equivalent_potential_temperature - - mass_content_of_cloud_liquid_water_in_atmosphere_layer - - air_pseudo_equivalent_temperature - - - - air_equivalent_temperature - - effective_radius_of_cloud_liquid_water_particles_at_liquid_water_cloud_top @@ -35110,10 +35936,6 @@ http://vocab.nerc.ac.uk/collection/P01/current/TPHSDSZZ/6/. tendency_of_mass_fraction_of_stratiform_cloud_ice_in_air_due_to_melting_to_cloud_liquid_water - - air_pseudo_equivalent_potential_temperature - - growth_limitation_of_diazotrophic_phytoplankton_due_to_solar_irradiance @@ -35194,10 +36016,6 @@ http://vocab.nerc.ac.uk/collection/P01/current/TPHSDSZZ/6/. integral_wrt_time_of_surface_downward_eastward_stress - - temperature_in_surface_snow - - thermal_energy_content_of_surface_snow @@ -35218,10 +36036,6 @@ http://vocab.nerc.ac.uk/collection/P01/current/TPHSDSZZ/6/. biological_taxon_lsid - - temperature_in_ground - - water_evapotranspiration_flux @@ -35234,14 +36048,6 @@ http://vocab.nerc.ac.uk/collection/P01/current/TPHSDSZZ/6/. moles_of_particulate_inorganic_carbon_per_unit_mass_in_sea_water - - drainage_amount_through_base_of_soil_model - - - - universal_thermal_comfort_index - - water_flux_into_sea_water_due_to_flux_adjustment @@ -35250,16 +36056,20 @@ http://vocab.nerc.ac.uk/collection/P01/current/TPHSDSZZ/6/. heat_flux_into_sea_water_due_to_flux_adjustment - - upward_derivative_of_eastward_wind + + volume_backwards_scattering_coefficient_of_radiative_flux_in_air_due_to_dried_aerosol_particles - - upward_derivative_of_northward_wind + + volume_extinction_coefficient_of_radiative_flux_in_air_due_to_ambient_aerosol_particles - - volume_backwards_scattering_coefficient_of_radiative_flux_in_air_due_to_dried_aerosol_particles + + volume_extinction_coefficient_of_radiative_flux_in_air_due_to_ambient_aerosol_particles + + + + volume_extinction_coefficient_of_radiative_flux_in_air_due_to_cloud_particles @@ -35273,6 +36083,94 @@ http://vocab.nerc.ac.uk/collection/P01/current/TPHSDSZZ/6/. volume_absorption_coefficient_of_radiative_flux_in_air_due_to_dried_aerosol_particles + + + air_equivalent_temperature + + + + air_equivalent_potential_temperature + + + + air_pseudo_equivalent_potential_temperature + + + + air_pseudo_equivalent_temperature + + + + surface_temperature + + + + surface_temperature + + + + surface_temperature + + + + temperature_in_ground + + + + temperature_in_surface_snow + + + + integral_wrt_depth_of_product_of_conservative_temperature_and_sea_water_density + + + + integral_wrt_depth_of_product_of_potential_temperature_and_sea_water_density + + + + integral_wrt_depth_of_sea_water_temperature + + + + integral_wrt_depth_of_sea_water_temperature + + + + integral_wrt_depth_of_sea_water_temperature + + + + integral_wrt_depth_of_sea_water_temperature + + + + universal_thermal_comfort_index + + + + product_of_lagrangian_tendency_of_air_pressure_and_air_temperature + + + + product_of_lagrangian_tendency_of_air_pressure_and_air_temperature + + + + integral_wrt_time_of_air_temperature_deficit + + + + integral_wrt_time_of_air_temperature_excess + + + + tendency_of_air_temperature_due_to_stratiform_precipitation + + + + tendency_of_sea_water_temperature_due_to_parameterized_eddy_advection + From ff85847570f2d40b4726dc5aa19e733558a8f55d Mon Sep 17 00:00:00 2001 From: Patrick Peglar Date: Tue, 30 Jul 2024 12:24:18 +0100 Subject: [PATCH 10/26] Add mesh metas to the iris.common.metadata public API. (#6093) Co-authored-by: Martin Yeo <40734014+trexfeathers@users.noreply.github.com> --- lib/iris/common/metadata.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/iris/common/metadata.py b/lib/iris/common/metadata.py index 6f3e455b4d..8705c79816 100644 --- a/lib/iris/common/metadata.py +++ b/lib/iris/common/metadata.py @@ -28,6 +28,8 @@ "CoordMetadata", "CubeMetadata", "DimCoordMetadata", + "MeshCoordMetadata", + "MeshMetadata", "SERVICES", "SERVICES_COMBINE", "SERVICES_DIFFERENCE", From 10e7f46d3f7e65833fa2490d088f6f29e49b88ee Mon Sep 17 00:00:00 2001 From: Henry Wright <84939917+HGWright@users.noreply.github.com> Date: Tue, 30 Jul 2024 14:38:07 +0100 Subject: [PATCH 11/26] Whats new updates for v3.10.0rc0 . (#6101) * Whats new updates for v3.10.0rc0 . * numpy whatsnew entry --- docs/src/whatsnew/{latest.rst => 3.10.rst} | 39 ++++++-- docs/src/whatsnew/index.rst | 4 +- docs/src/whatsnew/latest.rst.template | 107 --------------------- 3 files changed, 33 insertions(+), 117 deletions(-) rename docs/src/whatsnew/{latest.rst => 3.10.rst} (81%) delete mode 100644 docs/src/whatsnew/latest.rst.template diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/3.10.rst similarity index 81% rename from docs/src/whatsnew/latest.rst rename to docs/src/whatsnew/3.10.rst index 4343b02f6e..419f3e5295 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/3.10.rst @@ -1,21 +1,35 @@ .. include:: ../common_links.inc -|iris_version| |build_date| [unreleased] -**************************************** +v3.10 (30 Jul 2024 [release candidate]) +*************************************** This document explains the changes made to Iris for this release (:doc:`View all changes `.) -.. dropdown:: |iris_version| Release Highlights +.. dropdown:: v3.10 Release Highlights :color: primary :icon: info :animate: fade-in :open: - The highlights for this major/minor release of Iris include: + The highlights for this minor release of Iris include: + + * Breaking Change: We have moved all of the mesh API from :mod:`iris.experimental.ugrid` to + :mod:`iris.mesh`. This is no longer experimental making this public supported API. + + * We have made a suite of mesh improvements, there is a separate entry below for each of these changes . + + * We have made :meth:`~iris.coords.guess_bounds` capable of setting bounds to the start and end of months and years. + + * We have significantly reduced warning noise during NetCDF loading. The datum :class:`python:FutureWarning` + will now only be raised if the + ``datum_support`` :class:`~iris.Future` flag is disabled AND a datum is + present on the loaded NetCDF grid mapping. + + * Checkout the performance enhancements section for an array of improvements to the performance of Iris. + Special thanks to the `ESMValTool`_ devs for these contributions. - * N/A And finally, get in touch with us on :issue:`GitHub` if you have any issues or feature requests for improving Iris. Enjoy! @@ -24,7 +38,12 @@ This document explains the changes made to Iris for this release 📢 Announcements ================ -#. N/A +#. Breaking Change: We have moved all of the mesh API from :mod:`iris.experimental.ugrid` to + :mod:`iris.mesh`. This is no longer experimental making this public supported API. + Future changes will honour Semantic Versioning - i.e. breaking changes will only be in major releases, + and ideally will be previewed via :class:`iris.Future` flags. + +#. Note that Iris is currently pinned to NumPy ``<2``, we hope to unpin this in the next minor release (Iris v3.11). ✨ Features @@ -127,7 +146,7 @@ This document explains the changes made to Iris for this release 🔥 Deprecations =============== -#. N/A +None! 🔗 Dependencies @@ -147,12 +166,15 @@ This document explains the changes made to Iris for this release See : https://github.com/matplotlib/matplotlib/issues/28567 (:pull:`6065`) +#. Note that Iris is currently pinned to NumPy ``<2``, we hope to unpin this in the next minor release (Iris v3.11). + 📚 Documentation ================ -#. `@hsteptoe`_ added more detailed examples to :class:`~iris.cube.Cube` functions :func:`~iris.cube.Cube.slices` and :func:`~iris.cube.Cube.slices_over`. (:pull:`5735`) +#. `@hsteptoe`_ added more detailed examples to :class:`~iris.cube.Cube` functions + :func:`~iris.cube.Cube.slices` and :func:`~iris.cube.Cube.slices_over`. (:pull:`5735`) 💼 Internal @@ -220,3 +242,4 @@ This document explains the changes made to Iris for this release Whatsnew resources in alphabetical order: .. _airspeed-velocity/asv#1397: https://github.com/airspeed-velocity/asv/pull/1397 +.. _ESMValTool: https://github.com/ESMValGroup/ESMValTool diff --git a/docs/src/whatsnew/index.rst b/docs/src/whatsnew/index.rst index 012e0b4498..45a5cad727 100644 --- a/docs/src/whatsnew/index.rst +++ b/docs/src/whatsnew/index.rst @@ -6,13 +6,13 @@ What's New in Iris ------------------ -.. include:: latest.rst +.. include:: 3.10.rst .. toctree:: :maxdepth: 1 :hidden: - latest.rst + 3.10.rst 3.9.rst 3.8.rst 3.7.rst diff --git a/docs/src/whatsnew/latest.rst.template b/docs/src/whatsnew/latest.rst.template deleted file mode 100644 index 80bf48dadd..0000000000 --- a/docs/src/whatsnew/latest.rst.template +++ /dev/null @@ -1,107 +0,0 @@ -.. include:: ../common_links.inc - -|iris_version| |build_date| [unreleased] -**************************************** - -This document explains the changes made to Iris for this release -(:doc:`View all changes `.) - - -.. dropdown:: |iris_version| Release Highlights - :color: primary - :icon: info - :animate: fade-in - :open: - - The highlights for this major/minor release of Iris include: - - * N/A - - And finally, get in touch with us on :issue:`GitHub` if you have - any issues or feature requests for improving Iris. Enjoy! - - -NOTE: section BELOW is a template for bugfix patches -==================================================== - (Please remove this section when creating an initial 'latest.rst' - -|iris_version| |build_date| -=========================== - -.. dropdown:: |iris_version| Patches - :color: primary - :icon: alert - :animate: fade-in - - The patches in this release of Iris include: - - #. N/A - -NOTE: section ABOVE is a template for bugfix patches -==================================================== - (Please remove this section when creating an initial 'latest.rst') - - -📢 Announcements -================ - -#. N/A - - -✨ Features -=========== - -#. N/A - - -🐛 Bugs Fixed -============= - -#. N/A - - -💣 Incompatible Changes -======================= - -#. N/A - - -🚀 Performance Enhancements -=========================== - -#. N/A - - -🔥 Deprecations -=============== - -#. N/A - - -🔗 Dependencies -=============== - -#. N/A - - -📚 Documentation -================ - -#. N/A - - -💼 Internal -=========== - -#. N/A - - -.. comment - Whatsnew author names (@github name) in alphabetical order. Note that, - core dev names are automatically included by the common_links.inc: - - - - -.. comment - Whatsnew resources in alphabetical order: \ No newline at end of file From f8d1e130c97751c0fb3f68b3ffd54afd50296b73 Mon Sep 17 00:00:00 2001 From: Henry Wright Date: Thu, 1 Aug 2024 12:58:37 +0100 Subject: [PATCH 12/26] Restore latest Whats New files. --- docs/src/whatsnew/index.rst | 3 +- docs/src/whatsnew/latest.rst | 86 +++++++++++++++++++++ docs/src/whatsnew/latest.rst.template | 107 ++++++++++++++++++++++++++ 3 files changed, 195 insertions(+), 1 deletion(-) create mode 100644 docs/src/whatsnew/latest.rst create mode 100644 docs/src/whatsnew/latest.rst.template diff --git a/docs/src/whatsnew/index.rst b/docs/src/whatsnew/index.rst index 45a5cad727..74cb0cd43d 100644 --- a/docs/src/whatsnew/index.rst +++ b/docs/src/whatsnew/index.rst @@ -6,12 +6,13 @@ What's New in Iris ------------------ -.. include:: 3.10.rst +.. include:: latest.rst .. toctree:: :maxdepth: 1 :hidden: + latest.rst 3.10.rst 3.9.rst 3.8.rst diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst new file mode 100644 index 0000000000..a174fd2cfb --- /dev/null +++ b/docs/src/whatsnew/latest.rst @@ -0,0 +1,86 @@ +.. include:: ../common_links.inc + +|iris_version| |build_date| [unreleased] +**************************************** + +This document explains the changes made to Iris for this release +(:doc:`View all changes `.) + + +.. dropdown:: |iris_version| Release Highlights + :color: primary + :icon: info + :animate: fade-in + :open: + + The highlights for this major/minor release of Iris include: + + * N/A + + And finally, get in touch with us on :issue:`GitHub` if you have + any issues or feature requests for improving Iris. Enjoy! + + +📢 Announcements +================ + +#. N/A + + +✨ Features +=========== + +#. N/A + + +🐛 Bugs Fixed +============= + +#. N/A + + +💣 Incompatible Changes +======================= + +#. N/A + + +🚀 Performance Enhancements +=========================== + +#. N/A + + +🔥 Deprecations +=============== + +#. N/A + + +🔗 Dependencies +=============== + +#. N/A + + +📚 Documentation +================ + +#. N/A + + +💼 Internal +=========== + +#. N/A + + +.. comment + Whatsnew author names (@github name) in alphabetical order. Note that, + core dev names are automatically included by the common_links.inc: + + + + +.. comment + Whatsnew resources in alphabetical order: diff --git a/docs/src/whatsnew/latest.rst.template b/docs/src/whatsnew/latest.rst.template new file mode 100644 index 0000000000..fedddec5c0 --- /dev/null +++ b/docs/src/whatsnew/latest.rst.template @@ -0,0 +1,107 @@ +.. include:: ../common_links.inc + +|iris_version| |build_date| [unreleased] +**************************************** + +This document explains the changes made to Iris for this release +(:doc:`View all changes `.) + + +.. dropdown:: |iris_version| Release Highlights + :color: primary + :icon: info + :animate: fade-in + :open: + + The highlights for this major/minor release of Iris include: + + * N/A + + And finally, get in touch with us on :issue:`GitHub` if you have + any issues or feature requests for improving Iris. Enjoy! + + +NOTE: section BELOW is a template for bugfix patches +==================================================== + (Please remove this section when creating an initial 'latest.rst') + +|iris_version| |build_date| +=========================== + +.. dropdown:: |iris_version| Patches + :color: primary + :icon: alert + :animate: fade-in + + The patches in this release of Iris include: + + #. N/A + +NOTE: section ABOVE is a template for bugfix patches +==================================================== + (Please remove this section when creating an initial 'latest.rst') + + +📢 Announcements +================ + +#. N/A + + +✨ Features +=========== + +#. N/A + + +🐛 Bugs Fixed +============= + +#. N/A + + +💣 Incompatible Changes +======================= + +#. N/A + + +🚀 Performance Enhancements +=========================== + +#. N/A + + +🔥 Deprecations +=============== + +#. N/A + + +🔗 Dependencies +=============== + +#. N/A + + +📚 Documentation +================ + +#. N/A + + +💼 Internal +=========== + +#. N/A + + +.. comment + Whatsnew author names (@github name) in alphabetical order. Note that, + core dev names are automatically included by the common_links.inc: + + + + +.. comment + Whatsnew resources in alphabetical order: \ No newline at end of file From dd7174058b1303dd51aafcaaf5d942bb114e4c85 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 6 Aug 2024 10:51:30 +0100 Subject: [PATCH 13/26] Bump scitools/workflows from 2024.07.6 to 2024.08.0 (#6105) Bumps [scitools/workflows](https://github.com/scitools/workflows) from 2024.07.6 to 2024.08.0. - [Release notes](https://github.com/scitools/workflows/releases) - [Commits](https://github.com/scitools/workflows/compare/2024.07.6...2024.08.0) --- updated-dependencies: - dependency-name: scitools/workflows dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci-manifest.yml | 2 +- .github/workflows/refresh-lockfiles.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci-manifest.yml b/.github/workflows/ci-manifest.yml index b0c85425af..a70ffc23db 100644 --- a/.github/workflows/ci-manifest.yml +++ b/.github/workflows/ci-manifest.yml @@ -23,4 +23,4 @@ concurrency: jobs: manifest: name: "check-manifest" - uses: scitools/workflows/.github/workflows/ci-manifest.yml@2024.07.6 + uses: scitools/workflows/.github/workflows/ci-manifest.yml@2024.08.0 diff --git a/.github/workflows/refresh-lockfiles.yml b/.github/workflows/refresh-lockfiles.yml index 2aa2e82d44..7d235678c8 100644 --- a/.github/workflows/refresh-lockfiles.yml +++ b/.github/workflows/refresh-lockfiles.yml @@ -14,5 +14,5 @@ on: jobs: refresh_lockfiles: - uses: scitools/workflows/.github/workflows/refresh-lockfiles.yml@2024.07.6 + uses: scitools/workflows/.github/workflows/refresh-lockfiles.yml@2024.08.0 secrets: inherit From 8a15e4a839cca463ef8da25ce3b431689e3b024b Mon Sep 17 00:00:00 2001 From: tkknight <2108488+tkknight@users.noreply.github.com> Date: Wed, 7 Aug 2024 17:44:08 +0100 Subject: [PATCH 14/26] fix typo for class reference (#6106) --- lib/iris/experimental/geovista.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/iris/experimental/geovista.py b/lib/iris/experimental/geovista.py index 07413f1529..57cbded2c2 100644 --- a/lib/iris/experimental/geovista.py +++ b/lib/iris/experimental/geovista.py @@ -35,7 +35,7 @@ def cube_to_polydata(cube, **kwargs): ---------- cube : :class:`~iris.cube.Cube` The Cube containing the spatial information and data for creating the - class:`~pyvista.PolyData`. + :class:`~pyvista.PolyData`. **kwargs : dict, optional Additional keyword arguments to be passed to the relevant From 9c3f3a70155a28882d06963f8a5e7d7d38fbe20f Mon Sep 17 00:00:00 2001 From: tkknight <2108488+tkknight@users.noreply.github.com> Date: Sun, 11 Aug 2024 17:23:54 +0100 Subject: [PATCH 15/26] remove --force option as it is not present in conda (only mamba) (#6111) --- docs/src/installing.rst | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/docs/src/installing.rst b/docs/src/installing.rst index 667ff932b3..a0a3fd2c62 100644 --- a/docs/src/installing.rst +++ b/docs/src/installing.rst @@ -72,16 +72,9 @@ Once conda is installed, you can create a development environment for Iris using conda and then activate it. The example commands below assume you are in the root directory of your local copy of Iris:: - conda env create --force --file=requirements/iris.yml + conda env create --file=requirements/iris.yml conda activate iris-dev -.. note:: - - The ``--force`` option, used when creating the environment, first removes - any previously existing ``iris-dev`` environment of the same name. This is - particularly useful when rebuilding your environment due to a change in - requirements. - The ``requirements/iris.yml`` file defines the Iris development conda environment *name* and all the relevant *top level* `conda-forge` package dependencies that you need to **code**, **test**, and **build** the From ac3c91409cbed35595ff66917b2a50202c1bd707 Mon Sep 17 00:00:00 2001 From: Henry Wright <84939917+HGWright@users.noreply.github.com> Date: Tue, 13 Aug 2024 11:26:07 +0100 Subject: [PATCH 16/26] Whats new updates for v3.10.0 . (#6112) --- docs/src/whatsnew/3.10.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/src/whatsnew/3.10.rst b/docs/src/whatsnew/3.10.rst index 419f3e5295..9007f6f9a6 100644 --- a/docs/src/whatsnew/3.10.rst +++ b/docs/src/whatsnew/3.10.rst @@ -1,7 +1,7 @@ .. include:: ../common_links.inc -v3.10 (30 Jul 2024 [release candidate]) -*************************************** +v3.10 (13 Aug 2024) +******************* This document explains the changes made to Iris for this release (:doc:`View all changes `.) From b8f554f7b8899f2ff6754857d59fc48944ef8e9b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 14 Aug 2024 12:30:52 +0100 Subject: [PATCH 17/26] Bump scitools/workflows from 2024.08.0 to 2024.08.1 (#6116) Bumps [scitools/workflows](https://github.com/scitools/workflows) from 2024.08.0 to 2024.08.1. - [Release notes](https://github.com/scitools/workflows/releases) - [Commits](https://github.com/scitools/workflows/compare/2024.08.0...2024.08.1) --- updated-dependencies: - dependency-name: scitools/workflows dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci-manifest.yml | 2 +- .github/workflows/refresh-lockfiles.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci-manifest.yml b/.github/workflows/ci-manifest.yml index a70ffc23db..73977f7d4c 100644 --- a/.github/workflows/ci-manifest.yml +++ b/.github/workflows/ci-manifest.yml @@ -23,4 +23,4 @@ concurrency: jobs: manifest: name: "check-manifest" - uses: scitools/workflows/.github/workflows/ci-manifest.yml@2024.08.0 + uses: scitools/workflows/.github/workflows/ci-manifest.yml@2024.08.1 diff --git a/.github/workflows/refresh-lockfiles.yml b/.github/workflows/refresh-lockfiles.yml index 7d235678c8..dd4d8bdc30 100644 --- a/.github/workflows/refresh-lockfiles.yml +++ b/.github/workflows/refresh-lockfiles.yml @@ -14,5 +14,5 @@ on: jobs: refresh_lockfiles: - uses: scitools/workflows/.github/workflows/refresh-lockfiles.yml@2024.08.0 + uses: scitools/workflows/.github/workflows/refresh-lockfiles.yml@2024.08.1 secrets: inherit From 773c55ba02b9e1d849d96f6a0a8dd56a0e83e79b Mon Sep 17 00:00:00 2001 From: Elias <110238618+ESadek-MO@users.noreply.github.com> Date: Fri, 23 Aug 2024 16:10:56 +0100 Subject: [PATCH 18/26] Include ancillary variables and cell_measures during intersect (#5804) * added rough fix, needs tests and refactoring * consolidated * added tests * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * review comments * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- lib/iris/cube.py | 49 ++++++++++++++----- .../Cube/intersection__Metadata/metadata.cml | 26 ++++++++++ .../metadata_wrapped.cml | 26 ++++++++++ lib/iris/tests/unit/cube/test_Cube.py | 16 ++++++ 4 files changed, 105 insertions(+), 12 deletions(-) diff --git a/lib/iris/cube.py b/lib/iris/cube.py index 54e086937d..bc90443a51 100644 --- a/lib/iris/cube.py +++ b/lib/iris/cube.py @@ -92,8 +92,8 @@ def from_cubes(cubes, constraints=None): constraints = iris._constraints.list_of_constraints(constraints) pairs = [_CubeFilter(constraint) for constraint in constraints] collection = _CubeFilterCollection(pairs) - for cube in cubes: - collection.add_cube(cube) + for c in cubes: + collection.add_cube(c) return collection def __init__(self, pairs): @@ -132,8 +132,8 @@ def __init__(self, *args, **kwargs): # Do whatever a list does, to initialise ourself "as a list" super().__init__(*args, **kwargs) # Check that all items in the list are cubes. - for cube in self: - self._assert_is_cube(cube) + for c in self: + self._assert_is_cube(c) def __str__(self): """Run short :meth:`Cube.summary` on every cube.""" @@ -308,9 +308,9 @@ def _extract_and_merge(cubes, constraints, strict=False, return_single_cube=Fals constraint_groups = dict( [(constraint, CubeList()) for constraint in constraints] ) - for cube in cubes: + for c in cubes: for constraint, cube_list in constraint_groups.items(): - sub_cube = constraint.extract(cube) + sub_cube = constraint.extract(c) if sub_cube is not None: cube_list.append(sub_cube) @@ -394,8 +394,8 @@ def merge_cube(self): # Register each of our cubes with a single ProtoCube. proto_cube = iris._merge.ProtoCube(self[0]) - for cube in self[1:]: - proto_cube.register(cube, error_on_mismatch=True) + for c in self[1:]: + proto_cube.register(c, error_on_mismatch=True) # Extract the merged cube from the ProtoCube. (merged_cube,) = proto_cube.merge() @@ -471,18 +471,18 @@ def merge(self, unique=True): """ # Register each of our cubes with its appropriate ProtoCube. proto_cubes_by_name = {} - for cube in self: - name = cube.standard_name + for c in self: + name = c.standard_name proto_cubes = proto_cubes_by_name.setdefault(name, []) proto_cube = None for target_proto_cube in proto_cubes: - if target_proto_cube.register(cube): + if target_proto_cube.register(c): proto_cube = target_proto_cube break if proto_cube is None: - proto_cube = iris._merge.ProtoCube(cube) + proto_cube = iris._merge.ProtoCube(c) proto_cubes.append(proto_cube) # Emulate Python 2 behaviour. @@ -3175,8 +3175,33 @@ def create_coords(src_coords, add_coord): add_coord(result_coord, dims) coord_mapping[id(src_coord)] = result_coord + def create_metadata(src_metadatas, add_metadata, get_metadata): + for src_metadata in src_metadatas: + dims = src_metadata.cube_dims(self) + if dim in dims: + dim_within_coord = dims.index(dim) + data = np.concatenate( + [ + get_metadata(chunk, src_metadata.name()).core_data() + for chunk in chunks + ], + dim_within_coord, + ) + result_coord = src_metadata.copy(values=data) + else: + result_coord = src_metadata.copy() + add_metadata(result_coord, dims) + create_coords(self.dim_coords, result.add_dim_coord) create_coords(self.aux_coords, result.add_aux_coord) + create_metadata( + self.cell_measures(), result.add_cell_measure, Cube.cell_measure + ) + create_metadata( + self.ancillary_variables(), + result.add_ancillary_variable, + Cube.ancillary_variable, + ) for factory in self.aux_factories: result.add_aux_factory(factory.updated(coord_mapping)) return result diff --git a/lib/iris/tests/results/unit/cube/Cube/intersection__Metadata/metadata.cml b/lib/iris/tests/results/unit/cube/Cube/intersection__Metadata/metadata.cml index f1f37e23b9..54d1f2311b 100644 --- a/lib/iris/tests/results/unit/cube/Cube/intersection__Metadata/metadata.cml +++ b/lib/iris/tests/results/unit/cube/Cube/intersection__Metadata/metadata.cml @@ -62,6 +62,32 @@ + + + + + + + + + + diff --git a/lib/iris/tests/results/unit/cube/Cube/intersection__Metadata/metadata_wrapped.cml b/lib/iris/tests/results/unit/cube/Cube/intersection__Metadata/metadata_wrapped.cml index 48f0fa1aaa..4b2e03ad30 100644 --- a/lib/iris/tests/results/unit/cube/Cube/intersection__Metadata/metadata_wrapped.cml +++ b/lib/iris/tests/results/unit/cube/Cube/intersection__Metadata/metadata_wrapped.cml @@ -65,6 +65,32 @@ + + + + + + + + + + diff --git a/lib/iris/tests/unit/cube/test_Cube.py b/lib/iris/tests/unit/cube/test_Cube.py index 878a793448..8c36240fb6 100644 --- a/lib/iris/tests/unit/cube/test_Cube.py +++ b/lib/iris/tests/unit/cube/test_Cube.py @@ -1207,6 +1207,22 @@ def create_cube(lon_min, lon_max, bounds=False): ), 2, ) + cube.add_cell_measure( + iris.coords.CellMeasure( + np.arange(0, n_lons * 3).reshape(3, -1), + "cell_area", + units="m2", + ), + data_dims=[1, 2], + ) + cube.add_ancillary_variable( + iris.coords.AncillaryVariable( + np.arange(0, n_lons * 3).reshape(3, -1), + "land_area_fraction", + units="%", + ), + data_dims=[1, 2], + ) if bounds: cube.coord("longitude").guess_bounds() cube.add_aux_coord( From 9a23aa0f835ea6b2a7496bf86d51a2042dbc6773 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 27 Aug 2024 12:09:37 +0100 Subject: [PATCH 19/26] Bump scitools/workflows from 2024.08.1 to 2024.08.3 (#6131) Bumps [scitools/workflows](https://github.com/scitools/workflows) from 2024.08.1 to 2024.08.3. - [Release notes](https://github.com/scitools/workflows/releases) - [Commits](https://github.com/scitools/workflows/compare/2024.08.1...2024.08.3) --- updated-dependencies: - dependency-name: scitools/workflows dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci-manifest.yml | 2 +- .github/workflows/refresh-lockfiles.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci-manifest.yml b/.github/workflows/ci-manifest.yml index 73977f7d4c..527356ff35 100644 --- a/.github/workflows/ci-manifest.yml +++ b/.github/workflows/ci-manifest.yml @@ -23,4 +23,4 @@ concurrency: jobs: manifest: name: "check-manifest" - uses: scitools/workflows/.github/workflows/ci-manifest.yml@2024.08.1 + uses: scitools/workflows/.github/workflows/ci-manifest.yml@2024.08.3 diff --git a/.github/workflows/refresh-lockfiles.yml b/.github/workflows/refresh-lockfiles.yml index dd4d8bdc30..f3755f7709 100644 --- a/.github/workflows/refresh-lockfiles.yml +++ b/.github/workflows/refresh-lockfiles.yml @@ -14,5 +14,5 @@ on: jobs: refresh_lockfiles: - uses: scitools/workflows/.github/workflows/refresh-lockfiles.yml@2024.08.1 + uses: scitools/workflows/.github/workflows/refresh-lockfiles.yml@2024.08.3 secrets: inherit From 1bfe3113cb6c1460886ce07dfe495c32782f1aa8 Mon Sep 17 00:00:00 2001 From: Bouwe Andela Date: Fri, 30 Aug 2024 10:51:33 +0200 Subject: [PATCH 20/26] Faster time coordinate categorization (#5999) * Faster coordinate categorization * Fix type hint * Add whatsnew entry * Update whatsnew Co-authored-by: Martin Yeo <40734014+trexfeathers@users.noreply.github.com> * Use type hint to determine if the coordinate needs to be translated to cftime.datetime objects * Shorter docstring * Small improvements * Remove unused argument * Use the right name for month_abbr function Co-authored-by: Martin Yeo <40734014+trexfeathers@users.noreply.github.com> * Advertise new feature of add_categorised_coord --------- Co-authored-by: Martin Yeo <40734014+trexfeathers@users.noreply.github.com> --- docs/src/conf.py | 1 + docs/src/whatsnew/latest.rst | 7 +- lib/iris/coord_categorisation.py | 177 +++++++++++++++---------------- 3 files changed, 93 insertions(+), 92 deletions(-) diff --git a/docs/src/conf.py b/docs/src/conf.py index 60f760c37f..4c8f59564f 100644 --- a/docs/src/conf.py +++ b/docs/src/conf.py @@ -246,6 +246,7 @@ def _dotv(version): # See https://www.sphinx-doc.org/en/master/usage/extensions/intersphinx.html intersphinx_mapping = { "cartopy": ("https://scitools.org.uk/cartopy/docs/latest/", None), + "cftime": ("https://unidata.github.io/cftime/", None), "dask": ("https://docs.dask.org/en/stable/", None), "iris-esmf-regrid": ("https://iris-esmf-regrid.readthedocs.io/en/stable/", None), "matplotlib": ("https://matplotlib.org/stable/", None), diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst index a174fd2cfb..efe9fc621c 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/latest.rst @@ -48,8 +48,11 @@ This document explains the changes made to Iris for this release 🚀 Performance Enhancements =========================== -#. N/A - +#. `@bouweandela`_ made the time coordinate categorisation functions in + :mod:`~iris.coord_categorisation` faster. Anyone using + :func:`~iris.coord_categorisation.add_categorised_coord` + with cftime :class:`~cftime.datetime` objects can benefit from the same + improvement by adding a type hint to their category funcion. (:pull:`5999`) 🔥 Deprecations =============== diff --git a/lib/iris/coord_categorisation.py b/lib/iris/coord_categorisation.py index 7ccee4fca8..770f8327a1 100644 --- a/lib/iris/coord_categorisation.py +++ b/lib/iris/coord_categorisation.py @@ -17,13 +17,23 @@ import calendar import collections +import inspect +from typing import Callable +import cftime import numpy as np import iris.coords +import iris.cube -def add_categorised_coord(cube, name, from_coord, category_function, units="1"): +def add_categorised_coord( + cube: iris.cube.Cube, + name: str, + from_coord: iris.coords.Coord | str, + category_function: Callable, + units: str = "1", +) -> None: """Add a new coordinate to a cube, by categorising an existing one. Make a new :class:`iris.coords.AuxCoord` from mapped values, and add @@ -32,31 +42,38 @@ def add_categorised_coord(cube, name, from_coord, category_function, units="1"): Parameters ---------- cube : :class:`iris.cube.Cube` - The cube containing 'from_coord'. The new coord will be added into it. + The cube containing 'from_coord'. The new coord will be added into it. name : str Name of the created coordinate. from_coord : :class:`iris.coords.Coord` or str Coordinate in 'cube', or the name of one. category_function : callable - Function(coordinate, value), returning a category value for a - coordinate point-value. + Function(coordinate, value), returning a category value for a coordinate + point-value. If ``value`` has a type hint :obj:`cftime.datetime`, the + coordinate points are translated to :obj:`cftime.datetime` s before + calling ``category_function``. units : str, default="1" Units of the category value, typically 'no_unit' or '1'. - """ # Interpret coord, if given as a name - if isinstance(from_coord, str): - from_coord = cube.coord(from_coord) + coord = cube.coord(from_coord) if isinstance(from_coord, str) else from_coord if len(cube.coords(name)) > 0: msg = 'A coordinate "%s" already exists in the cube.' % name raise ValueError(msg) + # Translate the coordinate points to cftime datetimes if requested. + value_param = list(inspect.signature(category_function).parameters.values())[1] + if issubclass(value_param.annotation, cftime.datetime): + points = coord.units.num2date(coord.points, only_use_cftime_datetimes=True) + else: + points = coord.points + # Construct new coordinate by mapping values, using numpy.vectorize to # support multi-dimensional coords. # Test whether the result contains strings. If it does we must manually # force the dtype because of a numpy bug (see numpy #3270 on GitHub). - result = category_function(from_coord, from_coord.points.ravel()[0]) + result = category_function(coord, points.ravel()[0]) if isinstance(result, str): str_vectorised_fn = np.vectorize(category_function, otypes=[object]) @@ -67,14 +84,14 @@ def vectorised_fn(*args): else: vectorised_fn = np.vectorize(category_function) new_coord = iris.coords.AuxCoord( - vectorised_fn(from_coord, from_coord.points), + vectorised_fn(coord, points), units=units, - attributes=from_coord.attributes.copy(), + attributes=coord.attributes.copy(), ) new_coord.rename(name) # Add into the cube - cube.add_aux_coord(new_coord, cube.coord_dims(from_coord)) + cube.add_aux_coord(new_coord, cube.coord_dims(coord)) # ====================================== @@ -84,78 +101,62 @@ def vectorised_fn(*args): # coordinates only # - -# Private "helper" function -def _pt_date(coord, time): - """Return the datetime of a time-coordinate point. - - Parameters - ---------- - coord : Coord - Coordinate (must be Time-type). - time : float - Value of a coordinate point. - - Returns - ------- - cftime.datetime - - """ - # NOTE: All of the currently defined categorisation functions are - # calendar operations on Time coordinates. - return coord.units.num2date(time, only_use_cftime_datetimes=True) - - # -------------------------------------------- # Time categorisations : calendar date components def add_year(cube, coord, name="year"): """Add a categorical calendar-year coordinate.""" - add_categorised_coord(cube, name, coord, lambda coord, x: _pt_date(coord, x).year) + + def get_year(_, value: cftime.datetime) -> int: + return value.year + + add_categorised_coord(cube, name, coord, get_year) def add_month_number(cube, coord, name="month_number"): """Add a categorical month coordinate, values 1..12.""" - add_categorised_coord(cube, name, coord, lambda coord, x: _pt_date(coord, x).month) + + def get_month_number(_, value: cftime.datetime) -> int: + return value.month + + add_categorised_coord(cube, name, coord, get_month_number) def add_month_fullname(cube, coord, name="month_fullname"): """Add a categorical month coordinate, values 'January'..'December'.""" - add_categorised_coord( - cube, - name, - coord, - lambda coord, x: calendar.month_name[_pt_date(coord, x).month], - units="no_unit", - ) + + def get_month_fullname(_, value: cftime.datetime) -> str: + return calendar.month_name[value.month] + + add_categorised_coord(cube, name, coord, get_month_fullname, units="no_unit") def add_month(cube, coord, name="month"): """Add a categorical month coordinate, values 'Jan'..'Dec'.""" - add_categorised_coord( - cube, - name, - coord, - lambda coord, x: calendar.month_abbr[_pt_date(coord, x).month], - units="no_unit", - ) + + def get_month_abbr(_, value: cftime.datetime) -> str: + return calendar.month_abbr[value.month] + + add_categorised_coord(cube, name, coord, get_month_abbr, units="no_unit") def add_day_of_month(cube, coord, name="day_of_month"): """Add a categorical day-of-month coordinate, values 1..31.""" - add_categorised_coord(cube, name, coord, lambda coord, x: _pt_date(coord, x).day) + + def get_day_of_month(_, value: cftime.datetime) -> int: + return value.day + + add_categorised_coord(cube, name, coord, get_day_of_month) def add_day_of_year(cube, coord, name="day_of_year"): """Add a categorical day-of-year coordinate, values 1..365 (1..366 in leap years).""" - # Note: cftime.datetime objects return a normal tuple from timetuple(), - # unlike datetime.datetime objects that return a namedtuple. - # Index the time tuple (element 7 is day of year) instead of using named - # element tm_yday. - add_categorised_coord( - cube, name, coord, lambda coord, x: _pt_date(coord, x).timetuple()[7] - ) + + def get_day_of_year(_, value: cftime.datetime) -> int: + return value.timetuple().tm_yday + + add_categorised_coord(cube, name, coord, get_day_of_year) # -------------------------------------------- @@ -164,31 +165,29 @@ def add_day_of_year(cube, coord, name="day_of_year"): def add_weekday_number(cube, coord, name="weekday_number"): """Add a categorical weekday coordinate, values 0..6 [0=Monday].""" - add_categorised_coord( - cube, name, coord, lambda coord, x: _pt_date(coord, x).dayofwk - ) + + def get_weekday_number(_, value: cftime.datetime) -> int: + return value.dayofwk + + add_categorised_coord(cube, name, coord, get_weekday_number) def add_weekday_fullname(cube, coord, name="weekday_fullname"): """Add a categorical weekday coordinate, values 'Monday'..'Sunday'.""" - add_categorised_coord( - cube, - name, - coord, - lambda coord, x: calendar.day_name[_pt_date(coord, x).dayofwk], - units="no_unit", - ) + + def get_weekday_fullname(_, value: cftime.datetime) -> str: + return calendar.day_name[value.dayofwk] + + add_categorised_coord(cube, name, coord, get_weekday_fullname, units="no_unit") def add_weekday(cube, coord, name="weekday"): """Add a categorical weekday coordinate, values 'Mon'..'Sun'.""" - add_categorised_coord( - cube, - name, - coord, - lambda coord, x: calendar.day_abbr[_pt_date(coord, x).dayofwk], - units="no_unit", - ) + + def get_weekday(_, value: cftime.datetime) -> str: + return calendar.day_abbr[value.dayofwk] + + add_categorised_coord(cube, name, coord, get_weekday, units="no_unit") # -------------------------------------------- @@ -197,7 +196,11 @@ def add_weekday(cube, coord, name="weekday"): def add_hour(cube, coord, name="hour"): """Add a categorical hour coordinate, values 0..23.""" - add_categorised_coord(cube, name, coord, lambda coord, x: _pt_date(coord, x).hour) + + def get_hour(_, value: cftime.datetime) -> int: + return value.hour + + add_categorised_coord(cube, name, coord, get_hour) # ---------------------------------------------- @@ -319,9 +322,8 @@ def add_season(cube, coord, name="season", seasons=("djf", "mam", "jja", "son")) month_season_numbers = _month_season_numbers(seasons) # Define a categorisation function. - def _season(coord, value): - dt = _pt_date(coord, value) - return seasons[month_season_numbers[dt.month]] + def _season(_, value: cftime.datetime) -> str: + return seasons[month_season_numbers[value.month]] # Apply the categorisation. add_categorised_coord(cube, name, coord, _season, units="no_unit") @@ -357,9 +359,8 @@ def add_season_number( month_season_numbers = _month_season_numbers(seasons) # Define a categorisation function. - def _season_number(coord, value): - dt = _pt_date(coord, value) - return month_season_numbers[dt.month] + def _season_number(_, value: cftime.datetime) -> int: + return month_season_numbers[value.month] # Apply the categorisation. add_categorised_coord(cube, name, coord, _season_number) @@ -401,10 +402,9 @@ def add_season_year( ) # Define a categorisation function. - def _season_year(coord, value): - dt = _pt_date(coord, value) - year = dt.year - year += month_year_adjusts[dt.month] + def _season_year(_, value: cftime.datetime) -> int: + year = value.year + year += month_year_adjusts[value.month] return year # Apply the categorisation. @@ -432,10 +432,7 @@ def add_season_membership(cube, coord, season, name="season_membership"): """ months = _months_in_season(season) - def _season_membership(coord, value): - dt = _pt_date(coord, value) - if dt.month in months: - return True - return False + def _season_membership(_, value: cftime.datetime) -> bool: + return value.month in months add_categorised_coord(cube, name, coord, _season_membership) From 5cc6737c0d1e5a3d9b0c258874cfbf959dc4205e Mon Sep 17 00:00:00 2001 From: Martin Yeo <40734014+trexfeathers@users.noreply.github.com> Date: Fri, 30 Aug 2024 14:10:26 +0100 Subject: [PATCH 21/26] Repeating Tracemalloc Benchmark for accuracy (#5981) * PoC custom benchmark. * Working tracemalloc subclass. * Docstrings and comments. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Finalised code. * Make custom benchmarks installable. * Experiment. * Re-write following testing. * Replace pyproject with setup.py. * Testing. * Make testing smaller. * Better testing. * Better testing 2. * Better testing 3. * Better testing 4. * Better testing 5. * Remove testing. * Remove tracemalloc decorator. * Docs tidy-up. * Restructure and use custom install script. * Temporary quick demo. * Revert "Temporary quick demo." This reverts commit 9dffa1bec548d8a73379ca62ab5b76f2e4fb3c2e. --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- benchmarks/README.md | 6 + benchmarks/asv.conf.json | 7 +- benchmarks/benchmarks/__init__.py | 105 ---------- benchmarks/benchmarks/cperf/save.py | 5 +- benchmarks/benchmarks/merge_concat.py | 11 +- .../benchmarks/mesh/utils/regions_combine.py | 13 +- benchmarks/benchmarks/regridding.py | 17 +- benchmarks/benchmarks/save.py | 4 +- .../benchmarks/sperf/combine_regions.py | 14 +- benchmarks/benchmarks/sperf/save.py | 5 +- benchmarks/benchmarks/stats.py | 12 +- benchmarks/benchmarks/trajectory.py | 12 +- benchmarks/custom_bms/README.md | 11 + benchmarks/custom_bms/install.py | 55 +++++ benchmarks/custom_bms/tracemallocbench.py | 196 ++++++++++++++++++ docs/src/whatsnew/latest.rst | 4 +- 16 files changed, 317 insertions(+), 160 deletions(-) create mode 100644 benchmarks/custom_bms/README.md create mode 100644 benchmarks/custom_bms/install.py create mode 100644 benchmarks/custom_bms/tracemallocbench.py diff --git a/benchmarks/README.md b/benchmarks/README.md index 49168e7281..911d5f7833 100644 --- a/benchmarks/README.md +++ b/benchmarks/README.md @@ -118,6 +118,12 @@ repeats _between_ `setup()` calls using the `repeat` attribute. `warmup_time = 0` is also advisable since ASV performs independent re-runs to estimate run-time, and these will still be subject to the original problem. +### Custom benchmarks + +Iris benchmarking implements custom benchmark types, such as a `tracemalloc` +benchmark to measure memory growth. See [custom_bms/](./custom_bms) for more +detail. + ### Scaling / non-Scaling Performance Differences **(We no longer advocate the below for benchmarks run during CI, given the diff --git a/benchmarks/asv.conf.json b/benchmarks/asv.conf.json index 13e7256b83..2857c90ad7 100644 --- a/benchmarks/asv.conf.json +++ b/benchmarks/asv.conf.json @@ -53,9 +53,12 @@ "command_comment": [ "We know that the Nox command takes care of installation in each", "environment, and in the case of Iris no specialised uninstall or", - "build commands are needed to get it working." + "build commands are needed to get it working.", + + "We do however need to install the custom benchmarks for them to be", + "usable." ], "install_command": [], "uninstall_command": [], - "build_command": [] + "build_command": ["python {conf_dir}/custom_bms/install.py"] } diff --git a/benchmarks/benchmarks/__init__.py b/benchmarks/benchmarks/__init__.py index 378c26332d..30a991a879 100644 --- a/benchmarks/benchmarks/__init__.py +++ b/benchmarks/benchmarks/__init__.py @@ -37,111 +37,6 @@ def disable_repeat_between_setup(benchmark_object): return benchmark_object -class TrackAddedMemoryAllocation: - """Measures by how much process resident memory grew, during execution. - - Context manager which measures by how much process resident memory grew, - during execution of its enclosed code block. - - Obviously limited as to what it actually measures : Relies on the current - process not having significant unused (de-allocated) memory when the - tested codeblock runs, and only reliable when the code allocates a - significant amount of new memory. - - Example: - with TrackAddedMemoryAllocation() as mb: - initial_call() - other_call() - result = mb.addedmem_mb() - - Attributes - ---------- - RESULT_MINIMUM_MB : float - The smallest result that should ever be returned, in Mb. Results - fluctuate from run to run (usually within 1Mb) so if a result is - sufficiently small this noise will produce a before-after ratio over - AVD's detection threshold and be treated as 'signal'. Results - smaller than this value will therefore be returned as equal to this - value, ensuring fractionally small noise / no noise at all. - Defaults to 1.0 - - RESULT_ROUND_DP : int - Number of decimal places of rounding on result values (in Mb). - Defaults to 1 - - """ - - RESULT_MINIMUM_MB = 0.2 - RESULT_ROUND_DP = 1 # I.E. to nearest 0.1 Mb - - def __enter__(self): - tracemalloc.start() - return self - - def __exit__(self, *_): - _, peak_mem_bytes = tracemalloc.get_traced_memory() - tracemalloc.stop() - # Save peak-memory allocation, scaled from bytes to Mb. - self._peak_mb = peak_mem_bytes * (2.0**-20) - - def addedmem_mb(self): - """Return measured memory growth, in Mb.""" - result = self._peak_mb - # Small results are too vulnerable to noise being interpreted as signal. - result = max(self.RESULT_MINIMUM_MB, result) - # Rounding makes results easier to read. - result = np.round(result, self.RESULT_ROUND_DP) - return result - - @staticmethod - def decorator(decorated_func): - """Benchmark to track growth in resident memory during execution. - - Intended for use on ASV ``track_`` benchmarks. Applies the - :class:`TrackAddedMemoryAllocation` context manager to the benchmark - code, sets the benchmark ``unit`` attribute to ``Mb``. - - """ - - def _wrapper(*args, **kwargs): - assert decorated_func.__name__[:6] == "track_" - # Run the decorated benchmark within the added memory context - # manager. - with TrackAddedMemoryAllocation() as mb: - decorated_func(*args, **kwargs) - return mb.addedmem_mb() - - decorated_func.unit = "Mb" - return _wrapper - - @staticmethod - def decorator_repeating(repeats=3): - """Benchmark to track growth in resident memory during execution. - - Tracks memory for repeated calls of decorated function. - - Intended for use on ASV ``track_`` benchmarks. Applies the - :class:`TrackAddedMemoryAllocation` context manager to the benchmark - code, sets the benchmark ``unit`` attribute to ``Mb``. - - """ - - def decorator(decorated_func): - def _wrapper(*args, **kwargs): - assert decorated_func.__name__[:6] == "track_" - # Run the decorated benchmark within the added memory context - # manager. - with TrackAddedMemoryAllocation() as mb: - for _ in range(repeats): - decorated_func(*args, **kwargs) - return mb.addedmem_mb() - - decorated_func.unit = "Mb" - return _wrapper - - return decorator - - def on_demand_benchmark(benchmark_object): """Disable these benchmark(s) unless ON_DEMAND_BENCHARKS env var is set. diff --git a/benchmarks/benchmarks/cperf/save.py b/benchmarks/benchmarks/cperf/save.py index 2d60f920c4..6dcd0b3bcf 100644 --- a/benchmarks/benchmarks/cperf/save.py +++ b/benchmarks/benchmarks/cperf/save.py @@ -6,7 +6,7 @@ from iris import save -from .. import TrackAddedMemoryAllocation, on_demand_benchmark +from .. import on_demand_benchmark from ..generate_data.ugrid import make_cube_like_2d_cubesphere, make_cube_like_umfield from . import _N_CUBESPHERE_UM_EQUIVALENT, _UM_DIMS_YX @@ -36,6 +36,5 @@ def _save_data(self, cube): def time_save_data_netcdf(self, data_type): self._save_data(self.cube) - @TrackAddedMemoryAllocation.decorator - def track_addedmem_save_data_netcdf(self, data_type): + def tracemalloc_save_data_netcdf(self, data_type): self._save_data(self.cube) diff --git a/benchmarks/benchmarks/merge_concat.py b/benchmarks/benchmarks/merge_concat.py index 1a18f92ce9..0bb4096e6c 100644 --- a/benchmarks/benchmarks/merge_concat.py +++ b/benchmarks/benchmarks/merge_concat.py @@ -8,7 +8,6 @@ from iris.cube import CubeList -from . import TrackAddedMemoryAllocation from .generate_data.stock import realistic_4d_w_everything @@ -34,10 +33,11 @@ def setup(self): def time_merge(self): _ = self.cube_list.merge_cube() - @TrackAddedMemoryAllocation.decorator_repeating() - def track_mem_merge(self): + def tracemalloc_merge(self): _ = self.cube_list.merge_cube() + tracemalloc_merge.number = 3 # type: ignore[attr-defined] + class Concatenate: # TODO: Improve coverage. @@ -56,6 +56,7 @@ def setup(self): def time_concatenate(self): _ = self.cube_list.concatenate_cube() - @TrackAddedMemoryAllocation.decorator_repeating() - def track_mem_merge(self): + def tracemalloc_concatenate(self): _ = self.cube_list.concatenate_cube() + + tracemalloc_concatenate.number = 3 # type: ignore[attr-defined] diff --git a/benchmarks/benchmarks/mesh/utils/regions_combine.py b/benchmarks/benchmarks/mesh/utils/regions_combine.py index 1a1a43a622..a61deea56d 100644 --- a/benchmarks/benchmarks/mesh/utils/regions_combine.py +++ b/benchmarks/benchmarks/mesh/utils/regions_combine.py @@ -17,7 +17,6 @@ from iris import load, load_cube, save from iris.mesh.utils import recombine_submeshes -from ... import TrackAddedMemoryAllocation from ...generate_data.ugrid import make_cube_like_2d_cubesphere @@ -169,8 +168,7 @@ def setup(self, n_cubesphere): def time_create_combined_cube(self, n_cubesphere): self.recombine() - @TrackAddedMemoryAllocation.decorator - def track_addedmem_create_combined_cube(self, n_cubesphere): + def tracemalloc_create_combined_cube(self, n_cubesphere): self.recombine() @@ -180,8 +178,7 @@ class CombineRegionsComputeRealData(MixinCombineRegions): def time_compute_data(self, n_cubesphere): _ = self.recombined_cube.data - @TrackAddedMemoryAllocation.decorator - def track_addedmem_compute_data(self, n_cubesphere): + def tracemalloc_compute_data(self, n_cubesphere): _ = self.recombined_cube.data @@ -199,8 +196,7 @@ def time_save(self, n_cubesphere): # Save to disk, which must compute data + stream it to file. save(self.recombined_cube, "tmp.nc") - @TrackAddedMemoryAllocation.decorator - def track_addedmem_save(self, n_cubesphere): + def tracemalloc_save(self, n_cubesphere): save(self.recombined_cube, "tmp.nc") def track_filesize_saved(self, n_cubesphere): @@ -227,6 +223,5 @@ def time_stream_file2file(self, n_cubesphere): # Save to disk, which must compute data + stream it to file. save(self.recombined_cube, "tmp.nc") - @TrackAddedMemoryAllocation.decorator - def track_addedmem_stream_file2file(self, n_cubesphere): + def tracemalloc_stream_file2file(self, n_cubesphere): save(self.recombined_cube, "tmp.nc") diff --git a/benchmarks/benchmarks/regridding.py b/benchmarks/benchmarks/regridding.py index 4cfda05ad1..e227da0ec6 100644 --- a/benchmarks/benchmarks/regridding.py +++ b/benchmarks/benchmarks/regridding.py @@ -14,8 +14,6 @@ from iris.analysis import AreaWeighted, PointInCell from iris.coords import AuxCoord -from . import TrackAddedMemoryAllocation - class HorizontalChunkedRegridding: def setup(self) -> None: @@ -53,20 +51,22 @@ def time_regrid_area_w_new_grid(self) -> None: # Realise data out.data - @TrackAddedMemoryAllocation.decorator_repeating() - def track_mem_regrid_area_w(self) -> None: + def tracemalloc_regrid_area_w(self) -> None: # Regrid the chunked cube out = self.cube.regrid(self.template_cube, self.scheme_area_w) # Realise data out.data - @TrackAddedMemoryAllocation.decorator_repeating() - def track_mem_regrid_area_w_new_grid(self) -> None: + tracemalloc_regrid_area_w.number = 3 # type: ignore[attr-defined] + + def tracemalloc_regrid_area_w_new_grid(self) -> None: # Regrid the chunked cube out = self.chunked_cube.regrid(self.template_cube, self.scheme_area_w) # Realise data out.data + tracemalloc_regrid_area_w_new_grid.number = 3 # type: ignore[attr-defined] + class CurvilinearRegridding: def setup(self) -> None: @@ -110,9 +110,10 @@ def time_regrid_pic(self) -> None: # Realise the data out.data - @TrackAddedMemoryAllocation.decorator_repeating() - def track_mem_regrid_pic(self) -> None: + def tracemalloc_regrid_pic(self) -> None: # Regrid the cube onto the template. out = self.cube.regrid(self.template_cube, self.scheme_pic) # Realise the data out.data + + tracemalloc_regrid_pic.number = 3 # type: ignore[attr-defined] diff --git a/benchmarks/benchmarks/save.py b/benchmarks/benchmarks/save.py index aaa8480d64..4bac1b1450 100644 --- a/benchmarks/benchmarks/save.py +++ b/benchmarks/benchmarks/save.py @@ -7,7 +7,6 @@ from iris import save from iris.mesh import save_mesh -from . import TrackAddedMemoryAllocation, on_demand_benchmark from .generate_data.ugrid import make_cube_like_2d_cubesphere @@ -38,8 +37,7 @@ def time_netcdf_save_mesh(self, n_cubesphere, is_unstructured): if is_unstructured: self._save_mesh(self.cube) - @TrackAddedMemoryAllocation.decorator - def track_addedmem_netcdf_save(self, n_cubesphere, is_unstructured): + def tracemalloc_netcdf_save(self, n_cubesphere, is_unstructured): # Don't need to copy the cube here since track_ benchmarks don't # do repeats between self.setup() calls. self._save_data(self.cube, do_copy=False) diff --git a/benchmarks/benchmarks/sperf/combine_regions.py b/benchmarks/benchmarks/sperf/combine_regions.py index b106befcae..591b7bb9be 100644 --- a/benchmarks/benchmarks/sperf/combine_regions.py +++ b/benchmarks/benchmarks/sperf/combine_regions.py @@ -12,7 +12,7 @@ from iris import load, load_cube, save from iris.mesh.utils import recombine_submeshes -from .. import TrackAddedMemoryAllocation, on_demand_benchmark +from .. import on_demand_benchmark from ..generate_data.ugrid import BENCHMARK_DATA, make_cube_like_2d_cubesphere @@ -175,8 +175,7 @@ def setup(self, n_cubesphere, imaginary_data=True, create_result_cube=False): def time_create_combined_cube(self, n_cubesphere): self.recombine() - @TrackAddedMemoryAllocation.decorator - def track_addedmem_create_combined_cube(self, n_cubesphere): + def tracemalloc_create_combined_cube(self, n_cubesphere): self.recombine() @@ -187,8 +186,7 @@ class ComputeRealData(Mixin): def time_compute_data(self, n_cubesphere): _ = self.recombined_cube.data - @TrackAddedMemoryAllocation.decorator - def track_addedmem_compute_data(self, n_cubesphere): + def tracemalloc_compute_data(self, n_cubesphere): _ = self.recombined_cube.data @@ -206,8 +204,7 @@ def time_save(self, n_cubesphere): # Save to disk, which must compute data + stream it to file. self.save_recombined_cube() - @TrackAddedMemoryAllocation.decorator - def track_addedmem_save(self, n_cubesphere): + def tracemalloc_save(self, n_cubesphere): self.save_recombined_cube() def track_filesize_saved(self, n_cubesphere): @@ -233,6 +230,5 @@ def time_stream_file2file(self, n_cubesphere): # Save to disk, which must compute data + stream it to file. self.save_recombined_cube() - @TrackAddedMemoryAllocation.decorator - def track_addedmem_stream_file2file(self, n_cubesphere): + def tracemalloc_stream_file2file(self, n_cubesphere): self.save_recombined_cube() diff --git a/benchmarks/benchmarks/sperf/save.py b/benchmarks/benchmarks/sperf/save.py index d8a03798f0..a715ec2424 100644 --- a/benchmarks/benchmarks/sperf/save.py +++ b/benchmarks/benchmarks/sperf/save.py @@ -9,7 +9,7 @@ from iris import save from iris.mesh import save_mesh -from .. import TrackAddedMemoryAllocation, on_demand_benchmark +from .. import on_demand_benchmark from ..generate_data.ugrid import make_cube_like_2d_cubesphere @@ -36,8 +36,7 @@ def _save_mesh(self, cube): def time_save_cube(self, n_cubesphere, is_unstructured): self._save_cube(self.cube) - @TrackAddedMemoryAllocation.decorator - def track_addedmem_save_cube(self, n_cubesphere, is_unstructured): + def tracemalloc_save_cube(self, n_cubesphere, is_unstructured): self._save_cube(self.cube) def time_save_mesh(self, n_cubesphere, is_unstructured): diff --git a/benchmarks/benchmarks/stats.py b/benchmarks/benchmarks/stats.py index 1f5262bf4c..fbab12cd4b 100644 --- a/benchmarks/benchmarks/stats.py +++ b/benchmarks/benchmarks/stats.py @@ -8,8 +8,6 @@ from iris.analysis.stats import pearsonr import iris.tests -from . import TrackAddedMemoryAllocation - class PearsonR: def setup(self): @@ -32,10 +30,11 @@ def setup(self): def time_real(self): pearsonr(self.cube_a, self.cube_b, weights=self.weights) - @TrackAddedMemoryAllocation.decorator_repeating() - def track_real(self): + def tracemalloc_real(self): pearsonr(self.cube_a, self.cube_b, weights=self.weights) + tracemalloc_real.number = 3 # type: ignore[attr-defined] + def time_lazy(self): for cube in self.cube_a, self.cube_b: cube.data = cube.lazy_data() @@ -43,10 +42,11 @@ def time_lazy(self): result = pearsonr(self.cube_a, self.cube_b, weights=self.weights) result.data - @TrackAddedMemoryAllocation.decorator_repeating() - def track_lazy(self): + def tracemalloc_lazy(self): for cube in self.cube_a, self.cube_b: cube.data = cube.lazy_data() result = pearsonr(self.cube_a, self.cube_b, weights=self.weights) result.data + + tracemalloc_lazy.number = 3 # type: ignore[attr-defined] diff --git a/benchmarks/benchmarks/trajectory.py b/benchmarks/benchmarks/trajectory.py index a31552eb9a..77825ef2f2 100644 --- a/benchmarks/benchmarks/trajectory.py +++ b/benchmarks/benchmarks/trajectory.py @@ -13,8 +13,6 @@ import iris from iris.analysis.trajectory import interpolate -from . import TrackAddedMemoryAllocation - class TrajectoryInterpolation: def setup(self) -> None: @@ -35,22 +33,24 @@ def time_trajectory_linear(self) -> None: # Realise the data out_cube.data - @TrackAddedMemoryAllocation.decorator_repeating() - def track_trajectory_linear(self) -> None: + def tracemalloc_trajectory_linear(self) -> None: # Regrid the cube onto the template. out_cube = interpolate(self.cube, self.sample_points, method="linear") # Realise the data out_cube.data + tracemalloc_trajectory_linear.number = 3 # type: ignore[attr-defined] + def time_trajectory_nearest(self) -> None: # Regrid the cube onto the template. out_cube = interpolate(self.cube, self.sample_points, method="nearest") # Realise the data out_cube.data - @TrackAddedMemoryAllocation.decorator_repeating() - def track_trajectory_nearest(self) -> None: + def tracemalloc_trajectory_nearest(self) -> None: # Regrid the cube onto the template. out_cube = interpolate(self.cube, self.sample_points, method="nearest") # Realise the data out_cube.data + + tracemalloc_trajectory_nearest.number = 3 # type: ignore[attr-defined] diff --git a/benchmarks/custom_bms/README.md b/benchmarks/custom_bms/README.md new file mode 100644 index 0000000000..eea85d74fe --- /dev/null +++ b/benchmarks/custom_bms/README.md @@ -0,0 +1,11 @@ +# Iris custom benchmarks + +To be recognised by ASV, these benchmarks must be packaged and installed in +line with the +[ASV guidelines](https://asv.readthedocs.io/projects/asv-runner/en/latest/development/benchmark_plugins.html). +This is achieved using the custom build in [install.py](./install.py). + +Installation is into the environment where the benchmarks are run (i.e. not +the environment containing ASV + Nox, but the one built to the same +specifications as the Tests environment). This is done via `build_command` +in [asv.conf.json](../asv.conf.json). diff --git a/benchmarks/custom_bms/install.py b/benchmarks/custom_bms/install.py new file mode 100644 index 0000000000..59d27a0b43 --- /dev/null +++ b/benchmarks/custom_bms/install.py @@ -0,0 +1,55 @@ +# Copyright Iris contributors +# +# This file is part of Iris and is released under the BSD license. +# See LICENSE in the root of the repository for full licensing details. +"""Install Iris' custom benchmarks for detection by ASV. + +See the requirements for being detected as an ASV plugin: +https://asv.readthedocs.io/projects/asv-runner/en/latest/development/benchmark_plugins.html +""" + +from pathlib import Path +import shutil +from subprocess import run +from tempfile import TemporaryDirectory + +this_dir = Path(__file__).parent + + +def package_files(new_dir: Path) -> None: + """Package Iris' custom benchmarks for detection by ASV. + + Parameters + ---------- + new_dir : Path + The directory to package the custom benchmarks in. + """ + asv_bench_iris = new_dir / "asv_bench_iris" + benchmarks = asv_bench_iris / "benchmarks" + benchmarks.mkdir(parents=True) + (asv_bench_iris / "__init__.py").touch() + + for py_file in this_dir.glob("*.py"): + if py_file != Path(__file__): + shutil.copy2(py_file, benchmarks) + + # Create this on the fly, as having multiple pyproject.toml files in 1 + # project causes problems. + py_project = new_dir / "pyproject.toml" + py_project.write_text( + """ + [project] + name = "asv_bench_iris" + version = "0.1" + """ + ) + + +def main(): + with TemporaryDirectory() as temp_dir: + package_files(Path(temp_dir)) + run(["python", "-m", "pip", "install", temp_dir]) + + +if __name__ == "__main__": + main() diff --git a/benchmarks/custom_bms/tracemallocbench.py b/benchmarks/custom_bms/tracemallocbench.py new file mode 100644 index 0000000000..486c67aeb9 --- /dev/null +++ b/benchmarks/custom_bms/tracemallocbench.py @@ -0,0 +1,196 @@ +# Copyright Iris contributors +# +# This file is part of Iris and is released under the BSD license. +# See LICENSE in the root of the repository for full licensing details. + +"""Benchmark for growth in process resident memory, repeating for accuracy. + +Uses a modified version of the repeat logic in +:class:`asv_runner.benchmarks.time.TimeBenchmark`. +""" + +import re +from timeit import Timer +import tracemalloc +from typing import Callable + +from asv_runner.benchmarks.time import TimeBenchmark, wall_timer + + +class TracemallocBenchmark(TimeBenchmark): + """Benchmark for growth in process resident memory, repeating for accuracy. + + Obviously limited as to what it actually measures : Relies on the current + process not having significant unused (de-allocated) memory when the + tested codeblock runs, and only reliable when the code allocates a + significant amount of new memory. + + Benchmark operations prefixed with ``tracemalloc_`` or ``Tracemalloc`` will + use this benchmark class. + + Inherits behaviour from :class:`asv_runner.benchmarks.time.TimeBenchmark`, + with modifications for memory measurement. See the below Attributes section + and https://asv.readthedocs.io/en/stable/writing_benchmarks.html#timing-benchmarks. + + Attributes + ---------- + Mostly identical to :class:`asv_runner.benchmarks.time.TimeBenchmark`. See + https://asv.readthedocs.io/en/stable/benchmarks.html#timing-benchmarks + Make sure to use the inherited ``repeat`` attribute if greater accuracy + is needed. Below are the attributes where inherited behaviour is + overridden. + + number : int + The number of times the benchmarked operation will be called per + ``repeat``. Memory growth is measured after ALL calls - + i.e. `number` should make no difference to the result if the operation + has perfect garbage collection. The parent class's intelligent + modification of `number` is NOT inherited. A minimum value of ``1`` is + enforced. + warmup_time, sample_time, min_run_count, timer + Not used. + type : str = "tracemalloc" + The name of this benchmark type. + unit : str = "bytes" + The units of the measured metric (i.e. the growth in memory). + + """ + + name_regex = re.compile("^(Tracemalloc[A-Z_].+)|(tracemalloc_.+)$") + + param: tuple + + def __init__(self, name: str, func: Callable, attr_sources: list) -> None: + """Initialize a new instance of `TracemallocBenchmark`. + + Parameters + ---------- + name : str + The name of the benchmark. + func : callable + The function to benchmark. + attr_sources : list + A list of objects from which to draw attributes. + """ + super().__init__(name, func, attr_sources) + self.type = "tracemalloc" + self.unit = "bytes" + + def _load_vars(self): + """Load benchmark variables from attribute sources. + + Downstream handling of ``number`` is not the same as in the parent, so + need to make sure it is at least 1. + """ + super()._load_vars() + self.number = max(1, self.number) + + def run(self, *param: tuple) -> dict: + """Run the benchmark with the given parameters. + + Downstream handling of ``param`` is not the same as in the parent, so + need to store it now. + + Parameters + ---------- + *param : tuple + The parameters to pass to the benchmark function. + + Returns + ------- + dict + A dictionary with the benchmark results. It contains the samples + taken, and "the number of times the function was called in each + sample" - for this benchmark that is always ``1`` to avoid the + parent class incorrectly modifying the results. + """ + self.param = param + return super().run(*param) + + def benchmark_timing( + self, + timer: Timer, + min_repeat: int, + max_repeat: int, + max_time: float, + warmup_time: float, + number: int, + min_run_count: int, + ) -> tuple[list[int], int]: + """Benchmark the timing of the function execution. + + Heavily modified from the parent method + - Directly performs setup and measurement (parent used timeit). + - `number` used differently (see Parameters). + - No warmup phase. + + Parameters + ---------- + timer : timeit.Timer + Not used. + min_repeat : int + The minimum number of times to repeat the function execution. + max_repeat : int + The maximum number of times to repeat the function execution. + max_time : float + The maximum total time to spend on the benchmarking. + warmup_time : float + Not used. + number : int + The number of times the benchmarked operation will be called per + repeat. Memory growth is measured after ALL calls - i.e. `number` + should make no difference to the result if the operation + has perfect garbage collection. The parent class's intelligent + modification of `number` is NOT inherited. + min_run_count : int + Not used. + + Returns + ------- + list + A list of the measured memory growths, in bytes. + int = 1 + Part of the inherited return signature. Must be 1 to avoid + the parent incorrectly modifying the results. + """ + start_time = wall_timer() + samples: list[int] = [] + + def too_slow(num_samples) -> bool: + """Stop taking samples if limits exceeded. + + Parameters + ---------- + num_samples : int + The number of samples taken so far. + + Returns + ------- + bool + True if the benchmark should stop, False otherwise. + """ + if num_samples < min_repeat: + return False + return wall_timer() > start_time + max_time + + # Collect samples + while len(samples) < max_repeat: + self.redo_setup() + tracemalloc.start() + for _ in range(number): + __ = self.func(*self.param) + _, peak_mem_bytes = tracemalloc.get_traced_memory() + tracemalloc.stop() + + samples.append(peak_mem_bytes) + + if too_slow(len(samples)): + break + + # ``number`` is not used in the same way as in the parent class. Must + # be returned as 1 to avoid parent incorrectly modifying the results. + return samples, 1 + + +# https://asv.readthedocs.io/projects/asv-runner/en/latest/development/benchmark_plugins.html +export_as_benchmark = [TracemallocBenchmark] diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst index efe9fc621c..5bc24b08dc 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/latest.rst @@ -75,7 +75,9 @@ This document explains the changes made to Iris for this release 💼 Internal =========== -#. N/A +#. `@trexfeathers`_ improved the new ``tracemalloc`` benchmarking (introduced + in Iris v3.10.0, :pull:`5948`) to use the same statistical repeat strategy + as timing benchmarks. (:pull:`5981`) .. comment From 44257ebf1e4588db73ea6694be985eb3665d62e7 Mon Sep 17 00:00:00 2001 From: Ruth Comer <10599679+rcomer@users.noreply.github.com> Date: Fri, 30 Aug 2024 15:17:58 +0100 Subject: [PATCH 22/26] Partial collapse of multi-dim string coords: take 2 (#5955) * add failing tests * pass tests * add and pass bounded tests * simpler bounds loop * consider masked case * whatsnew * consider case when all axes chosen * slicing loops -= 1 * remove dubious check; reorder tests * ruff format * improve apply_along_axis comment * add tests for collapsing 2 dimensions --------- Co-authored-by: stephenworsley <49274989+stephenworsley@users.noreply.github.com> --- docs/src/whatsnew/latest.rst | 3 + lib/iris/coords.py | 41 ++++++--- lib/iris/tests/unit/coords/test_Coord.py | 107 +++++++++++++++++++++++ 3 files changed, 139 insertions(+), 12 deletions(-) diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst index 5bc24b08dc..9b3fad7a4b 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/latest.rst @@ -38,6 +38,9 @@ This document explains the changes made to Iris for this release #. N/A +#. `@rcomer`_ enabled partial collapse of multi-dimensional string coordinates, + fixing :issue:`3653`. (:pull:`5955`) + 💣 Incompatible Changes ======================= diff --git a/lib/iris/coords.py b/lib/iris/coords.py index d2f5b05f89..8afe9dad41 100644 --- a/lib/iris/coords.py +++ b/lib/iris/coords.py @@ -2115,22 +2115,39 @@ def collapsed(self, dims_to_collapse=None): if np.issubdtype(self.dtype, np.str_): # Collapse the coordinate by serializing the points and # bounds as strings. - def serialize(x): - return "|".join([str(i) for i in x.flatten()]) + def serialize(x, axis): + if axis is None: + return "|".join(str(i) for i in x.flatten()) + + # np.apply_along_axis combined with str.join will truncate strings in + # some cases (https://github.com/numpy/numpy/issues/8352), so we need to + # loop through the array directly. First move (possibly multiple) axis + # of interest to trailing dim(s), then make a 2D array we can loop + # through. + work_array = np.moveaxis(x, axis, range(-len(axis), 0)) + out_shape = work_array.shape[: -len(axis)] + work_array = work_array.reshape(np.prod(out_shape, dtype=int), -1) + + joined = [] + for arr_slice in work_array: + joined.append(serialize(arr_slice, None)) + + return np.array(joined).reshape(out_shape) bounds = None if self.has_bounds(): - shape = self._bounds_dm.shape[1:] - bounds = [] - for index in np.ndindex(shape): - index_slice = (slice(None),) + tuple(index) - bounds.append(serialize(self.bounds[index_slice])) - dtype = np.dtype("U{}".format(max(map(len, bounds)))) - bounds = np.array(bounds, dtype=dtype).reshape((1,) + shape) - points = serialize(self.points) - dtype = np.dtype("U{}".format(len(points))) + # Express dims_to_collapse as non-negative integers. + if dims_to_collapse is None: + dims_to_collapse = range(self.ndim) + else: + dims_to_collapse = tuple( + dim % self.ndim for dim in dims_to_collapse + ) + bounds = serialize(self.bounds, dims_to_collapse) + + points = serialize(self.points, dims_to_collapse) # Create the new collapsed coordinate. - coord = self.copy(points=np.array(points, dtype=dtype), bounds=bounds) + coord = self.copy(points=np.array(points), bounds=bounds) else: # Collapse the coordinate by calculating the bounded extremes. if self.ndim > 1: diff --git a/lib/iris/tests/unit/coords/test_Coord.py b/lib/iris/tests/unit/coords/test_Coord.py index c63261f95c..97429f58f8 100644 --- a/lib/iris/tests/unit/coords/test_Coord.py +++ b/lib/iris/tests/unit/coords/test_Coord.py @@ -16,6 +16,7 @@ import cf_units import dask.array as da import numpy as np +import numpy.ma as ma import pytest import iris @@ -701,6 +702,112 @@ def test_lazy_3_bounds(self): self.assertArrayAlmostEqual(collapsed_coord.points, da.array([2.0])) self.assertArrayAlmostEqual(collapsed_coord.bounds, da.array([[0.0, 4.0]])) + def test_string_masked(self): + points = ma.array(["foo", "bar", "bing"], mask=[0, 1, 0], dtype=str) + coord = AuxCoord(points) + + collapsed_coord = coord.collapsed(0) + + expected = "foo|--|bing" + self.assertEqual(collapsed_coord.points, expected) + + def test_string_nd_first(self): + self.setupTestArrays((3, 4)) + coord = AuxCoord(self.pts_real.astype(str)) + + collapsed_coord = coord.collapsed(0) + expected = [ + "0.0|40.0|80.0", + "10.0|50.0|90.0", + "20.0|60.0|100.0", + "30.0|70.0|110.0", + ] + + self.assertArrayEqual(collapsed_coord.points, expected) + + def test_string_nd_second(self): + self.setupTestArrays((3, 4)) + coord = AuxCoord(self.pts_real.astype(str)) + + collapsed_coord = coord.collapsed(1) + expected = [ + "0.0|10.0|20.0|30.0", + "40.0|50.0|60.0|70.0", + "80.0|90.0|100.0|110.0", + ] + + self.assertArrayEqual(collapsed_coord.points, expected) + + def test_string_nd_both(self): + self.setupTestArrays((3, 4)) + coord = AuxCoord(self.pts_real.astype(str)) + + collapsed_coord = coord.collapsed() + expected = ["0.0|10.0|20.0|30.0|40.0|50.0|60.0|70.0|80.0|90.0|100.0|110.0"] + + self.assertArrayEqual(collapsed_coord.points, expected) + + def test_string_nd_bounds_first(self): + self.setupTestArrays((3, 4)) + coord = AuxCoord(self.pts_real.astype(str), bounds=self.bds_real.astype(str)) + + collapsed_coord = coord.collapsed(0) + + # Points handling is as for non bounded case. So just check bounds. + expected_lower = [ + "-2.0|38.0|78.0", + "8.0|48.0|88.0", + "18.0|58.0|98.0", + "28.0|68.0|108.0", + ] + + expected_upper = [ + "2.0|42.0|82.0", + "12.0|52.0|92.0", + "22.0|62.0|102.0", + "32.0|72.0|112.0", + ] + + self.assertArrayEqual(collapsed_coord.bounds[:, 0], expected_lower) + self.assertArrayEqual(collapsed_coord.bounds[:, 1], expected_upper) + + def test_string_nd_bounds_second(self): + self.setupTestArrays((3, 4)) + coord = AuxCoord(self.pts_real.astype(str), bounds=self.bds_real.astype(str)) + + collapsed_coord = coord.collapsed(1) + + # Points handling is as for non bounded case. So just check bounds. + expected_lower = [ + "-2.0|8.0|18.0|28.0", + "38.0|48.0|58.0|68.0", + "78.0|88.0|98.0|108.0", + ] + + expected_upper = [ + "2.0|12.0|22.0|32.0", + "42.0|52.0|62.0|72.0", + "82.0|92.0|102.0|112.0", + ] + + self.assertArrayEqual(collapsed_coord.bounds[:, 0], expected_lower) + self.assertArrayEqual(collapsed_coord.bounds[:, 1], expected_upper) + + def test_string_nd_bounds_both(self): + self.setupTestArrays((3, 4)) + coord = AuxCoord(self.pts_real.astype(str), bounds=self.bds_real.astype(str)) + + collapsed_coord = coord.collapsed() + + # Points handling is as for non bounded case. So just check bounds. + expected_lower = ["-2.0|8.0|18.0|28.0|38.0|48.0|58.0|68.0|78.0|88.0|98.0|108.0"] + expected_upper = [ + "2.0|12.0|22.0|32.0|42.0|52.0|62.0|72.0|82.0|92.0|102.0|112.0" + ] + + self.assertArrayEqual(collapsed_coord.bounds[:, 0], expected_lower) + self.assertArrayEqual(collapsed_coord.bounds[:, 1], expected_upper) + class Test_is_compatible(tests.IrisTest): def setUp(self): From 13017e3fc4c01a9c6be4b6eec2cdda85390690e4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 3 Sep 2024 08:33:11 +0100 Subject: [PATCH 23/26] Bump scitools/workflows from 2024.08.3 to 2024.09.0 (#6136) Bumps [scitools/workflows](https://github.com/scitools/workflows) from 2024.08.3 to 2024.09.0. - [Release notes](https://github.com/scitools/workflows/releases) - [Commits](https://github.com/scitools/workflows/compare/2024.08.3...2024.09.0) --- updated-dependencies: - dependency-name: scitools/workflows dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci-manifest.yml | 2 +- .github/workflows/refresh-lockfiles.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci-manifest.yml b/.github/workflows/ci-manifest.yml index 527356ff35..cb42087910 100644 --- a/.github/workflows/ci-manifest.yml +++ b/.github/workflows/ci-manifest.yml @@ -23,4 +23,4 @@ concurrency: jobs: manifest: name: "check-manifest" - uses: scitools/workflows/.github/workflows/ci-manifest.yml@2024.08.3 + uses: scitools/workflows/.github/workflows/ci-manifest.yml@2024.09.0 diff --git a/.github/workflows/refresh-lockfiles.yml b/.github/workflows/refresh-lockfiles.yml index f3755f7709..c0a23041bd 100644 --- a/.github/workflows/refresh-lockfiles.yml +++ b/.github/workflows/refresh-lockfiles.yml @@ -14,5 +14,5 @@ on: jobs: refresh_lockfiles: - uses: scitools/workflows/.github/workflows/refresh-lockfiles.yml@2024.08.3 + uses: scitools/workflows/.github/workflows/refresh-lockfiles.yml@2024.09.0 secrets: inherit From a045c81a4910f748c99a7a4b3380c18e28633de4 Mon Sep 17 00:00:00 2001 From: Bouwe Andela Date: Tue, 3 Sep 2024 15:06:28 +0200 Subject: [PATCH 24/26] Parallel concatenate (#5926) * Simplify concatenate * First attempt at parallel concatenate * Clean up a bit * Add support for comparing different data types * Undo unnessary change * More tests * Use faster lookup * Add test to show that NaNs are considered equal * Avoid inserting closures into the Dask graph * Fix type hints * Compute numpy array hashes immediately * Concatenate 25 cubes instead of 2 * Improve test coverage * Add whatsnew entry * Various improvements from review * Use correct value for chunks for numpy arrays * Python 3.10 compatibility * Avoid creating derived coordinates multiple times * Support comparing differently shaped arrays * Rewrite for code style without multiple returns * Remove print call * Better hashing algorithm * Add more information to release notes --- benchmarks/benchmarks/merge_concat.py | 32 +- docs/src/whatsnew/latest.rst | 12 + lib/iris/_concatenate.py | 562 +++++++++++++----- .../concatenate/test_concatenate.py | 9 + .../unit/concatenate/test__CoordSignature.py | 4 +- .../tests/unit/concatenate/test_hashing.py | 73 +++ 6 files changed, 524 insertions(+), 168 deletions(-) create mode 100644 lib/iris/tests/unit/concatenate/test_hashing.py diff --git a/benchmarks/benchmarks/merge_concat.py b/benchmarks/benchmarks/merge_concat.py index 0bb4096e6c..2d3738683a 100644 --- a/benchmarks/benchmarks/merge_concat.py +++ b/benchmarks/benchmarks/merge_concat.py @@ -4,9 +4,12 @@ # See LICENSE in the root of the repository for full licensing details. """Benchmarks relating to :meth:`iris.cube.CubeList.merge` and ``concatenate``.""" +import warnings + import numpy as np from iris.cube import CubeList +from iris.warnings import IrisVagueMetadataWarning from .generate_data.stock import realistic_4d_w_everything @@ -44,19 +47,26 @@ class Concatenate: cube_list: CubeList - def setup(self): - source_cube = realistic_4d_w_everything() - second_cube = source_cube.copy() - first_dim_coord = second_cube.coord(dimensions=0, dim_coords=True) - first_dim_coord.points = ( - first_dim_coord.points + np.ptp(first_dim_coord.points) + 1 - ) - self.cube_list = CubeList([source_cube, second_cube]) - - def time_concatenate(self): + params = [[False, True]] + param_names = ["Lazy operations"] + + def setup(self, lazy_run: bool): + warnings.filterwarnings("ignore", message="Ignoring a datum") + warnings.filterwarnings("ignore", category=IrisVagueMetadataWarning) + source_cube = realistic_4d_w_everything(lazy=lazy_run) + self.cube_list = CubeList([source_cube]) + for _ in range(24): + next_cube = self.cube_list[-1].copy() + first_dim_coord = next_cube.coord(dimensions=0, dim_coords=True) + first_dim_coord.points = ( + first_dim_coord.points + np.ptp(first_dim_coord.points) + 1 + ) + self.cube_list.append(next_cube) + + def time_concatenate(self, _): _ = self.cube_list.concatenate_cube() - def tracemalloc_concatenate(self): + def tracemalloc_concatenate(self, _): _ = self.cube_list.concatenate_cube() tracemalloc_concatenate.number = 3 # type: ignore[attr-defined] diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst index 9b3fad7a4b..3a761e0073 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/latest.rst @@ -57,6 +57,18 @@ This document explains the changes made to Iris for this release with cftime :class:`~cftime.datetime` objects can benefit from the same improvement by adding a type hint to their category funcion. (:pull:`5999`) +#. `@bouweandela`_ made :meth:`iris.cube.CubeList.concatenate` faster if more + than two cubes are concatenated with equality checks on the values of + auxiliary coordinates, derived coordinates, cell measures, or ancillary + variables enabled. + In some cases, this may lead to higher memory use. This can be remedied by + reducing the number of Dask workers. + In rare cases, the new implementation could potentially be slower. This + may happen when there are very many or large auxiliary coordinates, derived + coordinates, cell measures, or ancillary variables to be checked that span + the concatenation axis. This issue can be avoided by disabling the + problematic check. (:pull:`5926`) + 🔥 Deprecations =============== diff --git a/lib/iris/_concatenate.py b/lib/iris/_concatenate.py index 1b33e344f9..90f2438742 100644 --- a/lib/iris/_concatenate.py +++ b/lib/iris/_concatenate.py @@ -4,13 +4,20 @@ # See LICENSE in the root of the repository for full licensing details. """Automatic concatenation of multiple cubes over one or more existing dimensions.""" -from collections import defaultdict, namedtuple +from collections import namedtuple +from collections.abc import Mapping, Sequence +import itertools +from typing import Any import warnings +import dask +import dask.array as da import numpy as np +from xxhash import xxh3_64 from iris._lazy_data import concatenate as concatenate_arrays import iris.coords +from iris.coords import AncillaryVariable, AuxCoord, CellMeasure, DimCoord import iris.cube import iris.exceptions from iris.util import array_equal, guess_coord_axis @@ -281,14 +288,253 @@ class _CoordExtent(namedtuple("CoordExtent", ["points", "bounds"])): __slots__ = () +def _hash_ndarray(a: np.ndarray) -> np.ndarray: + """Compute a hash from a numpy array. + + Calculates a 64-bit non-cryptographic hash of the provided array, using + the fast ``xxhash`` hashing algorithm. + + Parameters + ---------- + a : + The array to hash. + + Returns + ------- + numpy.ndarray : + An array of shape (1,) containing the hash value. + + """ + # Include the array dtype as it is not preserved by `ndarray.tobytes()`. + hash = xxh3_64(f"dtype={a.dtype}".encode("utf-8")) + + # Hash the bytes representing the array data. + hash.update(b"data=") + if isinstance(a, np.ma.MaskedArray): + # Hash only the unmasked data + hash.update(a.compressed().tobytes()) + # Hash the mask + hash.update(b"mask=") + hash.update(a.mask.tobytes()) + else: + hash.update(a.tobytes()) + return np.frombuffer(hash.digest(), dtype=np.int64) + + +def _hash_chunk( + x_chunk: np.ndarray, + axis: tuple[int] | None, + keepdims: bool, +) -> np.ndarray: + """Compute a hash from a numpy array. + + This function can be applied to each chunk or intermediate chunk in + :func:`~dask.array.reduction`. It preserves the number of input dimensions + to facilitate combining intermediate results into intermediate chunks. + + Parameters + ---------- + x_chunk : + The array to hash. + axis : + Unused but required by :func:`~dask.array.reduction`. + keepdims : + Unused but required by :func:`~dask.array.reduction`. + + Returns + ------- + numpy.ndarray : + An array containing the hash value. + + """ + return _hash_ndarray(x_chunk).reshape((1,) * x_chunk.ndim) + + +def _hash_aggregate( + x_chunk: np.ndarray, + axis: tuple[int] | None, + keepdims: bool, +) -> np.int64: + """Compute a hash from a numpy array. + + This function can be applied as the final step in :func:`~dask.array.reduction`. + + Parameters + ---------- + x_chunk : + The array to hash. + axis : + Unused but required by :func:`~dask.array.reduction`. + keepdims : + Unused but required by :func:`~dask.array.reduction`. + + Returns + ------- + np.int64 : + The hash value. + + """ + (result,) = _hash_ndarray(x_chunk) + return result + + +def _hash_array(a: da.Array | np.ndarray) -> np.int64: + """Calculate a hash representation of the provided array. + + Calculates a 64-bit non-cryptographic hash of the provided array, using + the fast ``xxhash`` hashing algorithm. + + Note that the computed hash depends on how the array is chunked. + + Parameters + ---------- + a : + The array that requires to have its hexdigest calculated. + + Returns + ------- + np.int64 + The array's hash. + + """ + if isinstance(a, da.Array): + # Use :func:`~dask.array.reduction` to compute a hash from a Dask array. + # + # A hash of each input chunk will be computed by the `chunk` function + # and those hashes will be combined into one or more intermediate chunks. + # If there are multiple intermediate chunks, a hash for each intermediate + # chunk will be computed by the `combine` function and the + # results will be combined into a new layer of intermediate chunks. This + # will be repeated until only a single intermediate chunk remains. + # Finally, a single hash value will be computed from the last + # intermediate chunk by the `aggregate` function. + result = da.reduction( + a, + chunk=_hash_chunk, + combine=_hash_chunk, + aggregate=_hash_aggregate, + keepdims=False, + meta=np.empty(tuple(), dtype=np.int64), + dtype=np.int64, + ) + else: + result = _hash_aggregate(a, None, False) + return result + + +class _ArrayHash(namedtuple("ArrayHash", ["value", "chunks"])): + """Container for a hash value and the chunks used when computing it. + + Parameters + ---------- + value : :class:`np.int64` + The hash value. + chunks : tuple + The chunks the array had when the hash was computed. + """ + + __slots__ = () + + def __eq__(self, other: Any) -> bool: + if not isinstance(other, self.__class__): + raise TypeError(f"Unable to compare {repr(self)} to {repr(other)}") + + def shape(chunks): + return tuple(sum(c) for c in chunks) + + if shape(self.chunks) == shape(other.chunks): + if self.chunks != other.chunks: + raise ValueError( + "Unable to compare arrays with different chunks: " + f"{self.chunks} != {other.chunks}" + ) + result = self.value == other.value + else: + result = False + return result + + +def _array_id( + coord: DimCoord | AuxCoord | AncillaryVariable | CellMeasure, + bound: bool, +) -> str: + """Get a unique key for looking up arrays associated with coordinates.""" + return f"{id(coord)}{bound}" + + +def _compute_hashes( + arrays: Mapping[str, np.ndarray | da.Array], +) -> dict[str, _ArrayHash]: + """Compute hashes for the arrays that will be compared. + + Two arrays are considered equal if each unmasked element compares equal + and the masks are equal. However, hashes depend on chunking and dtype. + Therefore, arrays with the same shape are rechunked so they have the same + chunks and arrays with numerical dtypes are cast up to the same dtype before + computing the hashes. + + Parameters + ---------- + arrays : + A mapping with key-array pairs. + + Returns + ------- + dict[str, _ArrayHash] : + An dictionary of hashes. + + """ + hashes = {} + + def is_numerical(dtype): + return np.issubdtype(dtype, np.bool_) or np.issubdtype(dtype, np.number) + + def group_key(item): + array_id, a = item + if is_numerical(a.dtype): + dtype = "numerical" + else: + dtype = str(a.dtype) + return a.shape, dtype + + sorted_arrays = sorted(arrays.items(), key=group_key) + for _, group_iter in itertools.groupby(sorted_arrays, key=group_key): + array_ids, group = zip(*group_iter) + # Unify dtype for numerical arrays, as the hash depends on it + if is_numerical(group[0].dtype): + dtype = np.result_type(*group) + same_dtype_arrays = tuple(a.astype(dtype) for a in group) + else: + same_dtype_arrays = group + if any(isinstance(a, da.Array) for a in same_dtype_arrays): + # Unify chunks as the hash depends on the chunks. + indices = tuple(range(group[0].ndim)) + # Because all arrays in a group have the same shape, `indices` + # are the same for all of them. Providing `indices` as a tuple + # instead of letters is easier to do programmatically. + argpairs = [(a, indices) for a in same_dtype_arrays] + __, rechunked_arrays = da.core.unify_chunks(*itertools.chain(*argpairs)) + else: + rechunked_arrays = same_dtype_arrays + for array_id, rechunked in zip(array_ids, rechunked_arrays): + if isinstance(rechunked, da.Array): + chunks = rechunked.chunks + else: + chunks = tuple((i,) for i in rechunked.shape) + hashes[array_id] = (_hash_array(rechunked), chunks) + + (hashes,) = dask.compute(hashes) + return {k: _ArrayHash(*v) for k, v in hashes.items()} + + def concatenate( - cubes, - error_on_mismatch=False, - check_aux_coords=True, - check_cell_measures=True, - check_ancils=True, - check_derived_coords=True, -): + cubes: Sequence[iris.cube.Cube], + error_on_mismatch: bool = False, + check_aux_coords: bool = True, + check_cell_measures: bool = True, + check_ancils: bool = True, + check_derived_coords: bool = True, +) -> iris.cube.CubeList: """Concatenate the provided cubes over common existing dimensions. Parameters @@ -326,21 +572,49 @@ def concatenate( A :class:`iris.cube.CubeList` of concatenated :class:`iris.cube.Cube` instances. """ - proto_cubes_by_name = defaultdict(list) + cube_signatures = [_CubeSignature(cube) for cube in cubes] + + proto_cubes: list[_ProtoCube] = [] # Initialise the nominated axis (dimension) of concatenation # which requires to be negotiated. axis = None + # Compute hashes for parallel array comparison. + arrays = {} + + def add_coords(cube_signature: _CubeSignature, coord_type: str) -> None: + for coord_and_dims in getattr(cube_signature, coord_type): + coord = coord_and_dims.coord + array_id = _array_id(coord, bound=False) + if isinstance(coord, (DimCoord, AuxCoord)): + arrays[array_id] = coord.core_points() + if coord.has_bounds(): + bound_array_id = _array_id(coord, bound=True) + arrays[bound_array_id] = coord.core_bounds() + else: + arrays[array_id] = coord.core_data() + + for cube_signature in cube_signatures: + if check_aux_coords: + add_coords(cube_signature, "aux_coords_and_dims") + if check_derived_coords: + add_coords(cube_signature, "derived_coords_and_dims") + if check_cell_measures: + add_coords(cube_signature, "cell_measures_and_dims") + if check_ancils: + add_coords(cube_signature, "ancillary_variables_and_dims") + + hashes = _compute_hashes(arrays) + # Register each cube with its appropriate proto-cube. - for cube in cubes: - name = cube.standard_name or cube.long_name - proto_cubes = proto_cubes_by_name[name] + for cube_signature in cube_signatures: registered = False # Register cube with an existing proto-cube. for proto_cube in proto_cubes: registered = proto_cube.register( - cube, + cube_signature, + hashes, axis, error_on_mismatch, check_aux_coords, @@ -354,19 +628,17 @@ def concatenate( # Create a new proto-cube for an unregistered cube. if not registered: - proto_cubes.append(_ProtoCube(cube)) + proto_cubes.append(_ProtoCube(cube_signature)) # Construct a concatenated cube from each of the proto-cubes. concatenated_cubes = iris.cube.CubeList() # Emulate Python 2 behaviour. - def _none_sort(item): - return (item is not None, item) + def _none_sort(proto_cube): + return (proto_cube.name is not None, proto_cube.name) - for name in sorted(proto_cubes_by_name, key=_none_sort): - for proto_cube in proto_cubes_by_name[name]: - # Construct the concatenated cube. - concatenated_cubes.append(proto_cube.concatenate()) + for proto_cube in sorted(proto_cubes, key=_none_sort): + concatenated_cubes.append(proto_cube.concatenate()) # Perform concatenation until we've reached an equilibrium. count = len(concatenated_cubes) @@ -384,7 +656,7 @@ class _CubeSignature: """ - def __init__(self, cube): + def __init__(self, cube: iris.cube.Cube) -> None: """Represent the cube metadata and associated coordinate metadata. Parameters @@ -413,14 +685,14 @@ def __init__(self, cube): self.defn = cube.metadata self.data_type = cube.dtype + self.src_cube = cube # # Collate the dimension coordinate metadata. # - for ind, coord in enumerate(self.dim_coords): + for coord in self.dim_coords: dims = cube.coord_dims(coord) - metadata = _CoordMetaData(coord, dims) - self.dim_metadata.append(metadata) + self.dim_metadata.append(_CoordMetaData(coord, dims)) self.dim_mapping.append(dims[0]) # @@ -440,10 +712,8 @@ def key_func(coord): for coord in sorted(cube.aux_coords, key=key_func): dims = cube.coord_dims(coord) if dims: - metadata = _CoordMetaData(coord, dims) - self.aux_metadata.append(metadata) - coord_and_dims = _CoordAndDims(coord, tuple(dims)) - self.aux_coords_and_dims.append(coord_and_dims) + self.aux_metadata.append(_CoordMetaData(coord, dims)) + self.aux_coords_and_dims.append(_CoordAndDims(coord, tuple(dims))) else: self.scalar_coords.append(coord) @@ -452,17 +722,13 @@ def meta_key_func(dm): for cm in sorted(cube.cell_measures(), key=meta_key_func): dims = cube.cell_measure_dims(cm) - metadata = _OtherMetaData(cm, dims) - self.cm_metadata.append(metadata) - cm_and_dims = _CoordAndDims(cm, tuple(dims)) - self.cell_measures_and_dims.append(cm_and_dims) + self.cm_metadata.append(_OtherMetaData(cm, dims)) + self.cell_measures_and_dims.append(_CoordAndDims(cm, tuple(dims))) for av in sorted(cube.ancillary_variables(), key=meta_key_func): dims = cube.ancillary_variable_dims(av) - metadata = _OtherMetaData(av, dims) - self.av_metadata.append(metadata) - av_and_dims = _CoordAndDims(av, tuple(dims)) - self.ancillary_variables_and_dims.append(av_and_dims) + self.av_metadata.append(_OtherMetaData(av, dims)) + self.ancillary_variables_and_dims.append(_CoordAndDims(av, tuple(dims))) def name_key_func(factory): return factory.name() @@ -470,10 +736,10 @@ def name_key_func(factory): for factory in sorted(cube.aux_factories, key=name_key_func): coord = factory.make_coord(cube.coord_dims) dims = factory.derived_dims(cube.coord_dims) - metadata = _CoordMetaData(coord, dims) - self.derived_metadata.append(metadata) - coord_and_dims = _DerivedCoordAndDims(coord, tuple(dims), factory) - self.derived_coords_and_dims.append(coord_and_dims) + self.derived_metadata.append(_CoordMetaData(coord, dims)) + self.derived_coords_and_dims.append( + _DerivedCoordAndDims(coord, tuple(dims), factory) + ) def _coordinate_differences(self, other, attr, reason="metadata"): """Determine the names of the coordinates that differ. @@ -607,7 +873,7 @@ def match(self, other, error_on_mismatch): class _CoordSignature: """Template for identifying a specific type of :class:`iris.cube.Cube` based on its coordinates.""" - def __init__(self, cube_signature): + def __init__(self, cube_signature: _CubeSignature) -> None: """Represent the coordinate metadata. Represent the coordinate metadata required to identify suitable @@ -626,7 +892,7 @@ def __init__(self, cube_signature): self.derived_coords_and_dims = cube_signature.derived_coords_and_dims self.dim_coords = cube_signature.dim_coords self.dim_mapping = cube_signature.dim_mapping - self.dim_extents = [] + self.dim_extents: list[_CoordExtent] = [] self.dim_order = [ metadata.kwargs["order"] for metadata in cube_signature.dim_metadata ] @@ -635,7 +901,10 @@ def __init__(self, cube_signature): self._calculate_extents() @staticmethod - def _cmp(coord, other): + def _cmp( + coord: iris.coords.DimCoord, + other: iris.coords.DimCoord, + ) -> tuple[bool, bool]: """Compare the coordinates for concatenation compatibility. Returns @@ -646,22 +915,17 @@ def _cmp(coord, other): """ # A candidate axis must have non-identical coordinate points. - candidate_axis = not array_equal(coord.core_points(), other.core_points()) + candidate_axis = not array_equal(coord.points, other.points) - if candidate_axis: - # Ensure both have equal availability of bounds. - result = (coord.core_bounds() is None) == (other.core_bounds() is None) - else: - if coord.core_bounds() is not None and other.core_bounds() is not None: - # Ensure equality of bounds. - result = array_equal(coord.core_bounds(), other.core_bounds()) - else: - # Ensure both have equal availability of bounds. - result = coord.core_bounds() is None and other.core_bounds() is None + # Ensure both have equal availability of bounds. + result = coord.has_bounds() == other.has_bounds() + if result and not candidate_axis: + # Ensure equality of bounds. + result = array_equal(coord.bounds, other.bounds) return result, candidate_axis - def candidate_axis(self, other): + def candidate_axis(self, other: "_CoordSignature") -> int | None: """Determine the candidate axis of concatenation with the given coordinate signature. If a candidate axis is found, then the coordinate @@ -693,13 +957,13 @@ def candidate_axis(self, other): # Only permit one degree of dimensional freedom when # determining the candidate axis of concatenation. if result and len(candidate_axes) == 1: - result = candidate_axes[0] + axis = candidate_axes[0] else: - result = None + axis = None - return result + return axis - def _calculate_extents(self): + def _calculate_extents(self) -> None: """Calculate the extent over each dimension coordinates points and bounds.""" self.dim_extents = [] for coord, order in zip(self.dim_coords, self.dim_order): @@ -729,21 +993,21 @@ def _calculate_extents(self): class _ProtoCube: """Framework for concatenating multiple source-cubes over one common dimension.""" - def __init__(self, cube): + def __init__(self, cube_signature): """Create a new _ProtoCube from the given cube and record the cube as a source-cube. Parameters ---------- - cube : - Source :class:`iris.cube.Cube` of the :class:`_ProtoCube`. + cube_signature : + Source :class:`_CubeSignature` of the :class:`_ProtoCube`. """ # Cache the source-cube of this proto-cube. - self._cube = cube + self._cube = cube_signature.src_cube # The cube signature is a combination of cube and coordinate # metadata that defines this proto-cube. - self._cube_signature = _CubeSignature(cube) + self._cube_signature = cube_signature # The coordinate signature allows suitable non-overlapping # source-cubes to be identified. @@ -751,7 +1015,7 @@ def __init__(self, cube): # The list of source-cubes relevant to this proto-cube. self._skeletons = [] - self._add_skeleton(self._coord_signature, cube.lazy_data()) + self._add_skeleton(self._coord_signature, self._cube.lazy_data()) # The nominated axis of concatenation. self._axis = None @@ -761,6 +1025,12 @@ def axis(self): """Return the nominated dimension of concatenation.""" return self._axis + @property + def name(self) -> str | None: + """Return the standard_name or long name.""" + metadata = self._cube_signature.defn + return metadata.standard_name or metadata.long_name + def concatenate(self): """Concatenate all the source-cubes registered with the :class:`_ProtoCube`. @@ -833,14 +1103,15 @@ def concatenate(self): def register( self, - cube, - axis=None, - error_on_mismatch=False, - check_aux_coords=False, - check_cell_measures=False, - check_ancils=False, - check_derived_coords=False, - ): + cube_signature: _CubeSignature, + hashes: Mapping[str, _ArrayHash], + axis: int | None = None, + error_on_mismatch: bool = False, + check_aux_coords: bool = False, + check_cell_measures: bool = False, + check_ancils: bool = False, + check_derived_coords: bool = False, + ) -> bool: """Determine if the given source-cube is suitable for concatenation. Determine if the given source-cube is suitable for concatenation @@ -848,9 +1119,12 @@ def register( Parameters ---------- - cube : :class:`iris.cube.Cube` - The :class:`iris.cube.Cube` source-cube candidate for + cube_signature : :class:`_CubeSignature` + The :class:`_CubeSignature` of the source-cube candidate for concatenation. + hashes : + A mapping containing hash values for checking coordinate, ancillary + variable, and cell measure equality. axis : optional Seed the dimension of concatenation for the :class:`_ProtoCube` rather than rely on negotiation with source-cubes. @@ -891,7 +1165,6 @@ def register( raise ValueError(msg) # Check for compatible cube signatures. - cube_signature = _CubeSignature(cube) match = self._cube_signature.match(cube_signature, error_on_mismatch) mismatch_error_msg = None @@ -917,97 +1190,76 @@ def register( elif not match: mismatch_error_msg = f"Found cubes with overlap on concatenate axis {candidate_axis}, skipping concatenation for these cubes" - # Check for compatible AuxCoords. - if match: - if check_aux_coords: - for coord_a, coord_b in zip( - self._cube_signature.aux_coords_and_dims, - cube_signature.aux_coords_and_dims, + def get_hashes( + coord: DimCoord | AuxCoord | AncillaryVariable | CellMeasure, + ) -> tuple[_ArrayHash, ...]: + array_id = _array_id(coord, bound=False) + result = [hashes[array_id]] + if isinstance(coord, (DimCoord, AuxCoord)) and coord.has_bounds(): + bound_array_id = _array_id(coord, bound=True) + result.append(hashes[bound_array_id]) + return tuple(result) + + # Mapping from `_CubeSignature` attributes to human readable names. + coord_type_names = { + "aux_coords_and_dims": "Auxiliary coordinates", + "cell_measures_and_dims": "Cell measures", + "ancillary_variables_and_dims": "Ancillary variables", + "derived_coords_and_dims": "Derived coordinates", + } + + def check_coord_match(coord_type: str) -> tuple[bool, str]: + result = (True, "") + for coord_a, coord_b in zip( + getattr(self._cube_signature, coord_type), + getattr(cube_signature, coord_type), + ): + # Coordinates that span the candidate axis can differ + if ( + candidate_axis not in coord_a.dims + or candidate_axis not in coord_b.dims ): - # AuxCoords that span the candidate axis can differ - if ( - candidate_axis not in coord_a.dims - or candidate_axis not in coord_b.dims - ): - if not coord_a == coord_b: - mismatch_error_msg = ( - "Auxiliary coordinates are unequal for phenomenon" - f" `{self._cube.name()}`:\n" - f"a: {coord_a}\n" - f"b: {coord_b}" - ) - match = False + if not get_hashes(coord_a.coord) == get_hashes(coord_b.coord): + mismatch_error_msg = ( + f"{coord_type_names[coord_type]} are unequal for phenomenon" + f" `{self._cube.name()}`:\n" + f"a: {coord_a}\n" + f"b: {coord_b}" + ) + result = (False, mismatch_error_msg) + break + + return result + + # Check for compatible AuxCoords. + if match and check_aux_coords: + match, msg = check_coord_match("aux_coords_and_dims") + if not match: + mismatch_error_msg = msg # Check for compatible CellMeasures. - if match: - if check_cell_measures: - for coord_a, coord_b in zip( - self._cube_signature.cell_measures_and_dims, - cube_signature.cell_measures_and_dims, - ): - # CellMeasures that span the candidate axis can differ - if ( - candidate_axis not in coord_a.dims - or candidate_axis not in coord_b.dims - ): - if not coord_a == coord_b: - mismatch_error_msg = ( - "Cell measures are unequal for phenomenon" - f" `{self._cube.name()}`:\n" - f"a: {coord_a}\n" - f"b: {coord_b}" - ) - match = False + if match and check_cell_measures: + match, msg = check_coord_match("cell_measures_and_dims") + if not match: + mismatch_error_msg = msg # Check for compatible AncillaryVariables. - if match: - if check_ancils: - for coord_a, coord_b in zip( - self._cube_signature.ancillary_variables_and_dims, - cube_signature.ancillary_variables_and_dims, - ): - # AncillaryVariables that span the candidate axis can differ - if ( - candidate_axis not in coord_a.dims - or candidate_axis not in coord_b.dims - ): - if not coord_a == coord_b: - mismatch_error_msg = ( - "Ancillary variables are unequal for phenomenon" - f" `{self._cube.name()}`:\n" - f"a: {coord_a}\n" - f"b: {coord_b}" - ) - match = False + if match and check_ancils: + match, msg = check_coord_match("ancillary_variables_and_dims") + if not match: + mismatch_error_msg = msg # Check for compatible derived coordinates. - if match: - if check_derived_coords: - for coord_a, coord_b in zip( - self._cube_signature.derived_coords_and_dims, - cube_signature.derived_coords_and_dims, - ): - # Derived coords that span the candidate axis can differ - if ( - candidate_axis not in coord_a.dims - or candidate_axis not in coord_b.dims - ): - if not coord_a == coord_b: - mismatch_error_msg = ( - "Derived coordinates are unequal for phenomenon" - f" `{self._cube.name()}`:\n" - f"a: {coord_a}\n" - f"b: {coord_b}" - ) - match = False + if match and check_derived_coords: + match, msg = check_coord_match("derived_coords_and_dims") + if not match: + mismatch_error_msg = msg if match: # Register the cube as a source-cube for this proto-cube. - self._add_skeleton(coord_signature, cube.lazy_data()) + self._add_skeleton(coord_signature, cube_signature.src_cube.lazy_data()) # Declare the nominated axis of concatenation. self._axis = candidate_axis - - if match: # If the protocube dimension order is constant (indicating it was # created from a cube with a length 1 dimension coordinate) but # a subsequently registered cube has a non-constant dimension diff --git a/lib/iris/tests/integration/concatenate/test_concatenate.py b/lib/iris/tests/integration/concatenate/test_concatenate.py index 063ae76d83..821bde42cb 100644 --- a/lib/iris/tests/integration/concatenate/test_concatenate.py +++ b/lib/iris/tests/integration/concatenate/test_concatenate.py @@ -111,6 +111,15 @@ def test_diff_aux_coord(self): result = concatenate([cube_a, cube_b]) self.assertEqual(len(result), 2) + def test_diff_aux_coord_anonymous_dim(self): + cube_a = self.create_cube() + cube_a.remove_coord("latitude") + cube_b = cube_a.copy()[:, :1] + cube_b.coord("time").points = [12, 18] + + result = concatenate([cube_a, cube_b]) + self.assertEqual(len(result), 2) + def test_ignore_diff_aux_coord(self): cube_a = self.create_cube() cube_b = cube_a.copy() diff --git a/lib/iris/tests/unit/concatenate/test__CoordSignature.py b/lib/iris/tests/unit/concatenate/test__CoordSignature.py index ebce624bae..051f02de94 100644 --- a/lib/iris/tests/unit/concatenate/test__CoordSignature.py +++ b/lib/iris/tests/unit/concatenate/test__CoordSignature.py @@ -59,7 +59,7 @@ def test_dim(order: int, coord_dtype, lazy: bool, with_bounds: bool) -> None: cube_signature = MockCubeSignature( dim_coords=[metadata.coord], dim_metadata=dim_metadata ) - coord_signature = _CoordSignature(cube_signature) + coord_signature = _CoordSignature(cube_signature) # type: ignore[arg-type] assert len(coord_signature.dim_extents) == 1 (actual,) = coord_signature.dim_extents first, last = coord_dtype(0), coord_dtype((N_POINTS - 1) * SCALE_FACTOR) @@ -102,7 +102,7 @@ def test_dim__scalar(coord_dtype, lazy: bool, with_bounds: bool) -> None: cube_signature = MockCubeSignature( dim_coords=[metadata.coord], dim_metadata=dim_metadata ) - coord_signature = _CoordSignature(cube_signature) + coord_signature = _CoordSignature(cube_signature) # type: ignore[arg-type] assert len(coord_signature.dim_extents) == 1 (actual,) = coord_signature.dim_extents point = coord_dtype(1) diff --git a/lib/iris/tests/unit/concatenate/test_hashing.py b/lib/iris/tests/unit/concatenate/test_hashing.py new file mode 100644 index 0000000000..7a56be1db8 --- /dev/null +++ b/lib/iris/tests/unit/concatenate/test_hashing.py @@ -0,0 +1,73 @@ +# Copyright Iris contributors +# +# This file is part of Iris and is released under the BSD license. +# See LICENSE in the root of the repository for full licensing details. +"""Test array hashing in :mod:`iris._concatenate`.""" + +import dask.array as da +import numpy as np +import pytest + +from iris import _concatenate + + +@pytest.mark.parametrize( + "a,b,eq", + [ + (np.arange(2), da.arange(2), True), + (np.arange(2), np.arange(2).reshape((1, 2)), False), + (da.arange(2), da.arange(2).reshape((1, 2)), False), + (np.array([1], dtype=np.float32), np.array([1], dtype=bool), True), + (np.array([np.nan, 1.0]), np.array([np.nan, 1.0]), True), + (np.ma.array([1, 2], mask=[0, 1]), np.ma.array([1, 2], mask=[0, 1]), True), + (np.ma.array([1, 2], mask=[0, 1]), np.ma.array([1, 2], mask=[0, 0]), False), + (da.arange(6).reshape((2, 3)), da.arange(6, chunks=1).reshape((2, 3)), True), + (da.arange(20, chunks=1), da.arange(20, chunks=2), True), + ( + da.ma.masked_array([1, 2], mask=[0, 1]), + da.ma.masked_array([1, 2], mask=[0, 1]), + True, + ), + ( + da.ma.masked_array([1, 2], mask=[0, 1]), + da.ma.masked_array([1, 3], mask=[0, 1]), + True, + ), + ( + np.ma.array([1, 2], mask=[0, 1]), + np.ma.array([1, 3], mask=[0, 1], fill_value=10), + True, + ), + ( + np.ma.masked_array([1], mask=[True]), + np.array([np.ma.default_fill_value(np.dtype("int64"))]), + False, + ), + (np.array(["a", "b"]), np.array(["a", "b"]), True), + (np.array(["a"]), np.array(["b"]), False), + (da.asarray(["a", "b"], chunks=1), da.asarray(["a", "b"], chunks=1), True), + (da.array(["a"]), da.array(["b"]), False), + (np.array(["a"]), da.array(["a"]), True), + (np.array(["a"]), np.array([1]), False), + (da.asarray([1, 1], chunks=1), da.asarray(["a", "b"], chunks=1), False), + (np.array(["a"]), np.array(["a"]).view(dtype=np.int32), False), + ], +) +def test_compute_hashes(a, b, eq): + hashes = _concatenate._compute_hashes({"a": a, "b": b}) + assert eq == (hashes["a"] == hashes["b"]) + + +def test_arrayhash_equal_incompatible_chunks_raises(): + hash1 = _concatenate._ArrayHash(1, chunks=((1, 1),)) + hash2 = _concatenate._ArrayHash(1, chunks=((2,),)) + msg = r"Unable to compare arrays with different chunks.*" + with pytest.raises(ValueError, match=msg): + hash1 == hash2 + + +def test_arrayhash_equal_incompatible_type_raises(): + hash = _concatenate._ArrayHash(1, chunks=(1, 1)) + msg = r"Unable to compare .*" + with pytest.raises(TypeError, match=msg): + hash == object() From 02928ce0cfec6c9dc2eb3994dcdf95cd34df8e7a Mon Sep 17 00:00:00 2001 From: Martin Yeo <40734014+trexfeathers@users.noreply.github.com> Date: Tue, 3 Sep 2024 15:50:45 +0100 Subject: [PATCH 25/26] Dependabot check weekly. (#6138) --- .github/dependabot.yml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index e9b45d116a..e5797077df 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -8,8 +8,11 @@ updates: - package-ecosystem: "github-actions" directory: "/" schedule: - # Check for updates to GitHub Actions every weekday - interval: "daily" + # Check later in the week - the upstream dependabot check in `workflows` runs deliberately early in the week. + # Therefore allowing time for the `workflows` update to be merged-and-released first. + interval: "weekly" + day: "thursday" + time: "01:00" + timezone: "Europe/London" labels: - - "New: Pull Request" - "Bot" From 1a9bdaa533384ceb3e82b84b6fe2d3b70b0eb3f0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 3 Sep 2024 15:59:50 +0100 Subject: [PATCH 26/26] Bump scitools/workflows from 2024.09.0 to 2024.09.1 (#6139) Bumps [scitools/workflows](https://github.com/scitools/workflows) from 2024.09.0 to 2024.09.1. - [Release notes](https://github.com/scitools/workflows/releases) - [Commits](https://github.com/scitools/workflows/compare/2024.09.0...2024.09.1) --- updated-dependencies: - dependency-name: scitools/workflows dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci-manifest.yml | 2 +- .github/workflows/refresh-lockfiles.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci-manifest.yml b/.github/workflows/ci-manifest.yml index cb42087910..cf79783a82 100644 --- a/.github/workflows/ci-manifest.yml +++ b/.github/workflows/ci-manifest.yml @@ -23,4 +23,4 @@ concurrency: jobs: manifest: name: "check-manifest" - uses: scitools/workflows/.github/workflows/ci-manifest.yml@2024.09.0 + uses: scitools/workflows/.github/workflows/ci-manifest.yml@2024.09.1 diff --git a/.github/workflows/refresh-lockfiles.yml b/.github/workflows/refresh-lockfiles.yml index c0a23041bd..24e72e059c 100644 --- a/.github/workflows/refresh-lockfiles.yml +++ b/.github/workflows/refresh-lockfiles.yml @@ -14,5 +14,5 @@ on: jobs: refresh_lockfiles: - uses: scitools/workflows/.github/workflows/refresh-lockfiles.yml@2024.09.0 + uses: scitools/workflows/.github/workflows/refresh-lockfiles.yml@2024.09.1 secrets: inherit