diff --git a/cf/field.py b/cf/field.py index b576981c5b..9ef41377b7 100644 --- a/cf/field.py +++ b/cf/field.py @@ -3006,48 +3006,6 @@ def close(self): removed_at="5.0.0", ) # pragma: no cover - def iscyclic(self, *identity, **filter_kwargs): - """Returns True if the specified axis is cyclic. - - .. versionadded:: 1.0 - - .. seealso:: `axis`, `cyclic`, `period`, `domain_axis` - - :Parameters: - - identity, filter_kwargs: optional - Select the unique domain axis construct returned by - ``f.domain_axis(*identity, **filter_kwargs)``. See - `domain_axis` for details. - - :Returns: - - `bool` - True if the selected axis is cyclic, otherwise False. - - **Examples** - - >>> f.iscyclic('X') - True - >>> f.iscyclic('latitude') - False - - >>> x = f.iscyclic('long_name=Latitude') - >>> x = f.iscyclic('dimensioncoordinate1') - >>> x = f.iscyclic('domainaxis2') - >>> x = f.iscyclic('key%domainaxis2') - >>> x = f.iscyclic('ncdim%y') - >>> x = f.iscyclic(2) - - """ - axis = self.domain_axis( - *identity, key=True, default=None, **filter_kwargs - ) - if axis is None: - raise ValueError("Can't identify unique domain axis") - - return axis in self.cyclic() - def weights( self, weights=True, diff --git a/cf/mixin/fielddomain.py b/cf/mixin/fielddomain.py index ac7cf16ca0..ac69a970ed 100644 --- a/cf/mixin/fielddomain.py +++ b/cf/mixin/fielddomain.py @@ -1345,10 +1345,14 @@ def autocyclic(self, key=None, coord=None, verbose=None, config={}): # Don't do anything return + # On a dry run, return as usual, but don't update _cyclic. + dry_run = config.get("dry_run") + if "cyclic" in config: if not config["cyclic"]: - if not noop: + if not dry_run: self.cyclic(key, iscyclic=False, config=config) + return False else: period = coord.period() @@ -1357,7 +1361,11 @@ def autocyclic(self, key=None, coord=None, verbose=None, config={}): else: period = config.get("period") - self.cyclic(key, iscyclic=True, period=period, config=config) + if not dry_run: + self.cyclic( + key, iscyclic=True, period=period, config=config + ) + return True if coord is None: @@ -1366,14 +1374,17 @@ def autocyclic(self, key=None, coord=None, verbose=None, config={}): ) if coord is None: return False + elif "X" in config: if not config["X"]: - if not noop: + if not dry_run: self.cyclic(key, iscyclic=False, config=config) + return False elif not coord.X: - if not noop: + if not dry_run: self.cyclic(key, iscyclic=False, config=config) + return False bounds_range = config.get("bounds_range") @@ -1382,14 +1393,16 @@ def autocyclic(self, key=None, coord=None, verbose=None, config={}): else: bounds = coord.get_bounds(None) if bounds is None: - if not noop: + if not dry_run: self.cyclic(key, iscyclic=False, config=config) + return False data = bounds.get_data(None, _fill_value=False) if data is None: - if not noop: + if not dry_run: self.cyclic(key, iscyclic=False, config=config) + return False bounds_units = bounds.Units @@ -1411,7 +1424,9 @@ def autocyclic(self, key=None, coord=None, verbose=None, config={}): elif bounds_units.equivalent(_units_degrees): period = Data(360.0, units="degrees") else: - self.cyclic(key, iscyclic=False, config=config) + if not dry_run: + self.cyclic(key, iscyclic=False, config=config) + return False period.Units = bounds_units @@ -1422,14 +1437,16 @@ def autocyclic(self, key=None, coord=None, verbose=None, config={}): bounds_range = None if bounds_range is None or bounds_range != period: - if not noop: + if not dry_run: self.cyclic(key, iscyclic=False, config=config) + return False config = config.copy() config["axis"] = self.get_data_axes(key, default=(None,))[0] - self.cyclic(key, iscyclic=True, period=period, config=config) + if not dry_run: + self.cyclic(key, iscyclic=True, period=period, config=config) return True @@ -1885,6 +1902,7 @@ def cyclic( coordinates, otherwise it is assumed to have the same units as the dimension coordinates. + config: `dict`, optional Additional parameters for optimising the operation. See the code for details. @@ -1923,7 +1941,18 @@ def cyclic( cyclic = self._cyclic if not identity and not filter_kwargs: - return cyclic.copy() + cyclic = cyclic.copy() + + # Check for axes that are currently marked as non-cyclic, + # but are in fact cyclic. + if ( + len(cyclic) < len(self.domain_axes(todict=True)) + and self.autocyclic() + ): + cyclic.update(self._cyclic) + self._cyclic = cyclic + + return cyclic axis = config.get("axis") if axis is None: diff --git a/cf/test/test_Domain.py b/cf/test/test_Domain.py index 63e639df4a..c42d806574 100644 --- a/cf/test/test_Domain.py +++ b/cf/test/test_Domain.py @@ -437,14 +437,15 @@ def test_Domain_cyclic_iscyclic(self): d2 = f2.domain # Getting - self.assertEqual(d1.cyclic(), f1.cyclic()) self.assertEqual(d1.cyclic(), set()) + self.assertEqual(d1.cyclic(), f1.cyclic()) self.assertFalse(d1.iscyclic("X")) self.assertFalse(d1.iscyclic("Y")) self.assertFalse(d1.iscyclic("Z")) self.assertFalse(d1.iscyclic("T")) - self.assertEqual(d2.cyclic(), f2.cyclic()) + self.assertEqual(d2.cyclic(), set(("domainaxis2",))) + self.assertEqual(d2.cyclic(), f2.cyclic()) self.assertTrue(d2.iscyclic("X")) self.assertFalse(d2.iscyclic("Y")) self.assertFalse(d2.iscyclic("Z")) @@ -452,9 +453,37 @@ def test_Domain_cyclic_iscyclic(self): # Setting self.assertEqual(d2.cyclic("X", iscyclic=False), set(("domainaxis2",))) - self.assertEqual(d2.cyclic(), set()) - self.assertEqual(d2.cyclic("X", period=360), set()) self.assertEqual(d2.cyclic(), set(("domainaxis2",))) + + d2.cyclic("Y", period=360), set() + self.assertEqual( + d2.cyclic(), + set( + ( + "domainaxis1", + "domainaxis2", + ) + ), + ) + self.assertTrue(d2.iscyclic("Y")) + self.assertEqual( + d2.cyclic("Y", iscyclic=False), + set( + ( + "domainaxis1", + "domainaxis2", + ) + ), + ) + self.assertEqual(d2.cyclic(), set(("domainaxis2",))) + + # Auto setting of cyclicity + self.assertTrue(d2.iscyclic("X")) + d2.cyclic("X", iscyclic=False) + self.assertFalse(d2._cyclic) + self.assertEqual(d2.cyclic(), set(("domainaxis2",))) + + d2.cyclic("X", iscyclic=False) self.assertTrue(d2.iscyclic("X")) diff --git a/cf/test/test_Field.py b/cf/test/test_Field.py index c1122ac338..03f87c7a46 100644 --- a/cf/test/test_Field.py +++ b/cf/test/test_Field.py @@ -2970,9 +2970,37 @@ def test_Field_cyclic_iscyclic(self): # Setting self.assertEqual(f2.cyclic("X", iscyclic=False), set(("domainaxis2",))) - self.assertEqual(f2.cyclic(), set()) - self.assertEqual(f2.cyclic("X", period=360), set()) self.assertEqual(f2.cyclic(), set(("domainaxis2",))) + + f2.cyclic("Y", period=360), set() + self.assertEqual( + f2.cyclic(), + set( + ( + "domainaxis1", + "domainaxis2", + ) + ), + ) + self.assertTrue(f2.iscyclic("Y")) + self.assertEqual( + f2.cyclic("Y", iscyclic=False), + set( + ( + "domainaxis1", + "domainaxis2", + ) + ), + ) + self.assertEqual(f2.cyclic(), set(("domainaxis2",))) + + # Auto setting of cyclicity + self.assertTrue(f2.iscyclic("X")) + f2.cyclic("X", iscyclic=False) + self.assertFalse(f2._cyclic) + self.assertEqual(f2.cyclic(), set(("domainaxis2",))) + + f2.cyclic("X", iscyclic=False) self.assertTrue(f2.iscyclic("X")) def test_Field_is_discrete_axis(self):