Repository: deepcharles/ruptures (language: python)

Issue:
Min_size=1 with kernel change point detection but no segments of 1
Hello!

I was hoping to identify segments of down to 1 point in size with kernel change point detection but no matter how low I drop the penalty, the minimum segment size I observe is 2. Is this a feature of kernel change point detection? that a segment of 1 is basically not possible?


Discussion / hints:
Hi,

can you share the line of code that you use? There is indeed one problem with min_size for some methods.
sure! roughly the code is 

`rupture_model = rpt.KernelCPD(kernel=kernel, min_size=min_size)`
`seg_brkpts = rupture_model.fit_predict(seg_values, pen=penalty)`

where kernel="rbf", min_size=1, penalty=2 though I tried a very low penalty out of curiosity (0.00001) and still only achieved segments of minimum size 2. is it not possible with the gaussian kernel to create a segment of min_size<2?
Thanks, there is a bug in the min_size is handled. Basically, `rupture_model.min_size` is 2 even if you set `min_size=1` in the argument. Try doing

```python
rupture_model = rpt.KernelCPD(kernel=kernel, min_size=min_size)
rupture_model.min_size = 1   # force min_size to be 1
seg_brkpts = rupture_model.fit_predict(seg_values, pen=penalty)
```

that worked! Thanks a lot!

Fix patch:
diff --git a/src/ruptures/base.py b/src/ruptures/base.py
index eb9e2815..37786762 100644
--- a/src/ruptures/base.py
+++ b/src/ruptures/base.py
@@ -1,6 +1,8 @@
-r"""All estimators and cost functions are subclasses of
+r"""All estimators and cost functions are subclasses of.
+
 [`BaseEstimator`][ruptures.base.BaseEstimator] and
-[`BaseCost`][ruptures.base.BaseCost] respectively."""
+[`BaseCost`][ruptures.base.BaseCost] respectively.
+"""
 import abc
 from ruptures.utils import pairwise
 
diff --git a/src/ruptures/costs/costrbf.py b/src/ruptures/costs/costrbf.py
index df44bda3..3ef76c78 100644
--- a/src/ruptures/costs/costrbf.py
+++ b/src/ruptures/costs/costrbf.py
@@ -14,7 +14,7 @@ class CostRbf(BaseCost):
 
     def __init__(self, gamma=None):
         """Initialize the object."""
-        self.min_size = 2
+        self.min_size = 1
         self.gamma = gamma
         self._gram = None
 


Test patch:
diff --git a/tests/test_costs.py b/tests/test_costs.py
index 5c6dd6a1..0b10d3cd 100644
--- a/tests/test_costs.py
+++ b/tests/test_costs.py
@@ -60,6 +60,8 @@ def test_costs_1D_names(signal_bkps_1D, cost_name):
             cost.error(1, 2)
         elif cost_name == "l2":
             cost.error(1, 1)
+        elif cost_name == "rbf":
+            cost.error(1, 1)
         else:
             cost.error(1, 2)
 
@@ -80,6 +82,8 @@ def test_costs_1D_noisy_names(signal_bkps_1D_noisy, cost_name):
             cost.error(1, 2)
         elif cost_name == "l2":
             cost.error(1, 1)
+        elif cost_name == "rbf":
+            cost.error(1, 1)
         else:
             cost.error(1, 2)
 
@@ -112,6 +116,8 @@ def test_costs_5D_names(signal_bkps_5D, cost_name):
             cost.error(1, 2)
         elif cost_name == "l2":
             cost.error(1, 1)
+        elif cost_name == "rbf":
+            cost.error(1, 1)
         else:
             cost.error(1, 2)
 
@@ -131,6 +137,8 @@ def test_costs_5D_noisy_names(signal_bkps_5D_noisy, cost_name):
             cost.error(1, 2)
         elif cost_name == "l2":
             cost.error(1, 1)
+        elif cost_name == "rbf":
+            cost.error(1, 1)
         else:
             cost.error(1, 2)
 
diff --git a/tests/test_detection.py b/tests/test_detection.py
index 48e207c6..f9ea615f 100644
--- a/tests/test_detection.py
+++ b/tests/test_detection.py
@@ -398,3 +398,18 @@ def test_binseg_deepcopy():
     binseg = Binseg()
     binseg_copy = deepcopy(binseg)
     assert id(binseg.single_bkp) != id(binseg_copy.single_bkp)
+
+
+def test_min_size_kernelcpd(signal_bkps_1D):
+    """Make sure that KernelCPD can handled min_size=1.
+
+    see https://github.com/deepcharles/ruptures/issues/269
+    """
+    signal, _ = signal_bkps_1D
+    # test if the attribute min_size is set to 1
+    algo = KernelCPD(kernel="rbf", min_size=1).fit(signal=signal)
+    assert algo.min_size == 1
+
+    # test if it can predict segments of size 1
+    n_samples = signal.shape[0]
+    algo.predict(n_bkps=n_samples - 1)