Repository: creare-com/podpac
Issue: Interpolation selector uses full coordinate range with negative step

When interpolating, if the step of the source coordinates is larger than the step of the requested coordinates, the request dataset is evaluated at all source coordinates. However, if the request coordinates have a negative step that is larger in magnitude than the source coordinate step, the request dataset is unnecessarily evaluated at all source coordinates rather than at a subset, impacting performance. 

https://github.com/creare-com/podpac/blob/develop/podpac/core/interpolation/selector.py#L138

Changed files:
podpac/core/interpolation/selector.py, podpac/core/interpolation/test/test_selector.py, podpac/core/interpolation/nearest_neighbor_interpolator.py

diff --git a/podpac/core/interpolation/nearest_neighbor_interpolator.py b/podpac/core/interpolation/nearest_neighbor_interpolator.py
index b02dcf2a..1ba29a2d 100644
--- a/podpac/core/interpolation/nearest_neighbor_interpolator.py
+++ b/podpac/core/interpolation/nearest_neighbor_interpolator.py
@@ -283,7 +283,7 @@ class NearestNeighbor(Interpolator):
                 step = self._time_to_float(source.step, source, request)
             else:
                 step = source.step
-            rindex[np.abs(index - rindex) * step > tol] = -1
+            rindex[np.abs(index - rindex) * np.abs(step) > tol] = -1
 
         return rindex
 
diff --git a/podpac/core/interpolation/selector.py b/podpac/core/interpolation/selector.py
index c6ea006e..09c5d68e 100644
--- a/podpac/core/interpolation/selector.py
+++ b/podpac/core/interpolation/selector.py
@@ -98,11 +98,11 @@ class Selector(tl.HasTraits):
         coords = []
         coords_inds = []
         for coord1d in source_coords._coords.values():
-            c, ci = self._select1d(coord1d, request_coords, index_type)
+            ci = self._select1d(coord1d, request_coords, index_type)
             ci = np.sort(np.unique(ci))
             if index_type == "slice":
                 ci = _index2slice(ci)
-            c = c[ci]
+            c = coord1d[ci]
             coords.append(c)
             coords_inds.append(ci)
         coords = Coordinates(coords)
@@ -122,7 +122,7 @@ class Selector(tl.HasTraits):
         # else:
         # _logger.info("Coordinates are not subselected for source {} with request {}".format(source, request))
         # return source, slice(0, None)
-        return source, ci
+        return ci
 
     def _merge_indices(self, indices, source_dims, request_dims):
         # For numpy to broadcast correctly, we have to reshape each of the indices
@@ -135,7 +135,7 @@ class Selector(tl.HasTraits):
 
     def _select_uniform(self, source, request, index_type):
         crds = request[source.name]
-        if crds.is_uniform and crds.step < source.step and not request.is_stacked(source.name):
+        if crds.is_uniform and abs(crds.step) < abs(source.step) and not request.is_stacked(source.name):
             return np.arange(source.size)
 
         index = (crds.coordinates - source.start) / source.step
diff --git a/podpac/core/interpolation/test/test_selector.py b/podpac/core/interpolation/test/test_selector.py
index 6ad19997..4ebb380a 100644
--- a/podpac/core/interpolation/test/test_selector.py
+++ b/podpac/core/interpolation/test/test_selector.py
@@ -149,6 +149,81 @@ class TestSelector(object):
                     ),
                 )
 
+    def test_bilinear_selector_negative_step(self):
+        selector = Selector("bilinear")
+        request1 = Coordinates([clinspace(-0.5, -1, 11)], ["lat"])
+        request2 = Coordinates([clinspace(-1, -0.5, 11)], ["lat"])
+        source1 = Coordinates([clinspace(-2, 0, 100)], ["lat"])
+        source2 = Coordinates([clinspace(0, -2, 100)], ["lat"])
+        c11, ci11 = selector.select(source1, request1)
+        assert len(c11["lat"]) == 22
+        assert len(ci11[0]) == 22
+
+        c12, ci12 = selector.select(source1, request2)
+        assert len(c12["lat"]) == 22
+        assert len(ci12[0]) == 22
+
+        c21, ci21 = selector.select(source2, request1)
+        assert len(c21["lat"]) == 22
+        assert len(ci21[0]) == 22
+
+        c22, ci22 = selector.select(source2, request2)
+        assert len(c22["lat"]) == 22
+        assert len(ci22[0]) == 22
+
+        np.testing.assert_equal(ci11[0], ci12[0])
+        np.testing.assert_equal(ci21[0], ci22[0])
+
+    def test_nearest_selector_negative_step(self):
+        selector = Selector("nearest")
+        request1 = Coordinates([clinspace(-0.5, -1, 11)], ["lat"])
+        request2 = Coordinates([clinspace(-1, -0.5, 11)], ["lat"])
+        source1 = Coordinates([clinspace(-2, 0, 100)], ["lat"])
+        source2 = Coordinates([clinspace(0, -2, 100)], ["lat"])
+        c11, ci11 = selector.select(source1, request1)
+        assert len(c11["lat"]) == 11
+        assert len(ci11[0]) == 11
+
+        c12, ci12 = selector.select(source1, request2)
+        assert len(c12["lat"]) == 11
+        assert len(ci12[0]) == 11
+
+        c21, ci21 = selector.select(source2, request1)
+        assert len(c21["lat"]) == 11
+        assert len(ci21[0]) == 11
+
+        c22, ci22 = selector.select(source2, request2)
+        assert len(c22["lat"]) == 11
+        assert len(ci22[0]) == 11
+
+        np.testing.assert_equal(ci11[0], ci12[0])
+        np.testing.assert_equal(ci21[0], ci22[0])
+
+    def test_nearest_selector_negative_time_step(self):
+        selector = Selector("nearest")
+        request1 = Coordinates([clinspace("2020-01-01", "2020-01-11", 11)], ["time"])
+        request2 = Coordinates([clinspace("2020-01-11", "2020-01-01", 11)], ["time"])
+        source1 = Coordinates([clinspace("2020-01-22T00", "2020-01-01T00", 126)], ["time"])
+        source2 = Coordinates([clinspace("2020-01-01T00", "2020-01-22T00", 126)], ["time"])
+        c11, ci11 = selector.select(source1, request1)
+        assert len(c11["time"]) == 11
+        assert len(ci11[0]) == 11
+
+        c12, ci12 = selector.select(source1, request2)
+        assert len(c12["time"]) == 11
+        assert len(ci12[0]) == 11
+
+        c21, ci21 = selector.select(source2, request1)
+        assert len(c21["time"]) == 11
+        assert len(ci21[0]) == 11
+
+        c22, ci22 = selector.select(source2, request2)
+        assert len(c22["time"]) == 11
+        assert len(ci22[0]) == 11
+
+        np.testing.assert_equal(ci11[0], ci12[0])
+        np.testing.assert_equal(ci21[0], ci22[0])
+
     def test_nn_selector(self):
         selector = Selector("nearest")
         for request in self.coords: