Skip to content

Commit 1970178

Browse files
committed
Add window function to moving window
Expose window functionality of underlying buffer. Only datetime indices are supported so far. Signed-off-by: cwasicki <[email protected]>
1 parent 61ddef1 commit 1970178

File tree

2 files changed

+51
-1
lines changed

2 files changed

+51
-1
lines changed

src/frequenz/sdk/timeseries/_moving_window.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,34 @@ def capacity(self) -> int:
241241
"""
242242
return self._buffer.maxlen
243243

244+
def window(
245+
self,
246+
start: datetime,
247+
end: datetime,
248+
*,
249+
force_copy: bool = True,
250+
) -> ArrayLike:
251+
"""
252+
Return an array containing the samples in the given time interval.
253+
254+
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.
257+
force_copy: If `True`, the returned array is a copy of the underlying
258+
data. Otherwise, if possible, a view of the underlying data is
259+
returned.
260+
261+
Returns:
262+
An array containing the samples in the given time interval.
263+
264+
Raises:
265+
IndexError: if `start` or `end` are not datetime objects.
266+
"""
267+
if not isinstance(start, datetime) or not isinstance(end, datetime):
268+
raise IndexError("Only datetime objects are supported as start and end.")
269+
270+
return self._buffer.window(start, end, force_copy=force_copy)
271+
244272
async def _run_impl(self) -> None:
245273
"""Awaits samples from the receiver and updates the underlying ring buffer.
246274

tests/timeseries/test_moving_window.py

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,18 @@ def init_moving_window(
6666
return window, lm_tx
6767

6868

69+
def dt(i: int) -> datetime: # pylint: disable=invalid-name
70+
"""Create datetime objects from indices.
71+
72+
Args:
73+
i: Index to create datetime from.
74+
75+
Returns:
76+
Datetime object.
77+
"""
78+
return datetime.fromtimestamp(i, tz=timezone.utc)
79+
80+
6981
async def test_access_window_by_index() -> None:
7082
"""Test indexing a window by integer index."""
7183
window, sender = init_moving_window(timedelta(seconds=1))
@@ -92,7 +104,8 @@ async def test_access_window_by_int_slice() -> None:
92104
async with window:
93105
await push_logical_meter_data(sender, range(0, 5))
94106
assert np.array_equal(window[3:5], np.array([3.0, 4.0]))
95-
107+
with pytest.raises(IndexError):
108+
window.window(3, 5) # type: ignore
96109
data = [1, 2, 2.5, 1, 1, 1, 2, 2, 1, 1, 1, 1, 1, 1]
97110
await push_logical_meter_data(sender, data)
98111
assert np.array_equal(window[5:14], np.array(data[5:14]))
@@ -106,6 +119,15 @@ async def test_access_window_by_ts_slice() -> None:
106119
time_start = UNIX_EPOCH + timedelta(seconds=3)
107120
time_end = time_start + timedelta(seconds=2)
108121
assert np.array_equal(window[time_start:time_end], np.array([3.0, 4.0])) # type: ignore
122+
assert np.array_equal(window.window(dt(3), dt(5)), np.array([3.0, 4.0]))
123+
assert np.array_equal(window.window(dt(3), dt(3)), np.array([]))
124+
# Window only supports slicing with ascending indices within allowed range
125+
with pytest.raises(IndexError):
126+
window.window(dt(3), dt(1))
127+
with pytest.raises(IndexError):
128+
window.window(dt(3), dt(6))
129+
with pytest.raises(IndexError):
130+
window.window(dt(-1), dt(5))
109131

110132

111133
async def test_access_empty_window() -> None:

0 commit comments

Comments
 (0)