Skip to content

Commit e5edd54

Browse files
committed
Support int indices for window method in moving window
Signed-off-by: cwasicki <[email protected]>
1 parent 3d18972 commit e5edd54

File tree

2 files changed

+49
-12
lines changed

2 files changed

+49
-12
lines changed

src/frequenz/sdk/timeseries/_moving_window.py

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -243,30 +243,26 @@ def capacity(self) -> int:
243243

244244
def window(
245245
self,
246-
start: datetime,
247-
end: datetime,
246+
start: datetime | int | None,
247+
end: datetime | int | None,
248248
*,
249249
force_copy: bool = True,
250250
) -> ArrayLike:
251251
"""
252252
Return an array containing the samples in the given time interval.
253253
254254
Args:
255-
start: The start of the time interval. Only datetime objects are supported.
256-
end: The end of the time interval. Only datetime objects are supported.
255+
start: The start of the time interval. If `None`, the start of the
256+
window is used.
257+
end: The end of the time interval. If `None`, the end of the window
258+
is used.
257259
force_copy: If `True`, the returned array is a copy of the underlying
258260
data. Otherwise, if possible, a view of the underlying data is
259261
returned.
260262
261263
Returns:
262264
An array containing the samples in the given time interval.
263-
264-
Raises:
265-
IndexError: if `start` or `end` are not datetime objects.
266265
"""
267-
if not isinstance(start, datetime) or not isinstance(end, datetime):
268-
raise IndexError("Only datetime objects are supported as start and end.")
269-
270266
return self._buffer.window(start, end, force_copy=force_copy)
271267

272268
async def _run_impl(self) -> None:

tests/timeseries/test_moving_window.py

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,11 +104,52 @@ async def test_access_window_by_int_slice() -> None:
104104
async with window:
105105
await push_logical_meter_data(sender, range(0, 5))
106106
assert np.array_equal(window[3:5], np.array([3.0, 4.0]))
107-
with pytest.raises(IndexError):
108-
window.window(3, 5) # type: ignore
107+
assert np.array_equal(window.window(3, 5), np.array([3.0, 4.0]))
108+
109109
data = [1, 2, 2.5, 1, 1, 1, 2, 2, 1, 1, 1, 1, 1, 1]
110110
await push_logical_meter_data(sender, data)
111111
assert np.array_equal(window[5:14], np.array(data[5:14]))
112+
assert np.array_equal(window.window(5, 14), np.array(data[5:14]))
113+
114+
window, sender = init_moving_window(timedelta(seconds=5))
115+
116+
def test_eq(expected: list[float], start: int | None, end: int | None) -> None:
117+
assert np.allclose(
118+
window.window(start, end), np.array(expected), equal_nan=True
119+
)
120+
121+
async with window:
122+
test_eq([], 0, 1)
123+
124+
# Incomplete window
125+
await push_logical_meter_data(sender, [0.0, 1.0])
126+
test_eq([0.0, 1.0], 0, 2)
127+
test_eq([0.0, 1.0], 0, 9)
128+
test_eq([0.0, 1.0], 0, None)
129+
test_eq([0.0, 1.0], -9, None)
130+
test_eq([0.0, 1.0], None, None)
131+
test_eq([0.0], -2, -1)
132+
test_eq([1.0], -1, None)
133+
134+
# Incomplete window with gap
135+
await push_logical_meter_data(
136+
sender, [3.0], start_ts=UNIX_EPOCH + timedelta(seconds=3)
137+
)
138+
test_eq([0.0, 1.0], 0, 2)
139+
# gap fill not supported yet:
140+
# test_eq([0.0, 1.0, np.nan, 3.0], 0, None)
141+
# test_eq([0.0, 1.0, np.nan, 3.0], -9, None)
142+
# test_eq([np.nan, 3.0], -2, None)
143+
144+
# Complete window
145+
await push_logical_meter_data(sender, [0.0, 1.0, 2.0, 3.0, 4.0])
146+
test_eq([0.0, 1.0], 0, 2)
147+
test_eq([3.0, 4.0], -2, None)
148+
149+
# Complete window with nan
150+
await push_logical_meter_data(sender, [0.0, 1.0, np.nan])
151+
test_eq([0.0, 1.0, np.nan], 0, 3)
152+
test_eq([np.nan, 3.0, 4.0], -3, None)
112153

113154

114155
async def test_access_window_by_ts_slice() -> None:

0 commit comments

Comments
 (0)