Repository: sktime/sktime

Issue:
[BUG] Clasp Segmentation sometimes raises a ValueError
**Describe the bug**
<!--
A clear and concise description of what the bug is.
-->

The `predict_scores` method for the `ClaSPSegmentation` algorithm sometimes raises a value error.

**To Reproduce**
<!--
Add a Minimal, Complete, and Verifiable example (for more details, see e.g. https://stackoverflow.com/help/mcve

If the code is too long, feel free to put it in a public gist and link it in the issue: https://gist.github.com
-->

Consider the following `main.py` script.

```python
# main.py
import numpy as np
from sktime.annotation import clasp

# Causes a value error kth(=3) out of bounds (3)
data = np.array([-4.93826793, -5.10968536, -4.94699538, -5.06644812, -5.09389618,
       -4.97855996, -5.03805906, -4.94288774, -4.93562747, -5.15704812,
       -4.98363671, -4.94730547, -4.95460821, -4.77100651, -4.97104642,
       -5.19168259, -4.86592807, -5.03451162, -4.8818181 , -4.96151397,
       -5.04680535, -5.10203082, -5.09094031, -5.14550955, -4.88810337,
       -4.98522959, -4.89865192, -5.17349344, -4.79173094, -4.98540751,
       -4.95218348, -4.92745787, -5.10828999, -5.018588  , -5.23138986,
       -5.0829429 , -4.95182016, -5.04750463, -5.04910479, -4.96474658,
       -4.87263583, -4.93261922, -4.88780183, -4.99625131, -5.04841316,
       -4.8784347 , -4.92589113, -5.23561638, -5.10026717, -4.98502644,
        4.95563698,  5.00165565,  5.02851673,  5.09061275,  5.03335048,
        5.01256648,  5.21727   ,  4.900725  ,  4.86488069,  5.03619925,
        4.84147019,  4.90967582,  4.94225031,  5.01319431,  4.88011974,
        5.03605379,  4.97238513,  4.9344612 ,  5.18930892,  4.90348493,
        4.83290085,  5.04300367,  4.92500575,  5.06299654,  4.83232756,
        4.94786228,  5.03254223,  4.86344949,  4.97827722,  5.03143399,
        5.1337682 ,  4.88999864,  5.16775253,  5.13377762,  5.09881714,
        5.21716965,  4.90141898,  4.94692338,  4.92881945,  4.99632702,
        5.14217334,  5.04286422,  5.02892189,  4.8142512 ,  4.91298724,
        4.97970406,  5.13247509,  4.97386383,  4.94512011,  4.91927367])

model_clasp = clasp.ClaSPSegmentation(n_cps=2)
model_clasp.fit(data)
```

Executing `predict_scores` using this script raises a value error.

```
python3 -i main.py
>>> model_clasp.predict_scores(data)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/alex/documents/sktime/sktime/annotation/base/_base.py", line 150, in predict_scores
    return self._predict_scores(X)
  File "/home/alex/documents/sktime/sktime/annotation/clasp.py", line 287, in _predict_scores
    self.found_cps, self.profiles, self.scores = self._run_clasp(X)
  File "/home/alex/documents/sktime/sktime/annotation/clasp.py", line 318, in _run_clasp
    self.found_cps, self.profiles, self.scores = _segmentation(
  File "/home/alex/documents/sktime/sktime/annotation/clasp.py", line 154, in _segmentation
    profile = clasp.transform(X[ranges])
  File "/home/alex/documents/sktime/sktime/transformations/base.py", line 583, in transform
    Xt = self._transform(X=X_inner, y=y_inner)
  File "/home/alex/documents/sktime/sktime/transformations/series/clasp.py", line 106, in _transform
    Xt, _ = clasp(
  File "/home/alex/documents/sktime/sktime/transformations/series/_clasp_numba.py", line 334, in clasp
    knn_mask = _compute_distances_iterative(X, m, k_neighbours).T
  File "/home/alex/documents/sktime/sktime/transformations/series/_clasp_numba.py", line 114, in _compute_distances_iterative
    idx = np.argpartition(dist, k)
  File "/home/alex/documents/sktime/.venv/lib/python3.10/site-packages/numpy/core/fromnumeric.py", line 858, in argpartition
    return _wrapfunc(a, 'argpartition', kth, axis=axis, kind=kind, order=order)
  File "/home/alex/documents/sktime/.venv/lib/python3.10/site-packages/numpy/core/fromnumeric.py", line 59, in _wrapfunc
    return bound(*args, **kwds)
ValueError: kth(=3) out of bounds (3)
```
From debugging, the error seems to come from `_compute_distances_iterative` in `_clasp_number.py` on line 53.

https://github.com/sktime/sktime/blob/f48b1f53f58004bb792abf78ce6818034835d3c2/sktime/transformations/series/_clasp_numba.py#L114

In the example, `k` is `3` and `dist` is `array([inf, inf, inf])`. Since `dist` has length 3, then `k` is out of range. I think this could be solved with some simple bound checking where if `k` is greater than or equal to the length of `dist` then set `k` to the length of `dist` minus 1.

**Expected behavior**
<!--
A clear and concise description of what you expected to happen.
-->

Using `predict_scores` should not raise a value error.

**Additional context**
<!--
Add any other context about the problem here.
-->

This bug happened when I was trying to fit the `ClaSPSegmentation` algorithm to several different data arrays genered using the following code.

```python
n = 50
data = np.concatenate([np.random.normal(-5, 0.1, n), np.random.normal(5, 0.1, n)])
```

**Versions**
<details>

<!--
Please run the following code snippet and paste the output here:

from sktime import show_versions; show_versions()
-->

My versions,

```
>>> from sktime import show_versions; show_versions()

System:
    python: 3.10.12 (main, Jun 11 2023, 05:26:28) [GCC 11.4.0]
executable: /home/alex/documents/sktime/.venv/bin/python3
   machine: Linux-5.15.90.1-microsoft-standard-WSL2-x86_64-with-glibc2.35

Python dependencies:
          pip: 22.0.2
       sktime: 0.24.0
      sklearn: 1.3.1
       skbase: 0.6.0
        numpy: 1.26.1
        scipy: 1.11.3
       pandas: 2.1.1
   matplotlib: 3.8.0
       joblib: 1.3.2
        numba: 0.58.1
  statsmodels: None
     pmdarima: None
statsforecast: None
      tsfresh: None
      tslearn: None
        torch: None
   tensorflow: None
tensorflow_probability: None
```

<!-- Thanks for contributing! -->



Discussion:

Resolving patch:
diff --git a/sktime/transformations/series/_clasp_numba.py b/sktime/transformations/series/_clasp_numba.py
--- a/sktime/transformations/series/_clasp_numba.py
+++ b/sktime/transformations/series/_clasp_numba.py
@@ -111,7 +111,8 @@ def _compute_distances_iterative(X, m, k):
         )
         dist[trivialMatchRange[0] : trivialMatchRange[1]] = np.inf
 
-        idx = np.argpartition(dist, k)
+        _k = min(k, len(dist) - 1)
+        idx = np.argpartition(dist, _k)
 
         knns[order, :] = idx[:k]
         dot_prev = dot_rolled


Test patch: