def predict(self, n_bkps=None, pen=None):
        """Return the optimal breakpoints. Must be called after the fit method.

        The breakpoints are associated with the signal passed to
        [`fit()`][ruptures.detection.kernelcpd.KernelCPD.fit].

        Args:
            n_bkps (int, optional): Number of change points. Defaults to None.
            pen (float, optional): penalty value (>0). Defaults to None. Not considered
                if n_bkps is not None.

        Raises:
            AssertionError: if `pen` or `n_bkps` is not strictly positive.
            BadSegmentationParameters: in case of impossible segmentation
                configuration

        Returns:
            list[int]: sorted list of breakpoints
        """
        # Our KernelCPD implementation with Pelt implies that we have at least one change point
        # raise an exception in case of impossible segmentation configuration
        if not sanity_check(
            n_samples=self.cost.signal.shape[0],
            n_bkps=1 if n_bkps is None else n_bkps,
            jump=self.jump,
            min_size=self.min_size,
        ):
            raise BadSegmentationParameters

        # dynamic programming if the user passed a number change points
        if n_bkps is not None:
            n_bkps = int(n_bkps)
            err_msg = "The number of changes must be positive: {}".format(n_bkps)
            assert n_bkps > 0, err_msg
            # if we have already computed it, return it without computations.
            if n_bkps in self.segmentations_dict:
                return self.segmentations_dict[n_bkps]
            # otherwise, call the C function
            if self.kernel_name == "linear":
                path_matrix_flat = ekcpd_L2(self.cost.signal, n_bkps, self.min_size)
            elif self.kernel_name == "rbf":
                path_matrix_flat = ekcpd_Gaussian(
                    self.cost.signal, n_bkps, self.min_size, self.cost.gamma
                )
            elif self.kernel_name == "cosine":
                path_matrix_flat = ekcpd_cosine(self.cost.signal, n_bkps, self.min_size)
            # from the path matrix, get all segmentation for k=1,...,n_bkps changes
            for k in range(1, n_bkps + 1):
                self.segmentations_dict[k] = from_path_matrix_to_bkps_list(
                    path_matrix_flat, k, self.n_samples, n_bkps, self.jump
                )
            return self.segmentations_dict[n_bkps]

        # Call pelt if the user passed a penalty
        if pen is not None:
            assert pen > 0, "The penalty must be positive: {}".format(pen)
            if self.kernel_name == "linear":
                path_matrix = ekcpd_pelt_L2(self.cost.signal, pen, self.min_size)
            elif self.kernel_name == "rbf":
                path_matrix = ekcpd_pelt_Gaussian(
                    self.cost.signal, pen, self.min_size, self.cost.gamma
                )
            elif self.kernel_name == "cosine":
                path_matrix = ekcpd_pelt_cosine(self.cost.signal, pen, self.min_size)

            my_bkps = list()
            ind = self.n_samples
            while ind > 0:
                my_bkps.append(ind)
                ind = path_matrix[ind]
            return my_bkps[::-1]
Return the optimal breakpoints. Must be called after the fit method.

        The breakpoints are associated with the signal passed to
        [`fit()`][ruptures.detection.kernelcpd.KernelCPD.fit].

        Args:
            n_bkps (int, optional): Number of change points. Defaults to None.
            pen (float, optional): penalty value (>0). Defaults to None. Not considered
                if n_bkps is not None.

        Raises:
            AssertionError: if `pen` or `n_bkps` is not strictly positive.
            BadSegmentationParameters: in case of impossible segmentation
                configuration

        Returns:
            list[int]: sorted list of breakpoints
        
src/ruptures/detection/kernelcpd.py