|
1 | 1 | import os |
2 | 2 | import ctypes as ct |
3 | 3 | import numpy as np |
| 4 | +import dask.array as da |
4 | 5 |
|
5 | 6 | class MallocVector(ct.Structure): |
6 | 7 | _fields_ = [("pointer", ct.c_void_p), |
@@ -43,6 +44,66 @@ def rqatrend(y: np.ndarray, threshold: float, border: int = 10, theiler: int = 1 |
43 | 44 | result_single = lib.rqatrend(py, threshold, border, theiler) |
44 | 45 | return result_single |
45 | 46 |
|
| 47 | + |
| 48 | +def rqatrend_matrix(matrix: np.ndarray, threshold: float, border: int = 10, theiler: int = 1) -> np.ndarray: |
| 49 | + """ |
| 50 | + Calculate the RQA trend for a matrix of time series. |
| 51 | + |
| 52 | + :param matrix: Input time series data as a numpy array of shape (n_timeseries, series_length). |
| 53 | + :param threshold: Threshold value for the RQA calculation. |
| 54 | + :param border: Border size for the RQA calculation. |
| 55 | + :param theiler: Theiler window size for the RQA calculation. |
| 56 | + :return: Numpy array of all RQA trend values of size n_timeseries. |
| 57 | + """ |
| 58 | + |
| 59 | + n = matrix.shape[0] |
| 60 | + result_several = np.ones(n) |
| 61 | + p_result_several = mvptr(result_several) |
| 62 | + p_matrix = mmptr(matrix) |
| 63 | + |
| 64 | + # arguments: result_vector, data, threshhold, border, theiler |
| 65 | + lib.rqatrend_inplace.argtypes = (ct.POINTER(MallocVector), ct.POINTER(MallocMatrix), ct.c_double, ct.c_int64, ct.c_int64) |
| 66 | + return_value = lib.rqatrend_inplace(p_result_several, p_matrix, threshold, border, theiler) |
| 67 | + return result_several |
| 68 | + |
| 69 | + |
| 70 | +def rqatrend_dask(x: da.Array, timeseries_axis: int, threshold: float, border: int = 10, theiler: int = 1, out_dtype: type = np.float64) -> da.Array: |
| 71 | + """ |
| 72 | + Apply rqatrend to a given dask array. |
| 73 | +
|
| 74 | + Consider comparing this function's performance with a simple `dask.array.apply_along_axis` |
| 75 | + ```py |
| 76 | + import dask.array as da |
| 77 | + da.apply_along_axis(lambda ts: rqatrend(ts.ravel(), threshold, border, theiler), time_axis, darr) |
| 78 | + ``` |
| 79 | +
|
| 80 | + :param x: dask Array on which rqatrend should be computed. |
| 81 | + :param timeseries_axis: dask Array axis on which the rqatrend function should be applied |
| 82 | + :param threshold: Threshold value for the RQA calculation. |
| 83 | + :param border: Border size for the RQA calculation. |
| 84 | + :param theiler: Theiler window size for the RQA calculation. |
| 85 | + :param out_dtype: dtype of the output dask array, in case a smaller float representation is wanted, or similar. |
| 86 | + :return: Dask array of all RQA trend values without the timeseries_axis dimension (it got aggregated by rqatrend). |
| 87 | + """ |
| 88 | + # Rechunk so full timeseries axis is in one block |
| 89 | + # do this first so we can use the optimized chunks also for moveaxis |
| 90 | + x_rechunked = x.rechunk({timeseries_axis: -1}) |
| 91 | + |
| 92 | + # Move timeseries axis to the end |
| 93 | + x_moved = da.moveaxis(x_rechunked, timeseries_axis, -1) |
| 94 | + |
| 95 | + def _block_wrapper(block): |
| 96 | + # block shape: (..., series_length) |
| 97 | + mat = block.reshape(-1, block.shape[-1]) # (n_timeseries, series_length) |
| 98 | + result = rqatrend_matrix(mat, threshold, border, theiler) # (n_timeseries,) |
| 99 | + return result.reshape(block.shape[:-1]) # reduce last axis |
| 100 | + |
| 101 | + return x_moved.map_blocks( |
| 102 | + _block_wrapper, |
| 103 | + dtype=out_dtype, |
| 104 | + drop_axis=-1 # <---- tell Dask we removed the last axis |
| 105 | + ) |
| 106 | + |
46 | 107 | def main() -> None: |
47 | 108 | pass |
48 | 109 |
|
|
0 commit comments