Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions pysatl_cpd/core/algorithms/bayesian_linear_heuristic.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ def detect(self, observation: np.float64 | npt.NDArray[np.float64]) -> bool:
:param observation: a new observation from a time series. Note: only univariate data is supported for now.
:return: whether a change point was detected by a main algorithm.
"""
if observation is npt.NDArray[np.float64]:
if isinstance(observation, np.ndarray):
raise TypeError("Multivariate observations are not supported")
assert self.__main_algorithm is not None, "Main algorithm must be initialized"

Expand All @@ -111,7 +111,7 @@ def localize(self, observation: np.float64 | npt.NDArray[np.float64]) -> Optiona
:param observation: a new observation from a time series. Note: only univariate data is supported for now.
:return: a change point, if it was localized, None otherwise.
"""
if observation is npt.NDArray[np.float64]:
if isinstance(observation, np.ndarray):
raise TypeError("Multivariate observations are not supported")
assert self.__main_algorithm is not None, "Main algorithm must be initialized"

Expand Down
4 changes: 2 additions & 2 deletions pysatl_cpd/core/algorithms/bayesian_online_algorithm.py
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ def detect(self, observation: np.float64 | npt.NDArray[np.float64]) -> bool:
:param observation: new observation of a time series. Note: multivariate time series aren't supported for now.
:return: whether a change point was detected after processing the new observation.
"""
if observation is npt.NDArray[np.float64]:
if isinstance(observation, np.ndarray):
raise TypeError("Multivariate observations are not supported")

self.__process_point(np.float64(observation), False)
Expand All @@ -203,7 +203,7 @@ def localize(self, observation: np.float64 | npt.NDArray[np.float64]) -> Optiona
:return: absolute location of a change point, acquired after processing the new observation,
or None if there wasn't any.
"""
if observation is npt.NDArray[np.float64]:
if isinstance(observation, np.ndarray):
raise TypeError("Multivariate observations are not supported")

self.__process_point(np.float64(observation), True)
Expand Down
33 changes: 33 additions & 0 deletions pysatl_cpd/core/algorithms/ssa/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
"""
Module for SSA CPD algorithm's customization blocks.
"""

__author__ = "Mark Dubrovchenko"
__copyright__ = "Copyright (c) 2026 PySATL project"
__license__ = "SPDX-License-Identifier: MIT"

from pysatl_cpd.core.algorithms.ssa.abstracts import (
SVD,
IDecomposition,
IDetectorSSA,
IEmbedding,
IGrouping,
)
from pysatl_cpd.core.algorithms.ssa.decomposition import BasicSVD
from pysatl_cpd.core.algorithms.ssa.detectors import DistanceThreshold
from pysatl_cpd.core.algorithms.ssa.embedding import BasicEmbedding
from pysatl_cpd.core.algorithms.ssa.grouping import ConstantGrouping
from pysatl_cpd.core.algorithms.ssa.ssa import SSA

__all__ = [
"SSA",
"SVD",
"BasicEmbedding",
"BasicSVD",
"ConstantGrouping",
"DistanceThreshold",
"IDecomposition",
"IDetectorSSA",
"IEmbedding",
"IGrouping",
]
14 changes: 14 additions & 0 deletions pysatl_cpd/core/algorithms/ssa/abstracts/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
"""
Module for abstract base classes for SSA CPD algorithm.
"""

__author__ = "Mark Dubrovchenko"
__copyright__ = "Copyright (c) 2026 PySATL project"
__license__ = "SPDX-License-Identifier: MIT"

from pysatl_cpd.core.algorithms.ssa.abstracts.idecomposition import SVD, IDecomposition
from pysatl_cpd.core.algorithms.ssa.abstracts.idetector import IDetectorSSA
from pysatl_cpd.core.algorithms.ssa.abstracts.iembedding import IEmbedding
from pysatl_cpd.core.algorithms.ssa.abstracts.igrouping import IGrouping

__all__ = ["SVD", "IDecomposition", "IDetectorSSA", "IEmbedding", "IGrouping"]
37 changes: 37 additions & 0 deletions pysatl_cpd/core/algorithms/ssa/abstracts/idecomposition.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
"""
Module for the SSA decomposition step base class.
"""

__author__ = "Mark Dubrovchenko"
__copyright__ = "Copyright (c) 2026 PySATL project"
__license__ = "SPDX-License-Identifier: MIT"

from abc import ABC, abstractmethod
from dataclasses import dataclass

import numpy as np
import numpy.typing as npt


@dataclass
class SVD:
"""
Dataclass of the singular value decomposition.
"""

U: npt.NDArray[np.float64]
sigma: npt.NDArray[np.float64]


class IDecomposition(ABC):
"""
Abstract class of the second step of SSA (Decomposition step).
"""

@abstractmethod
def decompose(self, X: npt.NDArray[np.float64]) -> SVD:
"""
Decomposes the trajectory matrix into elementary matrices.
:param X: trajectory matrix from embedding step.
:return: matrix decomposition for grouping step.
"""
35 changes: 35 additions & 0 deletions pysatl_cpd/core/algorithms/ssa/abstracts/idetector.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
"""
Module for SSA CPD algorithm detector's abstract base class.
"""

__author__ = "Mark Dubrovchenko"
__copyright__ = "Copyright (c) 2026 PySATL project"
__license__ = "SPDX-License-Identifier: MIT"

from abc import ABC, abstractmethod

import numpy as np
import numpy.typing as npt


class IDetectorSSA(ABC):
"""
Abstract class for detectors that detect a change point.
"""

@abstractmethod
def detect(
self, subspace: npt.NDArray[np.float64], test_data: list[np.float64]
) -> bool:
"""
Checks whether a changepoint has occurred at the start of the test data.
:param subspace: vectors of the training set subspace.
:param test_data: test sample for breakpoint detection.
:return: boolean indicating whether a changepoint occurred.
"""

@abstractmethod
def clean(self) -> None:
"""
Clears the detector's state.
"""
29 changes: 29 additions & 0 deletions pysatl_cpd/core/algorithms/ssa/abstracts/iembedding.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
"""
Module for the SSA embedding step base class.
"""

__author__ = "Mark Dubrovchenko"
__copyright__ = "Copyright (c) 2026 PySATL project"
__license__ = "SPDX-License-Identifier: MIT"

from abc import ABC, abstractmethod

import numpy as np
import numpy.typing as npt


class IEmbedding(ABC):
"""
Abstract class of the first step of SSA (Embedding step).
"""

@abstractmethod
def transform(
self, segment: npt.NDArray[np.float64], L: int
) -> npt.NDArray[np.float64]:
"""
Converts a segment of the time series into a trajectory matrix.
:param segment: segment of the time series.
:param L: window width.
:return: trajectory matrix for decomposition step.
"""
28 changes: 28 additions & 0 deletions pysatl_cpd/core/algorithms/ssa/abstracts/igrouping.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
"""
Module for the SSA grouping step base class.
"""

__author__ = "Mark Dubrovchenko"
__copyright__ = "Copyright (c) 2026 PySATL project"
__license__ = "SPDX-License-Identifier: MIT"

from abc import ABC, abstractmethod

import numpy as np
import numpy.typing as npt

from pysatl_cpd.core.algorithms.ssa.abstracts.idecomposition import SVD


class IGrouping(ABC):
"""
Abstract class of the third step of SSA (Grouping step).
"""

@abstractmethod
def group(self, svd: SVD) -> npt.NDArray[np.float64]:
"""
Groups vectors to define a subspace of the time series.
:param svd: decomposition of trajectory matrix.
:return: vector group characterizing a subspace.
"""
11 changes: 11 additions & 0 deletions pysatl_cpd/core/algorithms/ssa/decomposition/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
"""
Module for implementations of SSA decomposition step
"""

__author__ = "Mark Dubrovchenko"
__copyright__ = "Copyright (c) 2026 PySATL project"
__license__ = "SPDX-License-Identifier: MIT"

from pysatl_cpd.core.algorithms.ssa.decomposition.standard_svd import BasicSVD

__all__ = ["BasicSVD"]
27 changes: 27 additions & 0 deletions pysatl_cpd/core/algorithms/ssa/decomposition/standard_svd.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
"""
Module for implementation of basic SSA decomposition step.
"""

__author__ = "Mark Dubrovchenko"
__copyright__ = "Copyright (c) 2026 PySATL project"
__license__ = "SPDX-License-Identifier: MIT"

import numpy as np
import numpy.typing as npt

from pysatl_cpd.core.algorithms.ssa.abstracts import SVD, IDecomposition


class BasicSVD(IDecomposition):
"""
Class of basic SSA decomposition step based on SVD.
"""

def decompose(self, X: npt.NDArray[np.float64]) -> SVD:
"""
Decomposes the trajectory matrix using SVD.
:param X: trajectory matrix from embedding step.
:return: matrix decomposition for grouping step.
"""
U, sigma, _ = np.linalg.svd(X, full_matrices=False)
return SVD(U=U, sigma=sigma)
13 changes: 13 additions & 0 deletions pysatl_cpd/core/algorithms/ssa/detectors/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
"""
Module for implementations of SSA CPD algorithm detectors.
"""

__author__ = "Mark Dubrovchenko"
__copyright__ = "Copyright (c) 2026 PySATL project"
__license__ = "SPDX-License-Identifier: MIT"

from pysatl_cpd.core.algorithms.ssa.detectors.distance_threshold import (
DistanceThreshold,
)

__all__ = ["DistanceThreshold"]
59 changes: 59 additions & 0 deletions pysatl_cpd/core/algorithms/ssa/detectors/distance_threshold.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
"""
Module for implementations of SSA CPD algorithm distance detector using a threshold.
"""

__author__ = "Mark Dubrovchenko"
__copyright__ = "Copyright (c) 2026 PySATL project"
__license__ = "SPDX-License-Identifier: MIT"

import numpy as np
import numpy.typing as npt

from pysatl_cpd.core.algorithms.ssa.abstracts.idetector import IDetectorSSA


class DistanceThreshold(IDetectorSSA):
"""
Class of implementations of SSA CPD algorithm detector using
the normalized Euclidean distance and a threshold.
"""

def __init__(self, threshold: float) -> None:
"""
Initializes SSA CPD algorithm distance detector with given threshold.
:param threshold: threshold for distance calculation.
"""
if not(0 <= threshold <= 1):
raise ValueError("Threshold must be in [0.0, 1.0]")
self._threshold = threshold

def detect(
self, subspace: npt.NDArray[np.float64], test_data: list[np.float64]
) -> bool:
"""
Checks whether a changepoint has occurred at the start of the test data using
the normalized Euclidean distance and comparing it to a threshold.
:param subspace: vectors of the training set subspace.
:param test_data: test sample for breakpoint detection.
:return: boolean indicating whether a changepoint occurred.
"""
L = subspace.shape[0]
n_test = len(test_data) - L + 1
X_test = np.zeros((L, n_test))

for i in range(n_test):
X_test[:, i] = test_data[i : i + L]

proj_sum = np.sum((subspace.T @ X_test) ** 2)
total_sum = np.sum(X_test**2)

if total_sum == 0:
return False

return bool(1 - (proj_sum / total_sum) > self._threshold)

def clean(self) -> None:
"""
Clears the detector's state.
"""
return
11 changes: 11 additions & 0 deletions pysatl_cpd/core/algorithms/ssa/embedding/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
"""
Module for implementations of SSA embedding step
"""

__author__ = "Mark Dubrovchenko"
__copyright__ = "Copyright (c) 2026 PySATL project"
__license__ = "SPDX-License-Identifier: MIT"

from pysatl_cpd.core.algorithms.ssa.embedding.basic_embedding import BasicEmbedding

__all__ = ["BasicEmbedding"]
34 changes: 34 additions & 0 deletions pysatl_cpd/core/algorithms/ssa/embedding/basic_embedding.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
"""
Module for implementation of basic SSA embedding step.
"""

__author__ = "Mark Dubrovchenko"
__copyright__ = "Copyright (c) 2026 PySATL project"
__license__ = "SPDX-License-Identifier: MIT"

import numpy as np
import numpy.typing as npt

from pysatl_cpd.core.algorithms.ssa.abstracts.iembedding import IEmbedding


class BasicEmbedding(IEmbedding):
"""
Class of basic SSA embedding step based on a sliding window.
"""

def transform(
self, segment: npt.NDArray[np.float64], L: int
) -> npt.NDArray[np.float64]:
"""
Converts a segment of the time series into a trajectory matrix based on a sliding window.
:param segment: segment of the time series.
:param L: window width.
:return: trajectory matrix for decomposition step.
"""
K = len(segment) - L + 1
X = np.zeros((L, K))
for i in range(K):
X[:, i] = segment[i : i + L]

return X
11 changes: 11 additions & 0 deletions pysatl_cpd/core/algorithms/ssa/grouping/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
"""
Module for implementations of SSA grouping step
"""

__author__ = "Mark Dubrovchenko"
__copyright__ = "Copyright (c) 2026 PySATL project"
__license__ = "SPDX-License-Identifier: MIT"

from pysatl_cpd.core.algorithms.ssa.grouping.constant_grouping import ConstantGrouping

__all__ = ["ConstantGrouping"]
Loading
Loading