Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Remove custom fix for concatenation of aux factories now that bug in iris is solved #2392

Merged
merged 2 commits into from
Apr 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 0 additions & 51 deletions esmvalcore/preprocessor/_io.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,55 +36,6 @@
}


def _fix_aux_factories(cube):
"""Fix :class:`iris.aux_factory.AuxCoordFactory` after concatenation.

Necessary because of bug in :mod:`iris` (see issue #2478).
"""
coord_names = [coord.name() for coord in cube.coords()]

# Hybrid sigma pressure coordinate
# TODO possibly add support for other hybrid coordinates
if 'atmosphere_hybrid_sigma_pressure_coordinate' in coord_names:
new_aux_factory = iris.aux_factory.HybridPressureFactory(
delta=cube.coord(var_name='ap'),
sigma=cube.coord(var_name='b'),
surface_air_pressure=cube.coord(var_name='ps'),
)
for aux_factory in cube.aux_factories:
if isinstance(aux_factory, iris.aux_factory.HybridPressureFactory):
break
else:
cube.add_aux_factory(new_aux_factory)

# Hybrid sigma height coordinate
if 'atmosphere_hybrid_height_coordinate' in coord_names:
new_aux_factory = iris.aux_factory.HybridHeightFactory(
delta=cube.coord(var_name='lev'),
sigma=cube.coord(var_name='b'),
orography=cube.coord(var_name='orog'),
)
for aux_factory in cube.aux_factories:
if isinstance(aux_factory, iris.aux_factory.HybridHeightFactory):
break
else:
cube.add_aux_factory(new_aux_factory)

# Atmosphere sigma coordinate
if 'atmosphere_sigma_coordinate' in coord_names:
new_aux_factory = iris.aux_factory.AtmosphereSigmaFactory(
pressure_at_top=cube.coord(var_name='ptop'),
sigma=cube.coord(var_name='lev'),
surface_air_pressure=cube.coord(var_name='ps'),
)
for aux_factory in cube.aux_factories:
if isinstance(aux_factory,
iris.aux_factory.AtmosphereSigmaFactory):
break
else:
cube.add_aux_factory(new_aux_factory)


def _get_attr_from_field_coord(ncfield, coord_name, attr):
if coord_name is not None:
attrs = ncfield.cf_group[coord_name].cf_attrs()
Expand Down Expand Up @@ -389,8 +340,6 @@ def concatenate(cubes, check_level=CheckLevels.DEFAULT):
else:
_get_concatenation_error(result)

_fix_aux_factories(result)

return result


Expand Down
208 changes: 1 addition & 207 deletions tests/integration/preprocessor/_io/test_concatenate.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,11 @@
"""Integration tests for :func:`esmvalcore.preprocessor._io.concatenate`."""

import unittest
import warnings
from unittest.mock import call

import numpy as np
import pytest
from cf_units import Unit
from iris.aux_factory import (
AtmosphereSigmaFactory,
HybridHeightFactory,
HybridPressureFactory,
)
from iris.aux_factory import HybridPressureFactory
from iris.coords import AuxCoord, DimCoord
from iris.cube import Cube, CubeList

Expand Down Expand Up @@ -64,98 +58,6 @@ def get_time_coord(time_point):
units='days since 6453-2-1')


@pytest.fixture
def mock_empty_cube():
"""Return mocked cube with irrelevant coordinates."""
cube = unittest.mock.create_autospec(Cube, spec_set=True, instance=True)
a_coord = AuxCoord(0.0, var_name='a')
b_coord = AuxCoord(0.0, var_name='b')
cube.coords.return_value = [a_coord, b_coord]
return cube


@pytest.fixture
def mock_atmosphere_sigma_cube():
"""Return mocked cube with atmosphere sigma coordinate."""
cube = unittest.mock.create_autospec(Cube, spec_set=True, instance=True)
ptop_coord = AuxCoord([1.0], var_name='ptop', units='Pa')
lev_coord = AuxCoord([0.0],
bounds=[[-0.5, 1.5]],
var_name='lev',
units='1')
ps_coord = AuxCoord([[[100000]]], var_name='ps', units='Pa')
cube.coord.side_effect = [
ptop_coord, lev_coord, ps_coord, ptop_coord, lev_coord, ps_coord
]
cube.coords.return_value = [
ptop_coord,
lev_coord,
ps_coord,
AuxCoord(0.0, standard_name='atmosphere_sigma_coordinate'),
]
aux_factory = AtmosphereSigmaFactory(
pressure_at_top=ptop_coord,
sigma=lev_coord,
surface_air_pressure=ps_coord,
)
cube.aux_factories = ['dummy', aux_factory]
return cube


@pytest.fixture
def mock_hybrid_height_cube():
"""Return mocked cube with hybrid height coordinate."""
cube = unittest.mock.create_autospec(Cube, spec_set=True, instance=True)
lev_coord = AuxCoord([1.0], bounds=[[0.0, 2.0]], var_name='lev', units='m')
b_coord = AuxCoord([0.0], bounds=[[-0.5, 1.5]], var_name='b')
orog_coord = AuxCoord([[[100000]]], var_name='orog', units='m')
cube.coord.side_effect = [
lev_coord, b_coord, orog_coord, lev_coord, b_coord, orog_coord
]
cube.coords.return_value = [
lev_coord,
b_coord,
orog_coord,
AuxCoord(0.0, standard_name='atmosphere_hybrid_height_coordinate'),
]
aux_factory = HybridHeightFactory(
delta=lev_coord,
sigma=b_coord,
orography=orog_coord,
)
cube.aux_factories = ['dummy', aux_factory]
return cube


@pytest.fixture
def mock_hybrid_pressure_cube():
"""Return mocked cube with hybrid pressure coordinate."""
cube = unittest.mock.create_autospec(Cube, spec_set=True, instance=True)
ap_coord = AuxCoord([1.0], bounds=[[0.0, 2.0]], var_name='ap', units='Pa')
b_coord = AuxCoord([0.0],
bounds=[[-0.5, 1.5]],
var_name='b',
units=Unit('1'))
ps_coord = AuxCoord([[[100000]]], var_name='ps', units='Pa')
cube.coord.side_effect = [
ap_coord, b_coord, ps_coord, ap_coord, b_coord, ps_coord
]
cube.coords.return_value = [
ap_coord,
b_coord,
ps_coord,
AuxCoord(0.0,
standard_name='atmosphere_hybrid_sigma_pressure_coordinate'),
]
aux_factory = HybridPressureFactory(
delta=ap_coord,
sigma=b_coord,
surface_air_pressure=ps_coord,
)
cube.aux_factories = ['dummy', aux_factory]
return cube


@pytest.fixture
def real_hybrid_pressure_cube():
"""Return real cube with hybrid pressure coordinate."""
Expand All @@ -168,114 +70,6 @@ def real_hybrid_pressure_cube_list():
return get_hybrid_pressure_cube_list()


def check_if_fix_aux_factories_is_necessary():
"""Check if _fix_aux_factories() is necessary (i.e. iris bug is fixed)."""
cubes = get_hybrid_pressure_cube_list()
cube = cubes.concatenate_cube()
coords = [coord.name() for coord in cube.coords()]
msg = ("Apparently concatenation of cubes that have a derived variable "
"is now possible in iris (i.e. issue #2478 has been fixed). Thus, "
"this test and ALL appearances of the function "
"'_fix_aux_factories' can safely be removed!")
if 'air_pressure' in coords:
warnings.warn(msg)


def test_fix_aux_factories_empty_cube(mock_empty_cube):
"""Test fixing with empty cube."""
check_if_fix_aux_factories_is_necessary()
_io._fix_aux_factories(mock_empty_cube)
assert mock_empty_cube.mock_calls == [call.coords()]


def test_fix_aux_factories_atmosphere_sigma(mock_atmosphere_sigma_cube):
"""Test fixing of atmosphere sigma coordinate."""
check_if_fix_aux_factories_is_necessary()

# Test with aux_factory object
_io._fix_aux_factories(mock_atmosphere_sigma_cube)
mock_atmosphere_sigma_cube.coords.assert_called_once_with()
mock_atmosphere_sigma_cube.coord.assert_has_calls(
[call(var_name='ptop'),
call(var_name='lev'),
call(var_name='ps')])
mock_atmosphere_sigma_cube.add_aux_factory.assert_not_called()

# Test without aux_factory object
mock_atmosphere_sigma_cube.reset_mock()
mock_atmosphere_sigma_cube.aux_factories = ['dummy']
_io._fix_aux_factories(mock_atmosphere_sigma_cube)
mock_atmosphere_sigma_cube.coords.assert_called_once_with()
mock_atmosphere_sigma_cube.coord.assert_has_calls(
[call(var_name='ptop'),
call(var_name='lev'),
call(var_name='ps')])
mock_atmosphere_sigma_cube.add_aux_factory.assert_called_once()


def test_fix_aux_factories_hybrid_height(mock_hybrid_height_cube):
"""Test fixing of hybrid height coordinate."""
check_if_fix_aux_factories_is_necessary()

# Test with aux_factory object
_io._fix_aux_factories(mock_hybrid_height_cube)
mock_hybrid_height_cube.coords.assert_called_once_with()
mock_hybrid_height_cube.coord.assert_has_calls(
[call(var_name='lev'),
call(var_name='b'),
call(var_name='orog')])
mock_hybrid_height_cube.add_aux_factory.assert_not_called()

# Test without aux_factory object
mock_hybrid_height_cube.reset_mock()
mock_hybrid_height_cube.aux_factories = ['dummy']
_io._fix_aux_factories(mock_hybrid_height_cube)
mock_hybrid_height_cube.coords.assert_called_once_with()
mock_hybrid_height_cube.coord.assert_has_calls(
[call(var_name='lev'),
call(var_name='b'),
call(var_name='orog')])
mock_hybrid_height_cube.add_aux_factory.assert_called_once()


def test_fix_aux_factories_hybrid_pressure(mock_hybrid_pressure_cube):
"""Test fixing of hybrid pressure coordinate."""
check_if_fix_aux_factories_is_necessary()

# Test with aux_factory object
_io._fix_aux_factories(mock_hybrid_pressure_cube)
mock_hybrid_pressure_cube.coords.assert_called_once_with()
mock_hybrid_pressure_cube.coord.assert_has_calls(
[call(var_name='ap'),
call(var_name='b'),
call(var_name='ps')])
mock_hybrid_pressure_cube.add_aux_factory.assert_not_called()

# Test without aux_factory object
mock_hybrid_pressure_cube.reset_mock()
mock_hybrid_pressure_cube.aux_factories = ['dummy']
_io._fix_aux_factories(mock_hybrid_pressure_cube)
mock_hybrid_pressure_cube.coords.assert_called_once_with()
mock_hybrid_pressure_cube.coord.assert_has_calls(
[call(var_name='ap'),
call(var_name='b'),
call(var_name='ps')])
mock_hybrid_pressure_cube.add_aux_factory.assert_called_once()


def test_fix_aux_factories_real_cube(real_hybrid_pressure_cube):
"""Test fixing of hybrid pressure coordinate on real cube."""
check_if_fix_aux_factories_is_necessary()
assert not real_hybrid_pressure_cube.coords('air_pressure')
_io._fix_aux_factories(real_hybrid_pressure_cube)
air_pressure_coord = real_hybrid_pressure_cube.coord('air_pressure')
expected_coord = AuxCoord([[[[1.0]]]],
bounds=[[[[[-50000., 150002.]]]]],
standard_name='air_pressure',
units='Pa')
assert air_pressure_coord == expected_coord


def test_concatenation_with_aux_factory(real_hybrid_pressure_cube_list):
"""Test actual concatenation of a cube with a derived coordinate."""
concatenated = _io.concatenate(real_hybrid_pressure_cube_list)
Expand Down