From 71c92a844c5e39917284938769eb0abc3774770f Mon Sep 17 00:00:00 2001
From: David Hassell <david.hassell@ncas.ac.uk>
Date: Tue, 8 Mar 2022 13:14:05 +0000
Subject: [PATCH 1/4] dask: Data.cyclic

---
 cf/data/data.py      | 51 ++++++++++++++++++++++++++++++++++++++++++--
 cf/test/test_Data.py | 28 ++++++++++++++++++++++++
 2 files changed, 77 insertions(+), 2 deletions(-)

diff --git a/cf/data/data.py b/cf/data/data.py
index 535bcd7011..47dd6ecf4c 100644
--- a/cf/data/data.py
+++ b/cf/data/data.py
@@ -8778,20 +8778,67 @@ def count_masked(self):
         """
         return self._size - self.count()
 
+    @daskified(_DASKIFIED_VERBOSE)
     def cyclic(self, axes=None, iscyclic=True):
-        """Returns or sets the axes of the data array which are cyclic.
+        """Get or set the cyclic axes.
+
+        Some methods treat the first and last elements of a cyclic
+        axis as adjacent and physically connected, such as
+        `convolution_filter`, `__getitem__` and `__setitem__`. Some
+        methods may make a cyclic axis non-cyclis, such as `halo`.
 
         :Parameters:
 
             axes: (sequence of) `int`, optional
+                Select the axes to to have their cyclicity set. By
+                default, or if *axes* is `None` or an empty sequence,
+                no axes are modified.
 
             iscyclic: `bool`
+                Specify whether to make the the axes cyclic or
+                non-cyclic. By default (True), the axes are set as
+                cyclic.
 
         :Returns:
 
             `set`
+                The cyclic axes prior to the change, or the current
+                cylcic axes if no axes are specified.
 
-        **Examples:**
+        **Examples**
+
+        >>> d = cf.Data(np.arange(12).reshape(3, 4))
+        >>> d.cyclic()
+        set()
+        >>> d.cyclic(0)
+        set()
+        >>> d.cyclic()
+        {0}
+        >>> d.cyclic(0, iscyclic=False)
+        {0}
+        >>> d.cyclic()
+        set()
+        >>> d.cyclic([0, 1])
+        set()
+        >>> d.cyclic()
+        {0, 1}
+        >>> d.cyclic([0, 1], iscyclic=False)
+        {0, 1}
+        >>> d.cyclic()
+        set()
+
+        >>> print(d.array)
+        [[ 0  1  2  3]
+         [ 4  5  6  7]
+         [ 8  9 10 11]]
+        >>> d[0, -1:2]
+        Traceback (most recent call last):
+            ...
+        IndexError: Can't take a cyclic slice of a non-cyclic axis
+        >>> d.cyclic(1)
+        set()
+        >>> d[0, -1:2]
+        <CF Data(1, 2): [[3, 0, 1]]>
 
         """
         cyclic_axes = self._cyclic
diff --git a/cf/test/test_Data.py b/cf/test/test_Data.py
index c693d7ca55..c80e2292ab 100644
--- a/cf/test/test_Data.py
+++ b/cf/test/test_Data.py
@@ -3850,6 +3850,34 @@ def test_Data__bool__(self):
         with self.assertRaises(ValueError):
             bool(cf.Data([1, 2]))
 
+    def test_Data_cyclic(self):
+        d = cf.Data(np.arange(12).reshape(3, 4))
+        self.assertEqual(d.cyclic(), set())
+        self.assertEqual(d.cyclic(0), set())
+        self.assertEqual(d.cyclic(), {0})
+        self.assertEqual(d.cyclic(1), {0})
+        self.assertEqual(d.cyclic(), {0, 1})
+        self.assertEqual(d.cyclic(0, iscyclic=False), {0, 1})
+        self.assertEqual(d.cyclic(), {1})
+        self.assertEqual(d.cyclic(1, iscyclic=False), {1})
+        self.assertEqual(d.cyclic(), set())
+        self.assertEqual(d.cyclic([0, 1]), set())
+        self.assertEqual(d.cyclic(), {0, 1})
+        self.assertEqual(d.cyclic([0, 1], iscyclic=False), {0, 1})
+        self.assertEqual(d.cyclic(), set())
+
+        # Invalid axis
+        with self.assertRaises(ValueError):
+            d.cyclic(2)
+
+        # Scalar data
+        d = cf.Data(9)
+        self.assertEqual(d.cyclic(), set())
+
+        # Scalar data invalid axis
+        with self.assertRaises(ValueError):
+            d.cyclic(0)
+
 
 if __name__ == "__main__":
     print("Run date:", datetime.datetime.now())

From 3276f2f4cdd3091805f9b42d7913965ed422f75b Mon Sep 17 00:00:00 2001
From: David Hassell <davidhassell@users.noreply.github.com>
Date: Wed, 16 Mar 2022 17:17:24 +0000
Subject: [PATCH 2/4] Typo

Co-authored-by: Sadie L. Bartholomew <sadie.bartholomew@ncas.ac.uk>
---
 cf/data/data.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/cf/data/data.py b/cf/data/data.py
index 47dd6ecf4c..092009592f 100644
--- a/cf/data/data.py
+++ b/cf/data/data.py
@@ -8785,7 +8785,7 @@ def cyclic(self, axes=None, iscyclic=True):
         Some methods treat the first and last elements of a cyclic
         axis as adjacent and physically connected, such as
         `convolution_filter`, `__getitem__` and `__setitem__`. Some
-        methods may make a cyclic axis non-cyclis, such as `halo`.
+        methods may make a cyclic axis non-cyclic, such as `halo`.
 
         :Parameters:
 

From 28787f45fe5ff13401fb7573f44e631fc0917f94 Mon Sep 17 00:00:00 2001
From: David Hassell <davidhassell@users.noreply.github.com>
Date: Wed, 16 Mar 2022 17:18:22 +0000
Subject: [PATCH 3/4] Typo

Co-authored-by: Sadie L. Bartholomew <sadie.bartholomew@ncas.ac.uk>
---
 cf/data/data.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/cf/data/data.py b/cf/data/data.py
index 092009592f..54f20ea370 100644
--- a/cf/data/data.py
+++ b/cf/data/data.py
@@ -8790,7 +8790,7 @@ def cyclic(self, axes=None, iscyclic=True):
         :Parameters:
 
             axes: (sequence of) `int`, optional
-                Select the axes to to have their cyclicity set. By
+                Select the axes to have their cyclicity set. By
                 default, or if *axes* is `None` or an empty sequence,
                 no axes are modified.
 

From 696554c0e4ddac3325938b47c4599a9cbd707e1c Mon Sep 17 00:00:00 2001
From: David Hassell <davidhassell@users.noreply.github.com>
Date: Wed, 16 Mar 2022 17:18:48 +0000
Subject: [PATCH 4/4] Typo

Co-authored-by: Sadie L. Bartholomew <sadie.bartholomew@ncas.ac.uk>
---
 cf/data/data.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/cf/data/data.py b/cf/data/data.py
index 54f20ea370..f63978b648 100644
--- a/cf/data/data.py
+++ b/cf/data/data.py
@@ -8795,7 +8795,7 @@ def cyclic(self, axes=None, iscyclic=True):
                 no axes are modified.
 
             iscyclic: `bool`
-                Specify whether to make the the axes cyclic or
+                Specify whether to make the axes cyclic or
                 non-cyclic. By default (True), the axes are set as
                 cyclic.
 
