Repository: pymor/pymor

Issue:
Remove slicing restrictions for vectorarray tests
Historically index tests were performed with a set of manually constructed `slice` objects. When rewriting the tests to instead draw slices from a hypothesis strategy it became necessary to add a filter to mimic the properties of the manual examples, or be drowned by test failures. Notably this filter excludes negative step sizes. It's unclear whether the assertions simply are defective or if real failures are among them. And I currently do not have time to look into this further. Anybody interested should check out the `vectorarray_tests_unrestricted_slicing` branch and run the tests there. 

Comments:

Patch:
diff --git a/src/pymor/vectorarrays/block.py b/src/pymor/vectorarrays/block.py
index 3aa9eb29a..9fdb31fbd 100644
--- a/src/pymor/vectorarrays/block.py
+++ b/src/pymor/vectorarrays/block.py
@@ -65,6 +65,9 @@ class BlockVectorArray(VectorArray):
             return 0
 
     def __getitem__(self, ind):
+        if isinstance(ind, Number) and (ind >= len(self) or ind < -len(self)):
+            raise IndexError('VectorArray index out of range')
+        assert self.check_ind(ind)
         return BlockVectorArrayView(self, ind)
 
     def __delitem__(self, ind):
diff --git a/src/pymor/vectorarrays/interface.py b/src/pymor/vectorarrays/interface.py
index f2871ef06..7825d088a 100644
--- a/src/pymor/vectorarrays/interface.py
+++ b/src/pymor/vectorarrays/interface.py
@@ -664,14 +664,22 @@ class VectorArray(BasicObject):
         return len({i if i >= 0 else l+i for i in ind})
 
     def normalize_ind(self, ind):
-        """Normalize given indices such that they are independent of the array length."""
+        """Normalize given indices such that they are independent of the array length.
+
+        Does not check validity of the indices.
+        """
+        l = len(self)
         if type(ind) is slice:
-            return slice(*ind.indices(len(self)))
+            start, stop, step = ind.indices(l)
+            if start == stop:
+                return slice(0, 0, 1)
+            assert start >= 0
+            assert stop >= 0 or (step < 0 and stop >= -1)
+            return slice(start, None if stop == -1 else stop, step)
         elif not hasattr(ind, '__len__'):
-            ind = ind if 0 <= ind else len(self)+ind
+            ind = ind if 0 <= ind else l+ind
             return slice(ind, ind+1)
         else:
-            l = len(self)
             return [i if 0 <= i else l+i for i in ind]
 
     def sub_index(self, ind, ind_ind):
diff --git a/src/pymor/vectorarrays/list.py b/src/pymor/vectorarrays/list.py
index 8537648dd..23bd976bc 100644
--- a/src/pymor/vectorarrays/list.py
+++ b/src/pymor/vectorarrays/list.py
@@ -385,6 +385,7 @@ class ListVectorArray(VectorArray):
     def __getitem__(self, ind):
         if isinstance(ind, Number) and (ind >= len(self) or ind < -len(self)):
             raise IndexError('VectorArray index out of range')
+        assert self.check_ind(ind)
         return ListVectorArrayView(self, ind)
 
     def __delitem__(self, ind):
diff --git a/src/pymor/vectorarrays/mpi.py b/src/pymor/vectorarrays/mpi.py
index 9bb24aa4f..64dbb0c3e 100644
--- a/src/pymor/vectorarrays/mpi.py
+++ b/src/pymor/vectorarrays/mpi.py
@@ -13,6 +13,8 @@ The implementations are based on the event loop provided
 by :mod:`pymor.tools.mpi`.
 """
 
+from numbers import Number
+
 import numpy as np
 
 from pymor.core.pickle import unpicklable
@@ -54,12 +56,16 @@ class MPIVectorArray(VectorArray):
         return mpi.call(mpi.method_call, self.obj_id, '__len__')
 
     def __getitem__(self, ind):
+        if isinstance(ind, Number) and (ind >= len(self) or ind < -len(self)):
+            raise IndexError('VectorArray index out of range')
+        assert self.check_ind(ind)
         U = type(self)(mpi.call(mpi.method_call_manage, self.obj_id, '__getitem__', ind),
                        self.space)
         U.is_view = True
         return U
 
     def __delitem__(self, ind):
+        assert self.check_ind(ind)
         mpi.call(mpi.method_call, self.obj_id, '__delitem__', ind)
 
     def copy(self, deep=False):
diff --git a/src/pymor/vectorarrays/numpy.py b/src/pymor/vectorarrays/numpy.py
index e4e2c0981..67de3d149 100644
--- a/src/pymor/vectorarrays/numpy.py
+++ b/src/pymor/vectorarrays/numpy.py
@@ -70,6 +70,7 @@ class NumpyVectorArray(VectorArray):
     def __getitem__(self, ind):
         if isinstance(ind, Number) and (ind >= self._len or ind < -self._len):
             raise IndexError('VectorArray index out of range')
+        assert self.check_ind(ind)
         return NumpyVectorArrayView(self, ind)
 
     def __delitem__(self, ind):
@@ -451,7 +452,6 @@ class NumpyVectorArrayView(NumpyVectorArray):
     is_view = True
 
     def __init__(self, array, ind):
-        assert array.check_ind(ind)
         self.base = array
         self.ind = array.normalize_ind(ind)
         self.space = array.space


Test patch:
diff --git a/src/pymortests/strategies.py b/src/pymortests/strategies.py
index 07ad9d0b6..0f17d2542 100644
--- a/src/pymortests/strategies.py
+++ b/src/pymortests/strategies.py
@@ -254,20 +254,15 @@ def valid_inds(v, length=None, random_module=None):
         yield []
 
 
-# TODO: remove old assumptions on slice values
-def _filtered_slices(length):
-    def _filter(sl):
-        return sl.step is None or sl.step > 0
-    return hyst.slices(length).filter(_filter)
-
-
 @hyst.composite
 def valid_indices(draw, array_strategy, length=None):
     v = draw(array_strategy)
     ints = hyst.integers(min_value=-len(v), max_value=max(len(v)-1, 0))
     indices = hyst.nothing()
     if length is None:
-        indices = indices | hyst.just([]) | hyst.lists(ints, max_size=2*len(v)) | _filtered_slices(len(v))
+        indices = indices | hyst.just([]) | hyst.lists(ints, max_size=2*len(v))
+    else:
+        indices = indices | hyst.slices(length)
     if len(v) > 0:
         inds = [-len(v), 0, len(v) - 1]
         if len(v) == length:
@@ -317,13 +312,13 @@ def st_valid_inds_of_same_length(draw, v1, v2):
     # `| hynp.integer_array_indices(shape=(LEN_X,))`
     if len1 == len2:
         ints = hyst.integers(min_value=-len1, max_value=max(len1 - 1, 0))
-        slicer = _filtered_slices(len1) | hyst.lists(ints, max_size=len1)
+        slicer = hyst.slices(len1) | hyst.lists(ints, max_size=len1)
         ret = ret | hyst.tuples(hyst.shared(slicer, key="st_valid_inds_of_same_length"),
                                 hyst.shared(slicer, key="st_valid_inds_of_same_length"))
     if len1 > 0 and len2 > 0:
         mlen = min(len1, len2)
         ints = hyst.integers(min_value=-mlen, max_value=max(mlen - 1, 0))
-        slicer = _filtered_slices(mlen) | ints | hyst.lists(ints, max_size=mlen)
+        slicer = hyst.slices(mlen) | ints | hyst.lists(ints, max_size=mlen)
         ret = ret | hyst.tuples(hyst.shared(slicer, key="st_valid_inds_of_same_length_uneven"),
                                 hyst.shared(slicer, key="st_valid_inds_of_same_length_uneven"))
     return draw(ret)
@@ -383,8 +378,8 @@ def st_valid_inds_of_different_length(draw, v1, v2):
 
     len1, len2 = len(v1), len(v2)
     # TODO we should include integer arrays here
-    val1 = _filtered_slices(len1)  # | hynp.integer_array_indices(shape=(len1,))
-    val2 = _filtered_slices(len2)  # | hynp.integer_array_indices(shape=(len1,))
+    val1 = hyst.slices(len1)  # | hynp.integer_array_indices(shape=(len1,))
+    val2 = hyst.slices(len2)  # | hynp.integer_array_indices(shape=(len1,))
     ret = hyst.tuples(val1, val2).filter(_filter)
     return draw(ret)
 
diff --git a/src/pymortests/vectorarray.py b/src/pymortests/vectorarray.py
index 28a0c972c..ad462ce85 100644
--- a/src/pymortests/vectorarray.py
+++ b/src/pymortests/vectorarray.py
@@ -315,6 +315,15 @@ def test_copy_repeated_index(vector_array):
             pass
 
 
+@pyst.given_vector_arrays(index_strategy=pyst.valid_indices)
+def test_normalize_ind(vectors_and_indices):
+    v, ind = vectors_and_indices
+    assert v.check_ind(ind)
+    normalized = v.normalize_ind(ind)
+    assert v.len_ind(normalized) == v.len_ind(ind)
+    assert v.len_ind_unique(normalized) == v.len_ind_unique(ind)
+
+
 @pyst.given_vector_arrays(count=2, index_strategy=pyst.pairs_both_lengths)
 def test_append(vectors_and_indices):
     (v1, v2), (_, ind) = vectors_and_indices