S2Generator 0.0.7__tar.gz → 0.0.9__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. {s2generator-0.0.7 → s2generator-0.0.9}/PKG-INFO +1 -1
  2. {s2generator-0.0.7 → s2generator-0.0.9}/S2Generator.egg-info/PKG-INFO +1 -1
  3. {s2generator-0.0.7 → s2generator-0.0.9}/S2Generator.egg-info/SOURCES.txt +11 -1
  4. {s2generator-0.0.7 → s2generator-0.0.9}/s2generator/__init__.py +2 -1
  5. s2generator-0.0.9/s2generator/augmentation/__init__.py +39 -0
  6. s2generator-0.0.9/s2generator/augmentation/_amplitude_modulation.py +103 -0
  7. s2generator-0.0.9/s2generator/augmentation/_censor_augmentation.py +74 -0
  8. s2generator-0.0.9/s2generator/augmentation/_empirical_mode_modulation.py +126 -0
  9. s2generator-0.0.9/s2generator/augmentation/_frequency_perturbation.py +104 -0
  10. s2generator-0.0.9/s2generator/augmentation/_spike_injection.py +16 -0
  11. s2generator-0.0.9/s2generator/augmentation/_time_transformation.py +68 -0
  12. s2generator-0.0.9/s2generator/augmentation/_wiener_filter.py +60 -0
  13. {s2generator-0.0.7 → s2generator-0.0.9}/s2generator/base.py +3 -3
  14. {s2generator-0.0.7 → s2generator-0.0.9}/s2generator/simulator/__init__.py +2 -0
  15. {s2generator-0.0.7 → s2generator-0.0.9}/s2generator/simulator/wiener_filter.py +47 -2
  16. {s2generator-0.0.7 → s2generator-0.0.9}/s2generator/utils/__init__.py +10 -0
  17. {s2generator-0.0.7 → s2generator-0.0.9}/s2generator/utils/_tools.py +76 -0
  18. {s2generator-0.0.7 → s2generator-0.0.9}/s2generator/utils/visualization.py +0 -1
  19. {s2generator-0.0.7 → s2generator-0.0.9}/setup.py +1 -1
  20. s2generator-0.0.9/tests/test_augmentation.py +141 -0
  21. s2generator-0.0.9/tests/test_wiener_filter_simulator.py +203 -0
  22. {s2generator-0.0.7 → s2generator-0.0.9}/LICENSE +0 -0
  23. {s2generator-0.0.7 → s2generator-0.0.9}/README.md +0 -0
  24. {s2generator-0.0.7 → s2generator-0.0.9}/S2Generator.egg-info/dependency_links.txt +0 -0
  25. {s2generator-0.0.7 → s2generator-0.0.9}/S2Generator.egg-info/requires.txt +0 -0
  26. {s2generator-0.0.7 → s2generator-0.0.9}/S2Generator.egg-info/top_level.txt +0 -0
  27. {s2generator-0.0.7 → s2generator-0.0.9}/s2generator/encoders.py +0 -0
  28. {s2generator-0.0.7 → s2generator-0.0.9}/s2generator/excitation/__init__.py +0 -0
  29. {s2generator-0.0.7 → s2generator-0.0.9}/s2generator/excitation/_interface.py +0 -0
  30. {s2generator-0.0.7 → s2generator-0.0.9}/s2generator/excitation/autoregressive_moving_average.py +0 -0
  31. {s2generator-0.0.7 → s2generator-0.0.9}/s2generator/excitation/base_excitation.py +0 -0
  32. {s2generator-0.0.7 → s2generator-0.0.9}/s2generator/excitation/forecast_pfn.py +0 -0
  33. {s2generator-0.0.7 → s2generator-0.0.9}/s2generator/excitation/intrinsic_mode_functions.py +0 -0
  34. {s2generator-0.0.7 → s2generator-0.0.9}/s2generator/excitation/kernel_synth.py +0 -0
  35. {s2generator-0.0.7 → s2generator-0.0.9}/s2generator/excitation/mixed_distribution.py +0 -0
  36. {s2generator-0.0.7 → s2generator-0.0.9}/s2generator/generators.py +0 -0
  37. {s2generator-0.0.7 → s2generator-0.0.9}/s2generator/params/__init__.py +0 -0
  38. {s2generator-0.0.7 → s2generator-0.0.9}/s2generator/params/series_params.py +0 -0
  39. {s2generator-0.0.7 → s2generator-0.0.9}/s2generator/params/symbol_params.py +0 -0
  40. {s2generator-0.0.7 → s2generator-0.0.9}/s2generator/simulator/arima.py +0 -0
  41. {s2generator-0.0.7 → s2generator-0.0.9}/s2generator/simulator/kalman_filtering.py +0 -0
  42. {s2generator-0.0.7 → s2generator-0.0.9}/s2generator/utils/_decomposition.py +0 -0
  43. {s2generator-0.0.7 → s2generator-0.0.9}/s2generator/utils/_print_status.py +0 -0
  44. {s2generator-0.0.7 → s2generator-0.0.9}/s2generator/utils/_smooth.py +0 -0
  45. {s2generator-0.0.7 → s2generator-0.0.9}/s2generator/utils/_wasserstein_distance.py +0 -0
  46. {s2generator-0.0.7 → s2generator-0.0.9}/s2generator/utils/print_symbol.py +0 -0
  47. {s2generator-0.0.7 → s2generator-0.0.9}/setup.cfg +0 -0
  48. {s2generator-0.0.7 → s2generator-0.0.9}/tests/__init__.py +0 -0
  49. {s2generator-0.0.7 → s2generator-0.0.9}/tests/test_all.py +0 -0
  50. {s2generator-0.0.7 → s2generator-0.0.9}/tests/test_arma.py +0 -0
  51. {s2generator-0.0.7 → s2generator-0.0.9}/tests/test_base_excitation.py +0 -0
  52. {s2generator-0.0.7 → s2generator-0.0.9}/tests/test_forecast_pfn.py +0 -0
  53. {s2generator-0.0.7 → s2generator-0.0.9}/tests/test_intrinsic_mode_functions.py +0 -0
  54. {s2generator-0.0.7 → s2generator-0.0.9}/tests/test_kernel_synth.py +0 -0
  55. {s2generator-0.0.7 → s2generator-0.0.9}/tests/test_mixed_distribution.py +0 -0
  56. {s2generator-0.0.7 → s2generator-0.0.9}/tests/test_series_params.py +0 -0
  57. {s2generator-0.0.7 → s2generator-0.0.9}/tests/test_stl.py +0 -0
  58. {s2generator-0.0.7 → s2generator-0.0.9}/tests/test_symbol_params.py +0 -0
  59. {s2generator-0.0.7 → s2generator-0.0.9}/tests/test_tools.py +0 -0
  60. {s2generator-0.0.7 → s2generator-0.0.9}/tests/test_wasserstein.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: S2Generator
3
- Version: 0.0.7
3
+ Version: 0.0.9
4
4
  Summary: A series-symbol (S2) dual-modality data generation mechanism, enabling the unrestricted creation of high-quality time series data paired with corresponding symbolic representations.
5
5
  Home-page: https://github.com/wwhenxuan/S2Generator
6
6
  Author: whenxuan, johnfan12, changewam
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: S2Generator
3
- Version: 0.0.7
3
+ Version: 0.0.9
4
4
  Summary: A series-symbol (S2) dual-modality data generation mechanism, enabling the unrestricted creation of high-quality time series data paired with corresponding symbolic representations.
5
5
  Home-page: https://github.com/wwhenxuan/S2Generator
6
6
  Author: whenxuan, johnfan12, changewam
@@ -10,6 +10,14 @@ s2generator/__init__.py
10
10
  s2generator/base.py
11
11
  s2generator/encoders.py
12
12
  s2generator/generators.py
13
+ s2generator/augmentation/__init__.py
14
+ s2generator/augmentation/_amplitude_modulation.py
15
+ s2generator/augmentation/_censor_augmentation.py
16
+ s2generator/augmentation/_empirical_mode_modulation.py
17
+ s2generator/augmentation/_frequency_perturbation.py
18
+ s2generator/augmentation/_spike_injection.py
19
+ s2generator/augmentation/_time_transformation.py
20
+ s2generator/augmentation/_wiener_filter.py
13
21
  s2generator/excitation/__init__.py
14
22
  s2generator/excitation/_interface.py
15
23
  s2generator/excitation/autoregressive_moving_average.py
@@ -36,6 +44,7 @@ s2generator/utils/visualization.py
36
44
  tests/__init__.py
37
45
  tests/test_all.py
38
46
  tests/test_arma.py
47
+ tests/test_augmentation.py
39
48
  tests/test_base_excitation.py
40
49
  tests/test_forecast_pfn.py
41
50
  tests/test_intrinsic_mode_functions.py
@@ -45,4 +54,5 @@ tests/test_series_params.py
45
54
  tests/test_stl.py
46
55
  tests/test_symbol_params.py
47
56
  tests/test_tools.py
48
- tests/test_wasserstein.py
57
+ tests/test_wasserstein.py
58
+ tests/test_wiener_filter_simulator.py
@@ -1,6 +1,6 @@
1
1
  # -*- coding: utf-8 -*-
2
2
 
3
- __version__ = "0.0.7"
3
+ __version__ = "0.0.9"
4
4
 
5
5
  __all__ = [
6
6
  "Node",
@@ -17,6 +17,7 @@ __all__ = [
17
17
  "print_hello",
18
18
  "excitation",
19
19
  "simulator",
20
+ "augmentation",
20
21
  "utils",
21
22
  "params",
22
23
  ]
@@ -0,0 +1,39 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ Created on 2026/03/02 12:15:45
4
+ @author: Whenxuan Wang
5
+ @email: wwhenxuan@gmail.com
6
+ @url: https://github.com/wwhenxuan/S2Generator
7
+ """
8
+
9
+ __all__ = [
10
+ "amplitude_modulation",
11
+ "censor_augmentation",
12
+ "empirical_mode_modulation",
13
+ "frequency_perturbation",
14
+ "spike_injection",
15
+ "wiener_filter",
16
+ "add_linear_trend",
17
+ "time_series_mixup",
18
+ ]
19
+
20
+ # Import the amplitude modulation function
21
+ from ._amplitude_modulation import amplitude_modulation
22
+
23
+ # Import the censoring augmentation function
24
+ from ._censor_augmentation import censor_augmentation
25
+
26
+ # Import the empirical mode modulation function
27
+ from ._empirical_mode_modulation import empirical_mode_modulation
28
+
29
+ # Import the frequency perturbation function
30
+ from ._frequency_perturbation import frequency_perturbation
31
+
32
+ # Import the spike injection function
33
+ from ._spike_injection import spike_injection
34
+
35
+ # Import the wiener filter function
36
+ from ._wiener_filter import wiener_filter
37
+
38
+ # Import the time transformation functions
39
+ from ._time_transformation import add_linear_trend, time_series_mixup
@@ -0,0 +1,103 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ Created on 2026/03/04 22:52:40
4
+ @author: Whenxuan Wang
5
+ @email: wwhenxuan@gmail.com
6
+ @url: https://github.com/wwhenxuan/S2Generator
7
+ """
8
+
9
+ import numpy as np
10
+
11
+ from s2generator.utils._tools import (
12
+ linear_interpolation,
13
+ cubic_spline_interpolation,
14
+ lagrange_interpolation,
15
+ )
16
+
17
+
18
+ def amplitude_modulation(
19
+ time_series: np.ndarray,
20
+ num_changepoints: int = 5,
21
+ amplitude_mean: float = 1.0,
22
+ amplitude_variation: float = 1.0,
23
+ interpolation_method: str = "linear",
24
+ rng: np.random.RandomState = None,
25
+ seed: int = 42,
26
+ ) -> np.ndarray:
27
+ """
28
+ Perform amplitude modulation on the input time series.
29
+ This augmentation introduces scale trends and change points into the time series
30
+ by multiplying the signal with a piecewise linear trend.
31
+ The modulation trend is generated by sampling change points and interpolating amplitudes between them.
32
+
33
+ :param time_series: Input time series, a 1D numpy array
34
+ :param num_changepoints: Number of change points to introduce in the modulation trend.
35
+ :param amplitude_mean: The mean amplitude of the modulation trend.
36
+ :param amplitude_variation: The variation of the amplitude around the mean.
37
+ :param interpolation_method: The method to interpolate the modulation trend.
38
+ Options are "linear", "cubic", or "lagrange".
39
+ :param rng: Optional random number generator for reproducibility. If None, a new RNG will be created using the provided seed.
40
+ :param seed: Random seed for reproducibility if rng is not provided.
41
+
42
+ :return: Amplitude modulated time series, a 1D numpy array of the same length as the input series.
43
+ """
44
+ # Validate the input time series
45
+ time_series = np.asarray(time_series)
46
+ if time_series.ndim != 1:
47
+ raise ValueError("Input time_series must be a 1D array.")
48
+
49
+ # Validate interpolation method
50
+ if interpolation_method not in ["linear", "cubic", "lagrange"]:
51
+ raise ValueError(
52
+ "interpolation_method must be one of 'linear', 'cubic', or 'lagrange'."
53
+ )
54
+
55
+ # Get the length of the time series
56
+ n = len(time_series)
57
+
58
+ # Validate num_changepoints
59
+ if num_changepoints < 2:
60
+ raise ValueError(
61
+ "num_changepoints must be at least 2 to create a modulation trend."
62
+ )
63
+ if num_changepoints > n:
64
+ raise ValueError(
65
+ "num_changepoints cannot exceed the length of the time series."
66
+ )
67
+
68
+ # Initialize random number generator
69
+ if rng is None:
70
+ rng = np.random.RandomState(seed)
71
+
72
+ # Sample change points and their corresponding amplitudes
73
+ changepoints = np.hstack(
74
+ [
75
+ 0,
76
+ np.sort(rng.choice(n - 2, size=num_changepoints - 2, replace=False) + 1),
77
+ n - 1,
78
+ ]
79
+ )
80
+
81
+ # Generate random amplitudes for each change point
82
+ amplitude = rng.normal(
83
+ loc=amplitude_mean, scale=amplitude_variation, size=num_changepoints
84
+ )
85
+
86
+ # Interpolate the modulation trend across the entire time series
87
+ if interpolation_method == "linear":
88
+ modulation_trend = linear_interpolation(
89
+ x_known=changepoints, y_known=amplitude, x_new=np.arange(n)
90
+ )
91
+ elif interpolation_method == "cubic":
92
+ modulation_trend = cubic_spline_interpolation(
93
+ x_known=changepoints, y_known=amplitude, x_new=np.arange(n)
94
+ )
95
+ elif interpolation_method == "lagrange":
96
+ modulation_trend = lagrange_interpolation(
97
+ x_known=changepoints, y_known=amplitude, x_new=np.arange(n)
98
+ )
99
+
100
+ # Apply the modulation trend to the original time series
101
+ modulated_series = time_series * modulation_trend
102
+
103
+ return modulated_series, np.array(modulation_trend)
@@ -0,0 +1,74 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ Created on 2026/03/05 00:42:51
4
+ @author: Whenxuan Wang
5
+ @email: wwhenxuan@gmail.com
6
+ @url: https://github.com/wwhenxuan/S2Generator
7
+ """
8
+
9
+ import numpy as np
10
+
11
+
12
+ def censor_augmentation(
13
+ time_series: np.ndarray,
14
+ upper_quantile: float = 0.65,
15
+ lower_quantile: float = 0.35,
16
+ bernoulli_p: float = 0.8,
17
+ rng: np.random.RandomState = None,
18
+ seed: int = 42,
19
+ ) -> np.ndarray:
20
+ """
21
+ Perform censoring augmentation on the input time series.
22
+
23
+ This augmentation censors (clips) the input signal either from below or
24
+ above, depending on a randomly sampled direction. The clipping threshold is determined by drawing
25
+ a quantile uniformly from the empirical distribution of the signal.
26
+
27
+ :param time_series: Input time series, a 1D numpy array
28
+ :param upper_quantile: Upper quantile threshold for censoring, default is 0.65
29
+ :param lower_quantile: Lower quantile threshold for censoring, default is 0.35
30
+ :param bernoulli_p: Probability of censoring direction (0 for lower censoring, 1 for upper censoring),
31
+ the default is 0.5, meaning equal probability for both directions.
32
+ :param rng: Optional random number generator for reproducibility.
33
+ If None, a new RNG will be created using the provided seed.
34
+ :param seed: Random seed for reproducibility if rng is not provided.
35
+
36
+ :return: Censored time series, a 1D numpy array of the same length as the input series.
37
+ """
38
+
39
+ # Validate the input time series
40
+ time_series = np.asarray(time_series)
41
+ if time_series.ndim != 1:
42
+ raise ValueError("Input time_series must be a 1D array.")
43
+
44
+ # Validate bernoulli_p
45
+ if not (0 <= bernoulli_p <= 1):
46
+ raise ValueError("bernoulli_p must be in the range [0, 1].")
47
+
48
+ # Get the length of the time series
49
+ length = time_series.shape[0]
50
+
51
+ # Set random seed for reproducibility
52
+ if rng is None:
53
+ rng = np.random.RandomState(seed)
54
+
55
+ # Randomly sample quantile thresholds for each time step
56
+ quantile_threshold = rng.uniform(lower_quantile, upper_quantile, size=length)
57
+
58
+ # Compute the threshold value based on the quantile of the time series
59
+ threshold_value = np.quantile(time_series, quantile_threshold)
60
+
61
+ # Sample the censor direction from bernoulli distribution (0.5)
62
+ censor_direction = rng.binomial(
63
+ n=1, p=bernoulli_p, size=length
64
+ ) # 0 for lower censoring, 1 for upper censoring
65
+
66
+ for t in range(length):
67
+ if censor_direction[t] == 1:
68
+ # Lower censoring
69
+ time_series[t] = max(time_series[t], threshold_value[t])
70
+ else:
71
+ # Upper censoring
72
+ time_series[t] = min(time_series[t], threshold_value[t])
73
+
74
+ return time_series
@@ -0,0 +1,126 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ Created on 2026/03/05 11:05:45
4
+ @author: Whenxuan Wang
5
+ @email: wwhenxuan@gmail.com
6
+ @url: https://github.com/wwhenxuan/S2Generator
7
+ """
8
+ from typing import Optional
9
+
10
+ import numpy as np
11
+
12
+ from pysdkit import EMD
13
+
14
+
15
+ def empirical_mode_modulation(
16
+ time_series: np.ndarray,
17
+ min_scale_factor: float = 0.5,
18
+ max_scale_factor: float = 2.0,
19
+ low_frequency_enhancement: bool = True,
20
+ spline_kind: str = "cubic",
21
+ extrema_detection: str = "parabol",
22
+ max_imfs: Optional[int] = None,
23
+ rng: Optional[np.random.RandomState] = None,
24
+ seed: int = 42,
25
+ ) -> np.ndarray:
26
+ """
27
+ Perform empirical mode modulation on the input time series.
28
+ This augmentation decomposes the time series into intrinsic mode functions (IMFs)
29
+ using Empirical Mode Decomposition (EMD) and then reconstructs the signal
30
+ by randomly modifying the IMFs to introduce non-linear trends and variations.
31
+
32
+ :param time_series: Input time series, a 1D numpy array.
33
+ :param min_scale_factor: The minimum scaling factor to apply to each IMF.
34
+ :param max_scale_factor: The maximum scaling factor to apply to each IMF.
35
+ :param low_frequency_enhancement: Whether to enhance the low-frequency components.
36
+ If True, the method will suppress the high-frequency noise in the
37
+ empirical mode decomposition results and focus on enhancing
38
+ the characterization of the low-frequency part.
39
+ :param spline_kind: The kind of spline to use for interpolation,
40
+ options are "akima", "cubic", "pchip", "cubic_hermite", "slinear", "quadratic", "linear"
41
+ :param extrema_detection: The method for detecting extrema in the EMD process, options are "parabol" or "simple"
42
+ :param max_imfs: The maximum number of IMFs to extract,
43
+ if None, it will extract all possible IMFs until the residue is a monotonic function.
44
+
45
+ :return: Empirical mode modulated time series, a 1D numpy array of the same length as the input series.
46
+ """
47
+ # Validate the input time series
48
+ time_series = np.asarray(time_series)
49
+ if time_series.ndim != 1:
50
+ raise ValueError("Input time_series must be a 1D array.")
51
+
52
+ # Normalize the input time series to have zero mean and unit variance
53
+ mean, std = np.mean(time_series), np.std(time_series)
54
+ time_series = (time_series - mean) / (
55
+ std + 1e-8
56
+ ) # Add a small value to avoid division by zero
57
+
58
+ # Check if max_imfs is valid
59
+ if max_imfs is None:
60
+ # 表示会完整的分解所有的IMF分量
61
+ # 直到剩余的分量不再满足IMF的定义为止
62
+ max_imfs = -1
63
+
64
+ # Validate the spline kind
65
+ assert spline_kind in [
66
+ "akima",
67
+ "cubic",
68
+ "pchip",
69
+ "cubic_hermite",
70
+ "slinear",
71
+ "quadratic",
72
+ "linear",
73
+ ], "spline_kind must be one of 'akima', 'cubic', 'pchip', 'cubic_hermite', 'slinear', 'quadratic', 'linear'."
74
+
75
+ # Validate the extrema detection method
76
+ assert extrema_detection in [
77
+ "parabol",
78
+ "simple",
79
+ ], "extrema_detection must be one of 'parabol' or 'simple'."
80
+
81
+ # Initialize random number generator
82
+ if rng is None:
83
+ rng = np.random.RandomState(seed=seed)
84
+
85
+ # Perform Empirical Mode Decomposition
86
+ emd = EMD(
87
+ max_imfs=max_imfs, spline_kind=spline_kind, extrema_detection=extrema_detection
88
+ )
89
+ print(type(time_series))
90
+ imfs = emd.fit_transform(signal=time_series)
91
+
92
+ # Get the number of IMFs extracted
93
+ num_imfs = imfs.shape[0]
94
+
95
+ # Randomly select a scaling factor for modulation
96
+ scale_factor = rng.uniform(
97
+ low=min_scale_factor, high=max_scale_factor, size=num_imfs
98
+ )
99
+
100
+ # Validate the low_frequency_enhancement parameter
101
+ assert isinstance(
102
+ low_frequency_enhancement, bool
103
+ ), "low_frequency_enhancement must be a boolean value."
104
+ if low_frequency_enhancement is True:
105
+ # If low-frequency enhancement is enabled, we can apply a stronger scaling to the lower frequency IMFs
106
+ scale_factor = np.sort(
107
+ scale_factor
108
+ ) # Sort the scale factors to enhance low-frequency components more than high-frequency ones
109
+
110
+ # Randomly modify the IMFs to create modulation
111
+ modified_imfs = []
112
+ for index, imf in enumerate(imfs):
113
+ # Randomly scale each IMF by a factor between min_scale_factor and max_scale_factor
114
+ scaled_imf = imf * scale_factor[index]
115
+ modified_imfs.append(scaled_imf)
116
+
117
+ # Reconstruct the signal from the modified IMFs
118
+ modulated_time_series = np.sum(modified_imfs, axis=0)
119
+
120
+ # Denormalize the modulated time series to restore the original scale
121
+ modulated_time_series = (modulated_time_series - np.mean(modulated_time_series)) / (
122
+ np.std(modulated_time_series) + 1e-8
123
+ )
124
+ modulated_time_series = modulated_time_series * (std + 1e-8) + mean
125
+
126
+ return modulated_time_series
@@ -0,0 +1,104 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ Created on 2026/03/02 12:16:05
4
+ @author: Whenxuan Wang
5
+ @email: wwhenxuan@gmail.com
6
+ @url: https://github.com/wwhenxuan/S2Generator
7
+ """
8
+
9
+ import numpy as np
10
+ from numpy import fft
11
+
12
+
13
+ def sample_random_perturbation(
14
+ K: int, min_alpha: float, max_alpha: float, rng: np.random.RandomState = None
15
+ ) -> np.ndarray:
16
+ """
17
+ Randomly sample K numbers in the interval [-alpha_max, -alpha_min] U [alpha_min, alpha_max]
18
+ The purpose of this sampling is to construct random perturbations in the frequency domain.
19
+
20
+ :param K: Number of random numbers to sample
21
+ :param min_alpha: Minimum absolute value of the random numbers
22
+ :param max_alpha: Maximum absolute value of the random numbers
23
+ :param rng: Optional random number generator, if not provided, the global numpy random number generator will be used
24
+
25
+ :return: A numpy array containing K random numbers, which are uniformly distributed in the interval [-alpha_max, -alpha_min] U [alpha_min, alpha_max]
26
+ """
27
+
28
+ # First generate random numbers in [alpha_min, alpha_max]
29
+ if rng is not None:
30
+ positive_rand = rng.uniform(min_alpha, max_alpha, K)
31
+
32
+ # Randomly generate sign (-1 or 1)
33
+ signs = rng.choice([-1, 1], size=K)
34
+
35
+ else:
36
+ # When the random number generator is not passed in, use the global numpy random number generator
37
+ positive_rand = np.random.uniform(min_alpha, max_alpha, K)
38
+ signs = np.random.choice([-1, 1], size=K)
39
+
40
+ # Combine to get the final result
41
+ final_random_nums = positive_rand * signs
42
+
43
+ return final_random_nums
44
+
45
+
46
+ def frequency_perturbation(
47
+ time_series: np.ndarray,
48
+ min_alpha: float,
49
+ max_alpha: float,
50
+ r: float = 0.5,
51
+ rng: np.random.RandomState = None,
52
+ ) -> np.ndarray:
53
+ """
54
+ Perform frequency domain perturbation on the input time series.
55
+ This method adds random perturbations to the frequency components of the time series,
56
+ which can help to enhance the diversity of the data and improve the robustness of models trained on it.
57
+
58
+ :param time_series: Input time series, a 1D numpy array
59
+ :param min_alpha: Minimum absolute value of the random perturbation added to the frequency components
60
+ :param max_alpha: Maximum absolute value of the random perturbation added to the frequency components
61
+ :param r: Proportion of frequency components to perturb (default is 0.5, meaning 50% of the frequency components will be perturbed)
62
+ :param rng: Optional random number generator, if not provided, the global numpy random number generator will be used.
63
+
64
+ :return: Perturbed time series, a 1D numpy array of the same length as the input series.
65
+ """
66
+ # Validate the input parameters
67
+ assert 0 <= r <= 1, "The proportion r must be between 0 and 1."
68
+ assert min_alpha >= 0, "min_alpha must be non-negative."
69
+ assert (
70
+ max_alpha >= min_alpha
71
+ ), "max_alpha must be greater than or equal to min_alpha."
72
+
73
+ # Validate that the input time series is ndarray
74
+ if isinstance(time_series, list):
75
+ time_series = np.array(time_series)
76
+
77
+ # Validate that the input time series is 1D
78
+ if time_series.ndim != 1:
79
+ raise ValueError("Input time series must be a 1D array.")
80
+
81
+ # Perform Fast Fourier Transform to convert the time series to the frequency domain
82
+ f = fft.rfft(time_series)
83
+ f_perturbed = f.copy()
84
+ frequencies = fft.fftfreq(len(time_series))
85
+
86
+ # Calculate the number of frequency domain components that can be perturbed
87
+ K = int(len(frequencies) * r)
88
+
89
+ # Sample random perturbations for the real and imaginary parts of the frequency components
90
+ alpha_real = sample_random_perturbation(
91
+ K=K, min_alpha=min_alpha, max_alpha=max_alpha, rng=rng
92
+ )
93
+ alpha_imag = sample_random_perturbation(
94
+ K=K, min_alpha=min_alpha, max_alpha=max_alpha, rng=rng
95
+ )
96
+
97
+ # Randomly select K frequency domain components for perturbation
98
+ indices = np.random.choice(len(f_perturbed), size=K, replace=False)
99
+ f_perturbed[indices] += alpha_real + 1j * alpha_imag
100
+
101
+ # Perform inverse Fourier transform to restore the original time-domain signal
102
+ perturbed_series = fft.irfft(f_perturbed).real
103
+
104
+ return perturbed_series
@@ -0,0 +1,16 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ Created on 2026/03/05 15:54:19
4
+ @author: Whenxuan Wang
5
+ @email: wwhenxuan@gmail.com
6
+ @url: https://github.com/wwhenxuan/S2Generator
7
+ """
8
+ import numpy as np
9
+
10
+
11
+ def spike_injection(time_series: np.ndarray) -> np.ndarray:
12
+ """
13
+ Perform spike injection augmentation on the input time series.
14
+ This augmentation randomly injects spikes into the input time series to simulate sudden and extreme events, which can help models learn to handle such anomalies.
15
+ """
16
+ pass
@@ -0,0 +1,68 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ Created on 2026/03/05 16:19:59
4
+ @author: Whenxuan Wang
5
+ @email: wwhenxuan@gmail.com
6
+ @url: https://github.com/wwhenxuan/S2Generator
7
+ """
8
+ import numpy as np
9
+
10
+
11
+ def add_linear_trend(
12
+ time_series: np.ndarray, trend_strength: float = 1.0, direction: str = "upward"
13
+ ) -> np.ndarray:
14
+ """
15
+ Perform linear trend augmentation on the input time series.
16
+ This augmentation adds a linear trend to the input time series,
17
+ which can help models learn to handle non-stationary data and improve their robustness to trends.
18
+
19
+ :param time_series: Input time series, a 1D numpy array
20
+ :param trend_strength: The strength of the linear trend to be added, default is 1.0.
21
+ :param direction: The direction of the linear trend, either "upward" or "downward", default is "upward".
22
+
23
+ :return: Augmented time series with a linear trend, a 1D numpy array of the same length as the input series.
24
+ """
25
+
26
+ # Get the length of the time series
27
+ seq_length = len(time_series)
28
+
29
+ # Calculate the the energy of the original time series
30
+ original_energy = np.mean(time_series**2)
31
+
32
+ # Create a linear trend
33
+ if direction == "upward":
34
+ trend = np.linspace(0, trend_strength * seq_length, seq_length)
35
+ elif direction == "downward":
36
+ trend = np.linspace(0, -trend_strength * seq_length, seq_length)
37
+ else:
38
+ raise ValueError("direction must be either 'upward' or 'downward'")
39
+
40
+ # Scale the trend to have the same energy as the original time series
41
+ trend_energy = np.mean(trend**2)
42
+
43
+ if trend_energy > 0:
44
+ trend = trend * np.sqrt(original_energy / trend_energy)
45
+
46
+ # Average the original signal and the trend to maintain the overall scale
47
+ return (time_series + trend) / 2
48
+
49
+
50
+ def time_series_mixup(a: np.ndarray, b: np.ndarray, alpha: float = 0.7) -> np.ndarray:
51
+ """
52
+ Mixup Enhancement: Weighted mixing of two time series to create a new augmented signal.
53
+ This method combines two time series by taking a weighted average of them,
54
+ where the weights are determined by a mixing parameter alpha.
55
+ This can help models learn to generalize better by exposing them to a wider variety of signal combinations.
56
+
57
+ :param a: First input time series, a 1D numpy array.
58
+ :param b: Second input time series, a 1D numpy array of the same length as a.
59
+ :param alpha: The mixing parameter that controls the weight of each time series in the mixup,
60
+ default is 0.7. A value of alpha close to 1 gives more weight to the first time series (a),
61
+ while a value close to 0 gives more weight to the second time series (b).
62
+
63
+ :return: Mixed time series, a 1D numpy array of the same length as the input series.
64
+ """
65
+ assert a.shape == b.shape, "Input time series must have the same shape"
66
+
67
+ # Calculate the mixed signal as a weighted average of the two input signals
68
+ return alpha * a + (1 - alpha) * b
@@ -0,0 +1,60 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ Created on 2026/03/05 16:12:41
4
+ @author: Whenxuan Wang
5
+ @email: wwhenxuan@gmail.com
6
+ @url: https://github.com/wwhenxuan/S2Generator
7
+ """
8
+
9
+ from typing import Optional
10
+
11
+ import numpy as np
12
+
13
+
14
+ def wiener_filter(
15
+ time_series: np.ndarray,
16
+ noise_variance: float = 1.0,
17
+ window_size: Optional[int] = None,
18
+ ) -> np.ndarray:
19
+ """
20
+ Implement Wiener filter to remove white noise from signals.
21
+
22
+ :param time_series: The input time series, a 1D numpy array representing the noisy signal.
23
+ :param noise_variance: The known variance of the white noise to be removed, default is 1.0
24
+ :param window_size: The window size for power spectral density estimation, optional
25
+ if None, it will use the entire signal length as the window size.
26
+
27
+ :return: The filtered time series, a 1D numpy array of the same length as the input series.
28
+ """
29
+ # Validate the input time series
30
+ time_series = np.asarray(time_series, dtype=np.float64)
31
+
32
+ # If window_size is not provided, use the length of the time series
33
+ if window_size is None:
34
+ window_size = len(time_series)
35
+
36
+ # Calculate the Fourier transform of the signal
37
+ signal_fft = np.fft.fft(time_series)
38
+
39
+ # Calculate the power spectral density (PSD) of the signal.
40
+ signal_psd = np.abs(signal_fft) ** 2 / len(time_series)
41
+
42
+ # Estimate the power spectrum of the original signal (noisy PSD - noise PSD)
43
+ # The power spectral density of noise is constant for white noise: noise variance
44
+ noise_psd = noise_variance
45
+ original_signal_psd_estimate = np.maximum(
46
+ signal_psd - noise_psd, 1e-10
47
+ ) # Avoid negative values
48
+
49
+ # Calculate the frequency domain response of the Wiener filter.
50
+ wiener_filter_freq = original_signal_psd_estimate / (
51
+ original_signal_psd_estimate + noise_psd
52
+ )
53
+
54
+ # Apply a filter and perform an inverse Fourier transform to return to the time domain.
55
+ filtered_fft = signal_fft * wiener_filter_freq
56
+
57
+ # Remove numerical error by taking the real part
58
+ filtered_signal = np.fft.ifft(filtered_fft).real
59
+
60
+ return filtered_signal
@@ -376,9 +376,9 @@ class NodeList(object):
376
376
  if xs.ndim > 1:
377
377
  # For multivariate case, keep other dimensions constant
378
378
  x_uniform_input = np.tile(np.mean(xs, axis=0), (n_integration_points, 1))
379
- x_uniform_input[:, 0] = (
380
- x_uniform # Replace first dimension with uniform grid
381
- )
379
+ x_uniform_input[
380
+ :, 0
381
+ ] = x_uniform # Replace first dimension with uniform grid
382
382
  else:
383
383
  x_uniform_input = x_uniform.reshape(-1, 1) # Ensure 2D array for val method
384
384
 
@@ -6,6 +6,8 @@ Created on 2026/02/13 13:04:42
6
6
  @url: https://github.com/wwhenxuan/S2Generator
7
7
  """
8
8
 
9
+ __all__ = ["ARIMASimulator", "WienerFilterSimulator"]
10
+
9
11
  from .arima import ARIMASimulator
10
12
 
11
13
  from .wiener_filter import WienerFilterSimulator