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.
- {s2generator-0.0.7 → s2generator-0.0.9}/PKG-INFO +1 -1
- {s2generator-0.0.7 → s2generator-0.0.9}/S2Generator.egg-info/PKG-INFO +1 -1
- {s2generator-0.0.7 → s2generator-0.0.9}/S2Generator.egg-info/SOURCES.txt +11 -1
- {s2generator-0.0.7 → s2generator-0.0.9}/s2generator/__init__.py +2 -1
- s2generator-0.0.9/s2generator/augmentation/__init__.py +39 -0
- s2generator-0.0.9/s2generator/augmentation/_amplitude_modulation.py +103 -0
- s2generator-0.0.9/s2generator/augmentation/_censor_augmentation.py +74 -0
- s2generator-0.0.9/s2generator/augmentation/_empirical_mode_modulation.py +126 -0
- s2generator-0.0.9/s2generator/augmentation/_frequency_perturbation.py +104 -0
- s2generator-0.0.9/s2generator/augmentation/_spike_injection.py +16 -0
- s2generator-0.0.9/s2generator/augmentation/_time_transformation.py +68 -0
- s2generator-0.0.9/s2generator/augmentation/_wiener_filter.py +60 -0
- {s2generator-0.0.7 → s2generator-0.0.9}/s2generator/base.py +3 -3
- {s2generator-0.0.7 → s2generator-0.0.9}/s2generator/simulator/__init__.py +2 -0
- {s2generator-0.0.7 → s2generator-0.0.9}/s2generator/simulator/wiener_filter.py +47 -2
- {s2generator-0.0.7 → s2generator-0.0.9}/s2generator/utils/__init__.py +10 -0
- {s2generator-0.0.7 → s2generator-0.0.9}/s2generator/utils/_tools.py +76 -0
- {s2generator-0.0.7 → s2generator-0.0.9}/s2generator/utils/visualization.py +0 -1
- {s2generator-0.0.7 → s2generator-0.0.9}/setup.py +1 -1
- s2generator-0.0.9/tests/test_augmentation.py +141 -0
- s2generator-0.0.9/tests/test_wiener_filter_simulator.py +203 -0
- {s2generator-0.0.7 → s2generator-0.0.9}/LICENSE +0 -0
- {s2generator-0.0.7 → s2generator-0.0.9}/README.md +0 -0
- {s2generator-0.0.7 → s2generator-0.0.9}/S2Generator.egg-info/dependency_links.txt +0 -0
- {s2generator-0.0.7 → s2generator-0.0.9}/S2Generator.egg-info/requires.txt +0 -0
- {s2generator-0.0.7 → s2generator-0.0.9}/S2Generator.egg-info/top_level.txt +0 -0
- {s2generator-0.0.7 → s2generator-0.0.9}/s2generator/encoders.py +0 -0
- {s2generator-0.0.7 → s2generator-0.0.9}/s2generator/excitation/__init__.py +0 -0
- {s2generator-0.0.7 → s2generator-0.0.9}/s2generator/excitation/_interface.py +0 -0
- {s2generator-0.0.7 → s2generator-0.0.9}/s2generator/excitation/autoregressive_moving_average.py +0 -0
- {s2generator-0.0.7 → s2generator-0.0.9}/s2generator/excitation/base_excitation.py +0 -0
- {s2generator-0.0.7 → s2generator-0.0.9}/s2generator/excitation/forecast_pfn.py +0 -0
- {s2generator-0.0.7 → s2generator-0.0.9}/s2generator/excitation/intrinsic_mode_functions.py +0 -0
- {s2generator-0.0.7 → s2generator-0.0.9}/s2generator/excitation/kernel_synth.py +0 -0
- {s2generator-0.0.7 → s2generator-0.0.9}/s2generator/excitation/mixed_distribution.py +0 -0
- {s2generator-0.0.7 → s2generator-0.0.9}/s2generator/generators.py +0 -0
- {s2generator-0.0.7 → s2generator-0.0.9}/s2generator/params/__init__.py +0 -0
- {s2generator-0.0.7 → s2generator-0.0.9}/s2generator/params/series_params.py +0 -0
- {s2generator-0.0.7 → s2generator-0.0.9}/s2generator/params/symbol_params.py +0 -0
- {s2generator-0.0.7 → s2generator-0.0.9}/s2generator/simulator/arima.py +0 -0
- {s2generator-0.0.7 → s2generator-0.0.9}/s2generator/simulator/kalman_filtering.py +0 -0
- {s2generator-0.0.7 → s2generator-0.0.9}/s2generator/utils/_decomposition.py +0 -0
- {s2generator-0.0.7 → s2generator-0.0.9}/s2generator/utils/_print_status.py +0 -0
- {s2generator-0.0.7 → s2generator-0.0.9}/s2generator/utils/_smooth.py +0 -0
- {s2generator-0.0.7 → s2generator-0.0.9}/s2generator/utils/_wasserstein_distance.py +0 -0
- {s2generator-0.0.7 → s2generator-0.0.9}/s2generator/utils/print_symbol.py +0 -0
- {s2generator-0.0.7 → s2generator-0.0.9}/setup.cfg +0 -0
- {s2generator-0.0.7 → s2generator-0.0.9}/tests/__init__.py +0 -0
- {s2generator-0.0.7 → s2generator-0.0.9}/tests/test_all.py +0 -0
- {s2generator-0.0.7 → s2generator-0.0.9}/tests/test_arma.py +0 -0
- {s2generator-0.0.7 → s2generator-0.0.9}/tests/test_base_excitation.py +0 -0
- {s2generator-0.0.7 → s2generator-0.0.9}/tests/test_forecast_pfn.py +0 -0
- {s2generator-0.0.7 → s2generator-0.0.9}/tests/test_intrinsic_mode_functions.py +0 -0
- {s2generator-0.0.7 → s2generator-0.0.9}/tests/test_kernel_synth.py +0 -0
- {s2generator-0.0.7 → s2generator-0.0.9}/tests/test_mixed_distribution.py +0 -0
- {s2generator-0.0.7 → s2generator-0.0.9}/tests/test_series_params.py +0 -0
- {s2generator-0.0.7 → s2generator-0.0.9}/tests/test_stl.py +0 -0
- {s2generator-0.0.7 → s2generator-0.0.9}/tests/test_symbol_params.py +0 -0
- {s2generator-0.0.7 → s2generator-0.0.9}/tests/test_tools.py +0 -0
- {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.
|
|
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.
|
|
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
|
|
@@ -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[
|
|
380
|
-
|
|
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
|
|