Skip to content

Commit

Permalink
Merge pull request #344 from creare-com/feature/time-select-precision
Browse files Browse the repository at this point in the history
FIX: Fixing time selection when precision is different
  • Loading branch information
jmilloy authored Dec 2, 2019
2 parents b6a2237 + 6b22224 commit a74f633
Show file tree
Hide file tree
Showing 5 changed files with 91 additions and 7 deletions.
26 changes: 23 additions & 3 deletions podpac/core/coordinates/coordinates1d.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

from podpac.core.utils import ArrayTrait
from podpac.core.coordinates.utils import make_coord_value, make_coord_delta, make_coord_delta_array
from podpac.core.coordinates.utils import add_coord, divide_delta
from podpac.core.coordinates.utils import add_coord, divide_delta, lower_precision_time_bounds
from podpac.core.coordinates.utils import Dimension, CoordinateType
from podpac.core.coordinates.base_coordinates import BaseCoordinates

Expand Down Expand Up @@ -350,19 +350,39 @@ def select(self, bounds, return_indices=False, outer=False):
index or slice for the selected coordinates (only if return_indices=True)
"""

# empty case
if self.dtype is None:
return self._select_empty(return_indices)

if isinstance(bounds, dict):
bounds = bounds.get(self.name)
if bounds is None:
return self._select_full(return_indices)

bounds = make_coord_value(bounds[0]), make_coord_value(bounds[1])

# check type
if not isinstance(bounds[0], self.dtype):
raise TypeError(
"Input bounds do match the coordinates dtype (%s != %s)" % (type(self.bounds[0]), self.dtype)
)
if not isinstance(bounds[1], self.dtype):
raise TypeError(
"Input bounds do match the coordinates dtype (%s != %s)" % (type(self.bounds[1]), self.dtype)
)

my_bounds = self.area_bounds.copy()

# If the bounds are of instance datetime64, then the comparison should happen at the lowest precision
if self.dtype == np.datetime64:
my_bounds, bounds = lower_precision_time_bounds(my_bounds, bounds, outer)

# full
if self.bounds[0] >= bounds[0] and self.bounds[1] <= bounds[1]:
if my_bounds[0] >= bounds[0] and my_bounds[1] <= bounds[1]:
return self._select_full(return_indices)

# none
if self.area_bounds[0] > bounds[1] or self.area_bounds[1] < bounds[0]:
if my_bounds[0] > bounds[1] or my_bounds[1] < bounds[0]:
return self._select_empty(return_indices)

# partial, implemented in child classes
Expand Down
10 changes: 10 additions & 0 deletions podpac/core/coordinates/test/test_array_coordinates1d.py
Original file line number Diff line number Diff line change
Expand Up @@ -1039,6 +1039,16 @@ def test_select_time(self):
s = c.select({"time": [np.datetime64("2018-01-03"), "2018-02-06"]})
assert_equal(s.coordinates, np.array(["2018-01-03", "2018-01-04"]).astype(np.datetime64))

def test_select_time_variable_precision(self):
c = ArrayCoordinates1d(["2012-05-19"], name="time")
c2 = ArrayCoordinates1d(["2012-05-19T12:00:00"], name="time")
s = c.select(c2.bounds, outer=True)
s1 = c.select(c2.bounds, outer=False)
s2 = c2.select(c.bounds)
assert s.size == 1
assert s1.size == 0
assert s2.size == 1

def test_select_dtype(self):
c = ArrayCoordinates1d([20.0, 40.0, 60.0, 10.0, 90.0, 50.0], name="lat")
with pytest.raises(TypeError):
Expand Down
10 changes: 10 additions & 0 deletions podpac/core/coordinates/test/test_uniform_coordinates1d.py
Original file line number Diff line number Diff line change
Expand Up @@ -1180,3 +1180,13 @@ def test_select_outer(self):
assert isinstance(s, ArrayCoordinates1d)
assert_equal(s.coordinates, [])
assert_equal(c.coordinates[I], [])

def test_select_time_variable_precision(self):
c = UniformCoordinates1d("2012-05-19", "2012-05-20", "1,D", name="time")
c2 = UniformCoordinates1d("2012-05-20T12:00:00", "2012-05-21T12:00:00", "1,D", name="time")
s = c.select(c2.bounds, outer=True)
s1 = c.select(c2.bounds, outer=False)
s2 = c2.select(c.bounds)
assert s.size == 1
assert s1.size == 0
assert s2.size == 1
14 changes: 10 additions & 4 deletions podpac/core/coordinates/uniform_coordinates1d.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from collections import OrderedDict

from podpac.core.coordinates.utils import make_coord_value, make_coord_delta, add_coord, divide_delta
from podpac.core.coordinates.utils import lower_precision_time_bounds
from podpac.core.coordinates.coordinates1d import Coordinates1d
from podpac.core.coordinates.array_coordinates1d import ArrayCoordinates1d

Expand Down Expand Up @@ -384,12 +385,17 @@ def copy(self):

def _select(self, bounds, return_indices, outer):
# TODO is there an easier way to do this with the new outer flag?
my_bounds = self.bounds.copy()

lo = max(bounds[0], self.bounds[0])
hi = min(bounds[1], self.bounds[1])
# If the bounds are of instance datetime64, then the comparison should happen at the lowest precision
if self.dtype == np.datetime64:
my_bounds, bounds = lower_precision_time_bounds(my_bounds, bounds, outer)

fmin = (lo - self.bounds[0]) / np.abs(self.step)
fmax = (hi - self.bounds[0]) / np.abs(self.step)
lo = max(bounds[0], my_bounds[0])
hi = min(bounds[1], my_bounds[1])

fmin = (lo - my_bounds[0]) / np.abs(self.step)
fmax = (hi - my_bounds[0]) / np.abs(self.step)
imin = int(np.ceil(fmin))
imax = int(np.floor(fmax))

Expand Down
38 changes: 38 additions & 0 deletions podpac/core/coordinates/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -529,3 +529,41 @@ def rem_vunits(crs):
if "+vunits" in crs:
crs = re.sub(r"\+vunits=[a-z\-]+", "", crs)
return crs


def lower_precision_time_bounds(my_bounds, other_bounds, outer):
"""
When given two bounds of np.datetime64, this function will convert both bounds to the lower-precision (in terms of
time unit) numpy datetime4 object if outer==True, otherwise only my_bounds will be converted.
Parameters
-----------
my_bounds : List(np.datetime64)
The bounds of the native coordinates of the dataset
other_bounds : List(np.datetime64)
The bounds used for the selection
outer : bool
When the other_bounds are higher precision than the input_bounds, only convert these IF outer=True
Returns
--------
my_bounds : List(np.datetime64)
The bounds of the native coordinates of the dataset at the new precision
other_bounds : List(np.datetime64)
The bounds used for the selection at the new precision, if outer == True, otherwise return original coordinates
"""
if not isinstance(other_bounds[0], np.datetime64) or not isinstance(other_bounds[1], np.datetime64):
raise TypeError("Input bounds should be of type np.datetime64 when selecting data from:", str(my_bounds))

if not isinstance(my_bounds[0], np.datetime64) or not isinstance(my_bounds[1], np.datetime64):
raise TypeError("Native bounds should be of type np.datetime64 when selecting data using:", str(other_bounds))

mine = np.timedelta64(1, my_bounds[0].dtype.name.replace("datetime64", "")[1:-1])
your = np.timedelta64(1, other_bounds[0].dtype.name.replace("datetime64", "")[1:-1])

if mine > your and outer:
other_bounds = [b.astype(my_bounds[0].dtype) for b in other_bounds]
else:
my_bounds = [b.astype(other_bounds[0].dtype) for b in my_bounds]

return my_bounds, other_bounds

0 comments on commit a74f633

Please sign in to comment.