1+ import numpy as np
12import sparse
23
34
4- def sliding_win_oneaxis (
5+ def sliding_win_oneaxis_old (
56 s : sparse .SparseArray , nwin : int , axis : int , step : int = 1
67) -> sparse .SparseArray :
78 """
89 Like `ezmsg.util.messages.axisarray.sliding_win_oneaxis` but for sparse arrays.
10+ This approach is about 4x slower than the version that uses coordinate arithmetic below.
911
1012 Args:
1113 s: The input sparse array.
@@ -25,5 +27,97 @@ def sliding_win_oneaxis(
2527 full_slices [: axis + 1 ] + (sl ,) + full_slices [axis + 2 :] for sl in targ_slices
2628 ]
2729 result = sparse .concatenate ([s [_ ] for _ in full_slices ], axis = axis )
28- # TODO: Profile this approach vs modifying coords only.
2930 return result
31+
32+
33+ def sliding_win_oneaxis (
34+ s : sparse .SparseArray , nwin : int , axis : int , step : int = 1
35+ ) -> sparse .SparseArray :
36+ """
37+ Generates a view-like sparse array using a sliding window of specified length along a specified axis.
38+ Sparse analog of an optimized dense as_strided-based implementation with these properties:
39+
40+ - Accepts a single `nwin` and a single `axis`.
41+ - Inserts a new 'win' axis immediately BEFORE the original target axis.
42+ Output shape:
43+ s.shape[:axis] + (W,) + (nwin,) + s.shape[axis+1:]
44+ where W = s.shape[axis] - (nwin - 1).
45+ - If `step > 1`, stepping is applied by slicing along the new windows axis (same observable behavior
46+ as doing `slice_along_axis(result, slice(None, None, step), axis)` in the dense version).
47+
48+ Args:
49+ s: Input sparse array (pydata/sparse COO-compatible).
50+ nwin: Sliding window size (must be > 0).
51+ axis: Axis of `s` along which the window slides (supports negative indexing).
52+ step: Stride between windows. If > 1, applied by slicing the windows axis after construction.
53+
54+ Returns:
55+ A sparse array with a new windows axis inserted before the original axis.
56+
57+ Notes:
58+ - Mirrors the dense function’s known edge case: when nwin == shape[axis] + 1, W becomes 0 and
59+ an empty windows axis is returned.
60+ - Built by coordinate arithmetic; no per-window indexing or concatenation.
61+ """
62+ if - s .ndim <= axis < 0 :
63+ axis = s .ndim + axis
64+ if not (0 <= axis < s .ndim ):
65+ raise ValueError (f"Invalid axis { axis } for array with { s .ndim } dimensions" )
66+ if nwin <= 0 :
67+ raise ValueError ("nwin must be > 0" )
68+ dim = s .shape [axis ]
69+
70+ last_win_start = dim - nwin
71+ win_starts = list (range (0 , last_win_start + 1 , step ))
72+ n_win_out = len (win_starts )
73+ if n_win_out <= 0 :
74+ # Return array with proper shape except empty along windows axis
75+ return sparse .zeros (
76+ s .shape [:axis ] + (0 ,) + (nwin ,) + s .shape [axis + 1 :], dtype = s .dtype
77+ )
78+
79+ coo = s .asformat ("coo" )
80+ coords = coo .coords # shape: (ndim, nnz)
81+ data = coo .data # shape: (nnz,)
82+ ia = coords [axis ] # indices along sliding axis, shape: (nnz,)
83+
84+ # We emit contributions for each offset o in [0, nwin-1].
85+ # For a nonzero at index i, it contributes to window start w = i - o when 0 <= w < W.
86+ out_coords_blocks = []
87+ out_data_blocks = []
88+
89+ # Small speed/memory tweak: reuse dtypes and pre-allocate o-array once per loop.
90+ idx_dtype = coords .dtype
91+
92+ for win_ix , win_start in enumerate (win_starts ):
93+ w = ia - win_start
94+ # Valid window starts are those within [0, nwin]
95+ mask = (w >= 0 ) & (w < nwin )
96+ if not mask .any ():
97+ continue
98+
99+ sel = np .nonzero (mask )[0 ]
100+ w_sel = w [sel ]
101+
102+ # Build new coords with windows axis inserted at `axis` and the original axis
103+ # becoming the next axis with fixed offset value `o`.
104+ # Output ndim = s.ndim + 1
105+ before = coords [:axis , sel ] # unchanged
106+ after_other = coords [axis + 1 :, sel ] # dims after original axis
107+ win_idx_row = np .full ((1 , sel .size ), win_ix , dtype = idx_dtype )
108+
109+ new_coords = np .vstack ([before , win_idx_row , w_sel [None , :], after_other ])
110+
111+ out_coords_blocks .append (new_coords )
112+ out_data_blocks .append (data [sel ])
113+
114+ if not out_coords_blocks :
115+ return sparse .zeros (
116+ s .shape [:axis ] + (n_win_out ,) + (nwin ,) + s .shape [axis + 1 :], dtype = s .dtype
117+ )
118+
119+ out_coords = np .hstack (out_coords_blocks )
120+ out_data = np .hstack (out_data_blocks )
121+ out_shape = s .shape [:axis ] + (n_win_out ,) + (nwin ,) + s .shape [axis + 1 :]
122+
123+ return sparse .COO (out_coords , out_data , shape = out_shape )
0 commit comments