From c17f4561f826a1934910d0370b1076f53dbe17ca Mon Sep 17 00:00:00 2001 From: Robert David Grant Date: Mon, 26 May 2014 18:59:22 -0500 Subject: [PATCH 1/6] Add ellipsis support to sanitize_indices. --- distarray/metadata_utils.py | 32 ++++++++++++++++++++++---- distarray/tests/test_metadata_utils.py | 19 +++++++++++++++ 2 files changed, 46 insertions(+), 5 deletions(-) diff --git a/distarray/metadata_utils.py b/distarray/metadata_utils.py index d855b498..1c02ffcf 100644 --- a/distarray/metadata_utils.py +++ b/distarray/metadata_utils.py @@ -275,8 +275,9 @@ def positivify(index, size): def sanitize_indices(indices, ndim=None, shape=None): """Classify and sanitize `indices`. - * Wrap Integral or slice indices into tuples - * Classify as 'value' or 'view' + * Wrap naked Integral, slice, or Ellipsis indices into tuples + * Classify result as 'value' or 'view' + * Expand `Ellipsis` objects to slices * If the length of the tuple-ized `indices` is < ndim (and it's provided), add slice(None)'s to indices until `indices` is ndim long * If `shape` is provided, call `positivify` on the indices @@ -290,21 +291,42 @@ def sanitize_indices(indices, ndim=None, shape=None): Returns ------- - 2-tuple of (str, ndim-tuple of slices and Integral values) + 2-tuple of (str, n-tuple of slices and Integral values) """ if isinstance(indices, Integral): rtype, sanitized = 'value', (indices,) - elif isinstance(indices, slice): + elif isinstance(indices, slice) or indices is Ellipsis: rtype, sanitized = 'view', (indices,) elif all(isinstance(i, Integral) for i in indices): rtype, sanitized = 'value', indices - elif all(isinstance(i, Integral) or isinstance(i, slice) for i in indices): + elif all(isinstance(i, Integral) + or isinstance(i, slice) + or i is Ellipsis for i in indices): rtype, sanitized = 'view', indices else: msg = ("Index must be an Integral, a slice, or a sequence of " "Integrals and slices.") raise TypeError(msg) + if Ellipsis in sanitized: + if ndim is None: + raise RuntimeError("Can't call `sanitize_indices` on Ellipsis " + "without providing `ndim`.") + # expand first Ellipsis + diff = ndim - (len(sanitized) - 1) + filler = (slice(None),) * diff + epos = sanitized.index(Ellipsis) + sanitized = sanitized[:epos] + filler + sanitized[epos+1:] + + # remaining Ellipsis objects are just converted to slices + def replace_ellipsis(idx): + if idx is Ellipsis: + return slice(None) + else: + return idx + sanitized = tuple(replace_ellipsis(i) for i in sanitized) + + if ndim is not None: diff = ndim - len(sanitized) if diff < 0: diff --git a/distarray/tests/test_metadata_utils.py b/distarray/tests/test_metadata_utils.py index 4feb0508..be5ea0d3 100644 --- a/distarray/tests/test_metadata_utils.py +++ b/distarray/tests/test_metadata_utils.py @@ -119,6 +119,25 @@ def test_too_many_indices(self): with self.assertRaises(IndexError): metadata_utils.sanitize_indices((2, 3, 4), ndim=2) + def test_trailing_ellipsis(self): + ndim = 5 + tag, sanitized = metadata_utils.sanitize_indices((10, Ellipsis), + ndim=ndim) + self.assertEqual(sanitized, (10,) + (slice(None),) * (ndim-1)) + + def test_leading_ellipsis(self): + ndim = 5 + tag, sanitized = metadata_utils.sanitize_indices((Ellipsis, 10), + ndim=ndim) + self.assertEqual(sanitized, (slice(None),) * (ndim-1) + (10,)) + + def test_multiple_ellipsis(self): + ndim = 6 + tag, sanitized = metadata_utils.sanitize_indices((Ellipsis, 10, + Ellipsis), + ndim=ndim) + self.assertEqual(sanitized, (slice(None),) * 4 + (10, slice(None))) + if __name__ == '__main__': unittest.main(verbosity=2) From 178356f054f8786fcaf54b9383b43fc1a6376d7c Mon Sep 17 00:00:00 2001 From: Robert David Grant Date: Mon, 26 May 2014 19:05:15 -0500 Subject: [PATCH 2/6] Add getitem ellipsis tests. --- distarray/dist/tests/test_distarray.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/distarray/dist/tests/test_distarray.py b/distarray/dist/tests/test_distarray.py index e83407c4..d28a5143 100644 --- a/distarray/dist/tests/test_distarray.py +++ b/distarray/dist/tests/test_distarray.py @@ -183,6 +183,32 @@ def test_incomplete_index_block_dist_2d(self): arr = self.dac.fromarray(expected) assert_array_equal(arr[1].toarray(), expected[1]) + def test_trailing_ellipsis(self): + shape = (2, 3, 7, 6) + expected = numpy.random.randint(10, size=shape) + arr = self.dac.fromarray(expected) + assert_array_equal(arr[1, ...].toarray(), expected[1, ...]) + + def test_leading_ellipsis(self): + shape = (2, 3, 7, 6) + expected = numpy.random.randint(10, size=shape) + arr = self.dac.fromarray(expected) + assert_array_equal(arr[..., 3].toarray(), expected[..., 3]) + + def test_multiple_ellipsis(self): + shape = (2, 4, 2, 4, 1, 5) + expected = numpy.random.randint(10, size=shape) + arr = self.dac.fromarray(expected) + assert_array_equal(arr[..., 3, ..., 4].toarray(), + expected[..., 3, ..., 4]) + + def test_vestigial_ellipsis(self): + shape = (1, 2, 3) + expected = numpy.random.randint(10, size=shape) + arr = self.dac.fromarray(expected) + assert_array_equal(arr[0, :, 0, ...].toarray(), + expected[0, :, 0, ...]) + class TestDistArrayCreationFromGlobalDimData(unittest.TestCase): From d34bb170eba929046696cb91096b5d59a97eaa1b Mon Sep 17 00:00:00 2001 From: Robert David Grant Date: Thu, 5 Jun 2014 12:29:51 -0500 Subject: [PATCH 3/6] Fix test in test_distarray. --- distarray/dist/tests/test_distarray.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/distarray/dist/tests/test_distarray.py b/distarray/dist/tests/test_distarray.py index 6369a4f1..c8ec48c5 100644 --- a/distarray/dist/tests/test_distarray.py +++ b/distarray/dist/tests/test_distarray.py @@ -174,26 +174,26 @@ def test_incomplete_index_block_dist_2d(self): def test_trailing_ellipsis(self): shape = (2, 3, 7, 6) expected = numpy.random.randint(10, size=shape) - arr = self.dac.fromarray(expected) + arr = self.context.fromarray(expected) assert_array_equal(arr[1, ...].toarray(), expected[1, ...]) def test_leading_ellipsis(self): shape = (2, 3, 7, 6) expected = numpy.random.randint(10, size=shape) - arr = self.dac.fromarray(expected) + arr = self.context.fromarray(expected) assert_array_equal(arr[..., 3].toarray(), expected[..., 3]) def test_multiple_ellipsis(self): shape = (2, 4, 2, 4, 1, 5) expected = numpy.random.randint(10, size=shape) - arr = self.dac.fromarray(expected) + arr = self.context.fromarray(expected) assert_array_equal(arr[..., 3, ..., 4].toarray(), expected[..., 3, ..., 4]) def test_vestigial_ellipsis(self): shape = (1, 2, 3) expected = numpy.random.randint(10, size=shape) - arr = self.dac.fromarray(expected) + arr = self.context.fromarray(expected) assert_array_equal(arr[0, :, 0, ...].toarray(), expected[0, :, 0, ...]) From 79402420b1198b146025514b876aeaaefb6657e2 Mon Sep 17 00:00:00 2001 From: Robert David Grant Date: Mon, 9 Jun 2014 14:59:42 -0500 Subject: [PATCH 4/6] Add an all-ellipsis test. --- distarray/dist/tests/test_distarray.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/distarray/dist/tests/test_distarray.py b/distarray/dist/tests/test_distarray.py index c8ec48c5..e9782205 100644 --- a/distarray/dist/tests/test_distarray.py +++ b/distarray/dist/tests/test_distarray.py @@ -197,6 +197,13 @@ def test_vestigial_ellipsis(self): assert_array_equal(arr[0, :, 0, ...].toarray(), expected[0, :, 0, ...]) + def test_all_ellipsis(self): + shape = (3, 2, 4) + expected = numpy.random.randint(10, size=shape) + arr = self.context.fromarray(expected) + assert_array_equal(arr[..., ..., ..., ...].toarray(), + expected[..., ..., ..., ...]) + class TestDistArrayCreationFromGlobalDimData(ContextTestCase): From e394e51eccb9cb3905515e4ea592660de186bea8 Mon Sep 17 00:00:00 2001 From: Robert David Grant Date: Mon, 9 Jun 2014 15:00:09 -0500 Subject: [PATCH 5/6] Add a skipped test for 0d arrays with ellipsis. --- distarray/dist/tests/test_distarray.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/distarray/dist/tests/test_distarray.py b/distarray/dist/tests/test_distarray.py index e9782205..b7f2f953 100644 --- a/distarray/dist/tests/test_distarray.py +++ b/distarray/dist/tests/test_distarray.py @@ -204,6 +204,14 @@ def test_all_ellipsis(self): assert_array_equal(arr[..., ..., ..., ...].toarray(), expected[..., ..., ..., ...]) + @unittest.skip("Waiting on 0d-array support.") + def test_0d_ellipsis(self): + shape = () + expected = numpy.random.randint(10, size=shape) + arr = self.context.fromarray(expected) + assert_array_equal(arr[...].toarray(), + expected[...]) + class TestDistArrayCreationFromGlobalDimData(ContextTestCase): From e6f8bdcb8a72166231dc3c8c6992af08d4e03041 Mon Sep 17 00:00:00 2001 From: Robert David Grant Date: Fri, 13 Jun 2014 20:21:58 -0500 Subject: [PATCH 6/6] Fix some things for empty slices. And add tests. --- distarray/dist/distarray.py | 7 +++++-- distarray/dist/maps.py | 17 ++++++++++------- distarray/dist/tests/test_distarray.py | 12 ++++++++++++ 3 files changed, 27 insertions(+), 9 deletions(-) diff --git a/distarray/dist/distarray.py b/distarray/dist/distarray.py index 0e806e28..3e371177 100644 --- a/distarray/dist/distarray.py +++ b/distarray/dist/distarray.py @@ -168,8 +168,11 @@ def raw_getitem(arr, index): # to be run locally def get_slice(arr, index, ddpr, comm): from distarray.local.maps import Distribution - local_distribution = Distribution(comm=comm, - dim_data=ddpr[comm.Get_rank()]) + if len(ddpr) == 0: + dim_data = () + else: + dim_data = ddpr[comm.Get_rank()] + local_distribution = Distribution(comm=comm, dim_data=dim_data) result = arr.global_index.get_slice(index, local_distribution) return proxyize(result) diff --git a/distarray/dist/maps.py b/distarray/dist/maps.py index 2e9a6747..98ded5f9 100644 --- a/distarray/dist/maps.py +++ b/distarray/dist/maps.py @@ -240,7 +240,7 @@ def from_global_dim_dict(cls, glb_dim_dict): self.bounds = list(zip(bounds[:-1], bounds[1:])) self.size = bounds[-1] - self.grid_size = len(bounds) - 1 + self.grid_size = max(len(bounds) - 1, 1) self.comm_padding = int(glb_dim_dict.get('comm_padding', 0)) self.boundary_padding = int(glb_dim_dict.get('boundary_padding', 0)) @@ -292,12 +292,14 @@ def slice_owners(self, idx): return coords if coords != [] else [0] def get_dimdicts(self): - grid_ranks = range(len(self.bounds)) + bounds = self.bounds or [[0, 0]] + grid_ranks = range(len(bounds)) cpadding = self.comm_padding - padding = [[cpadding, cpadding] for i in grid_ranks] - padding[0][0] = self.boundary_padding - padding[-1][-1] = self.boundary_padding - data_tuples = zip(grid_ranks, padding, self.bounds) + padding = [[cpadding, cpadding] for _ in grid_ranks] + if len(padding) > 0: + padding[0][0] = self.boundary_padding + padding[-1][-1] = self.boundary_padding + data_tuples = zip(grid_ranks, padding, bounds) # Build the result out = [] for grid_rank, padding, (start, stop) in data_tuples: @@ -709,7 +711,8 @@ def get_dim_data_per_rank(self): return [] cart_dds = product(*dds) coord_and_dd = [zip(*cdd) for cdd in cart_dds] - rank_and_dd = sorted((self.rank_from_coords[c], dd) for (c, dd) in coord_and_dd) + rank_and_dd = sorted((self.rank_from_coords[c], dd) + for (c, dd) in coord_and_dd) return [dd for (_, dd) in rank_and_dd] def is_compatible(self, o): diff --git a/distarray/dist/tests/test_distarray.py b/distarray/dist/tests/test_distarray.py index 18ceac53..64c9e653 100644 --- a/distarray/dist/tests/test_distarray.py +++ b/distarray/dist/tests/test_distarray.py @@ -171,6 +171,18 @@ def test_incomplete_index_block_dist_2d(self): arr = self.context.fromarray(expected) assert_array_equal(arr[1].toarray(), expected[1]) + def test_empty_slice_1d(self): + shape = (10,) + expected = numpy.random.randint(10, size=shape) + arr = self.context.fromarray(expected) + assert_array_equal(arr[100:].toarray(), expected[100:]) + + def test_empty_slice_2d(self): + shape = (10, 20) + expected = numpy.random.randint(10, size=shape) + arr = self.context.fromarray(expected) + assert_array_equal(arr[100:, 100:].toarray(), expected[100:, 100:]) + def test_trailing_ellipsis(self): shape = (2, 3, 7, 6) expected = numpy.random.randint(10, size=shape)