Skip to content

Commit 05d37d7

Browse files
authored
Merge pull request #925 from davidhassell/fix-cyclic-subspace-2
Fix for subspacing with cyclic `cf.wi` and `cf.wo` arguments
2 parents bb01e7c + e4712ef commit 05d37d7

File tree

5 files changed

+80
-11
lines changed

5 files changed

+80
-11
lines changed

.pre-commit-config.yaml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,11 @@
33

44
# Excludes for *all* pre-commit hooks as listed below:
55
# (these are documentation files, either old ones or aut-generated ones)
6-
exclude: docs\/3\..*\d\/|docs\/_downloads\/|docs\/.*\/tutorial\.py
6+
exclude: |
7+
(?x)^(
8+
recipes-docs/|
9+
docs/
10+
)
711
812
repos:
913

Changelog.rst

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,13 @@
1+
Version NEXTVERSION
2+
--------------
3+
4+
**2026-??-??**
5+
6+
* Fix for subspacing with cyclic `cf.wi` and `cf.wo` arguments
7+
(https://github.com/NCAS-CMS/cf-python/issues/887)
8+
9+
----
10+
111
Version 3.19.0
212
--------------
313

cf/mixin/fielddomain.py

Lines changed: 39 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
bounds_combination_mode,
1717
normalize_slice,
1818
)
19-
from ..query import Query, wi
19+
from ..query import Query, wi, wo
2020
from ..units import Units
2121

2222
logger = logging.getLogger(__name__)
@@ -245,8 +245,8 @@ def _indices(self, config, data_axes, ancillary_mask, kwargs):
245245
tuples of domain axis identifier combinations, each
246246
of which has of a `Data` object containing the
247247
ancillary mask to apply to those domain axes
248-
immediately after the subspace has been created
249-
by the ``'indices'``. This dictionary will always be
248+
immediately after the subspace has been created by
249+
the ``'indices'``. This dictionary will always be
250250
empty if the *ancillary_mask* parameter is False.
251251
252252
"""
@@ -456,6 +456,30 @@ def _indices(self, config, data_axes, ancillary_mask, kwargs):
456456
if debug:
457457
logger.debug(" 1-d CASE 2:") # pragma: no cover
458458

459+
arg0, arg1 = value.value
460+
if arg0 > arg1:
461+
# Query has swapped operands (i.e. arg0 >
462+
# arg1) => Create a new equivalant Query
463+
# that has arg0 < arg1, for a new
464+
# arg1. E.g. for a period of 360,
465+
# cf.wi(355, 5) is transformed to
466+
# cf.wi(355, 365).
467+
#
468+
# This is done (effectively) by repeatedly
469+
# adding the cyclic period to arg1 until
470+
# it is greater than arg0, taking into
471+
# account any units that have been set.
472+
period = item.period()
473+
value = value.copy()
474+
value.set_condition_units(period.Units)
475+
arg0, arg1 = value.value
476+
n = ((arg0 - arg1) / period).ceil()
477+
arg1 = arg1 + n * period
478+
if value.operator == "wi":
479+
value = wi(arg0, arg1)
480+
else:
481+
value = wo(arg0, arg1)
482+
459483
size = item.size
460484
if item.increasing:
461485
anchor = value.value[0]
@@ -2020,12 +2044,18 @@ def cyclic(
20202044

20212045
# Check for axes that are currently marked as non-cyclic,
20222046
# but are in fact cyclic.
2023-
if (
2024-
len(cyclic) < len(self.domain_axes(todict=True))
2025-
and self.autocyclic()
2026-
):
2027-
cyclic.update(self._cyclic)
2028-
self._cyclic = cyclic
2047+
#
2048+
# Note: We have to do a "dry run" on the 'autocyclic' call
2049+
# in the if test in order to prevent corrupting
2050+
# self._cyclic in the case that an axis tested by
2051+
# autocyclic is already marked as cylcic, but
2052+
# nonetheless autocyclic returns False (sounds
2053+
# niche, but this really happens!).
2054+
if len(cyclic) < len(
2055+
self.domain_axes(todict=True)
2056+
) and self.autocyclic(config={"dry_run": True}):
2057+
self.autocyclic()
2058+
cyclic = self._cyclic.copy()
20292059

20302060
return cyclic
20312061

cf/mixin/propertiesdatabounds.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@
1818
)
1919
from ..functions import equivalent as cf_equivalent
2020
from ..functions import inspect as cf_inspect
21-
from ..functions import parse_indices
21+
from ..functions import (
22+
parse_indices,
23+
)
2224
from ..functions import size as cf_size
2325
from ..query import Query
2426
from ..units import Units

cf/test/test_Field.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1290,6 +1290,14 @@ def test_Field_indices(self):
12901290
a[..., [0, 1, 6, 7, 8]] = np.ma.masked
12911291
self.assertTrue(cf.functions._numpy_allclose(g.array, a), g.array)
12921292

1293+
# Cyclic cf.wi with swapped operands (increasing coords)
1294+
for q in (cf.wi(315, 45), cf.wi(-45, -675)):
1295+
indices = f.indices(grid_longitude=q)
1296+
g = f[indices]
1297+
self.assertEqual(g.shape, (1, 10, 3))
1298+
x = g.dimension_coordinate("X").array
1299+
self.assertTrue((x == [-40, 0, 40]).all())
1300+
12931301
# wi (decreasing)
12941302
f.flip("X", inplace=True)
12951303

@@ -1346,6 +1354,14 @@ def test_Field_indices(self):
13461354
(x == [0, 40, 80, 120, 160, 200, 240, 280, 320][::-1]).all()
13471355
)
13481356

1357+
# Cyclic cf.wi with swapped operands (decreasing coords)
1358+
for q in (cf.wi(315, 45), cf.wi(-45, -675)):
1359+
indices = f.indices(grid_longitude=q)
1360+
g = f[indices]
1361+
self.assertEqual(g.shape, (1, 10, 3))
1362+
x = g.dimension_coordinate("X").array
1363+
self.assertTrue((x == [40, 0, -40]).all())
1364+
13491365
# wo
13501366
f = f0.copy()
13511367

@@ -3055,6 +3071,13 @@ def test_Field_cyclic_iscyclic(self):
30553071
f2.cyclic("X", iscyclic=False)
30563072
self.assertTrue(f2.iscyclic("X"))
30573073

3074+
# In the case that autocyclic thinks the axis is not cyclic,
3075+
# check that calling iscylcic (which calls cyclic) doesn't
3076+
# change the cyclicity!
3077+
f2.dimension_coordinate("X").del_bounds()
3078+
self.assertTrue(f2.iscyclic("X"))
3079+
self.assertTrue(f2.iscyclic("X"))
3080+
30583081
def test_Field_is_discrete_axis(self):
30593082
"""Test the `is_discrete_axis` Field method."""
30603083
# No discrete axes

0 commit comments

Comments
 (0)