PySDKit 0.4.33__tar.gz → 0.4.34__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.
- {pysdkit-0.4.33 → pysdkit-0.4.34}/PKG-INFO +1 -1
- {pysdkit-0.4.33 → pysdkit-0.4.34}/PySDKit.egg-info/PKG-INFO +1 -1
- {pysdkit-0.4.33 → pysdkit-0.4.34}/PySDKit.egg-info/SOURCES.txt +4 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/__init__.py +7 -1
- pysdkit-0.4.34/pysdkit/_gdmd/__init__.py +32 -0
- pysdkit-0.4.34/pysdkit/_gdmd/gdmd.py +467 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/_ssa/ssa.py +43 -16
- pysdkit-0.4.34/pysdkit/tests/test_gdmd.py +160 -0
- pysdkit-0.4.34/pysdkit/tests/test_ssa.py +153 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/tsa/_stl.py +2 -2
- {pysdkit-0.4.33 → pysdkit-0.4.34}/LICENSE +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/PySDKit.egg-info/dependency_links.txt +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/PySDKit.egg-info/requires.txt +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/PySDKit.egg-info/top_level.txt +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/README.md +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pyproject.toml +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/_alif/__init__.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/_alif/alif.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/_alif/data/__init__.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/_alif/data/prefixed_double_filter.npy +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/_alif/iterative_filtering.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/_apmd/__init__.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/_apmd/apmd.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/_emd/__init__.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/_emd/_find_extrema.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/_emd/_prepare_points.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/_emd/_splines.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/_emd/ceemdan.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/_emd/eemd.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/_emd/efd.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/_emd/emd.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/_emd/hht/__init__.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/_emd/hht/frequency.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/_emd/hht/hht.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/_emd/memd.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/_emd/remd.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/_emd/tvf_emd.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/_emd2d/__init__.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/_emd2d/bemd.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/_emd2d/bmemd.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/_emd2d/emd2d.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/_esmd/__init__.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/_esmd/esmd.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/_ewt/__init__.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/_ewt/ewt.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/_ewt/ewt2d.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/_faemd/__init__.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/_faemd/extrema.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/_faemd/faemd.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/_faemd/faemd2d.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/_faemd/faemd3d.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/_faemd/filter.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/_fmd/__init__.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/_fmd/fmd.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/_fmd/nfmd.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/_hvd/__init__.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/_hvd/hvd.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/_itd/__init__.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/_itd/itd.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/_jmd/__init__.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/_jmd/jmd.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/_jmd/mjmd.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/_jmd/sjmd.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/_lmd/__init__.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/_lmd/lmd.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/_lmd/rlmd.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/_osd/__init__.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/_osd/swd.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/_ssa/__init__.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/_vmd/__init__.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/_vmd/acmd.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/_vmd/avmd.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/_vmd/ba_acmd.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/_vmd/base.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/_vmd/mvmd.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/_vmd/ovmd.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/_vmd/stvmd.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/_vmd/svmd.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/_vmd/vmd_c.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/_vmd/vmd_f.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/_vmd/vme.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/_vmd2d/__init__.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/_vmd2d/cvmd2d.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/_vmd2d/vmd2d.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/_vncmd/__init__.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/_vncmd/avncmd.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/_vncmd/incmd.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/_vncmd/vncmd.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/data/__init__.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/data/_add_noise.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/data/_cube.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/data/_generator.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/data/_image.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/data/_models.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/data/_test_univariate.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/data/_time_series.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/data/texture.txt +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/entropy/__init__.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/entropy/_approxiamte_entropy.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/entropy/_permutation_entropy.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/entropy/_sample_entropy.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/models/__init__.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/models/_base.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/models/_kmeans.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/models/_knn.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/models/_pca.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/plot/__init__.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/plot/_fourier_spectra.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/plot/_functions.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/plot/_plot_images.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/plot/_plot_imfs.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/plot/_plot_signal.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/tests/__init__.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/tests/data/__init__.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/tests/data/test_generator_cube.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/tests/data/test_generator_image.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/tests/data/test_generator_signal.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/tests/data/test_generator_univariate_signal.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/tests/models/__init__.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/tests/models/test_knn.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/tests/models/test_pca.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/tests/run_all.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/tests/test_alif.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/tests/test_apmd.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/tests/test_avncmd.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/tests/test_ceemdan.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/tests/test_emd.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/tests/test_ewt.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/tests/test_ewt2d.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/tests/test_faemd.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/tests/test_faemd2d.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/tests/test_faemd3d.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/tests/test_itd.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/tests/test_jmd.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/tests/test_lmd.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/tests/test_moving_decomp.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/tests/test_ovmd.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/tests/test_rlmd.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/tests/test_sjmd.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/tests/test_stl.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/tests/test_stvmd.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/tests/test_svmd.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/tests/test_swd.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/tests/test_vmd.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/tests/test_vmd2d.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/tests/test_vncmd.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/tsa/__init__.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/tsa/_dtw.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/tsa/_moving_decomp.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/tsa/_mstl.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/utils/__init__.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/utils/_cite.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/utils/_correlation.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/utils/_diagnalization.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/utils/_differ.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/utils/_fft.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/utils/_function.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/utils/_hilbert.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/utils/_instantaneous.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/utils/_kernel_matrix.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/utils/_mirror.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/utils/_process.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/pysdkit/utils/_smooth1d.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.34}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: PySDKit
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.34
|
|
4
4
|
Summary: A Python library for signal decomposition algorithms with a unified interface.
|
|
5
5
|
Author: josefinez, Deeksha Manjunath, Yuan Feng, JacktheFowler
|
|
6
6
|
Author-email: Whenxuan Wang <wwhenxuan@gmail.com>, RuiZhe Wang <3133986068@qq.com>, WenTong Zhao <23049200290@stu.xidian.edu.cn>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: PySDKit
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.34
|
|
4
4
|
Summary: A Python library for signal decomposition algorithms with a unified interface.
|
|
5
5
|
Author: josefinez, Deeksha Manjunath, Yuan Feng, JacktheFowler
|
|
6
6
|
Author-email: Whenxuan Wang <wwhenxuan@gmail.com>, RuiZhe Wang <3133986068@qq.com>, WenTong Zhao <23049200290@stu.xidian.edu.cn>
|
|
@@ -46,6 +46,8 @@ pysdkit/_faemd/filter.py
|
|
|
46
46
|
pysdkit/_fmd/__init__.py
|
|
47
47
|
pysdkit/_fmd/fmd.py
|
|
48
48
|
pysdkit/_fmd/nfmd.py
|
|
49
|
+
pysdkit/_gdmd/__init__.py
|
|
50
|
+
pysdkit/_gdmd/gdmd.py
|
|
49
51
|
pysdkit/_hvd/__init__.py
|
|
50
52
|
pysdkit/_hvd/hvd.py
|
|
51
53
|
pysdkit/_itd/__init__.py
|
|
@@ -116,6 +118,7 @@ pysdkit/tests/test_ewt2d.py
|
|
|
116
118
|
pysdkit/tests/test_faemd.py
|
|
117
119
|
pysdkit/tests/test_faemd2d.py
|
|
118
120
|
pysdkit/tests/test_faemd3d.py
|
|
121
|
+
pysdkit/tests/test_gdmd.py
|
|
119
122
|
pysdkit/tests/test_itd.py
|
|
120
123
|
pysdkit/tests/test_jmd.py
|
|
121
124
|
pysdkit/tests/test_lmd.py
|
|
@@ -123,6 +126,7 @@ pysdkit/tests/test_moving_decomp.py
|
|
|
123
126
|
pysdkit/tests/test_ovmd.py
|
|
124
127
|
pysdkit/tests/test_rlmd.py
|
|
125
128
|
pysdkit/tests/test_sjmd.py
|
|
129
|
+
pysdkit/tests/test_ssa.py
|
|
126
130
|
pysdkit/tests/test_stl.py
|
|
127
131
|
pysdkit/tests/test_stvmd.py
|
|
128
132
|
pysdkit/tests/test_svmd.py
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
A Python library for signal decomposition algorithms.
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
-
__version__ = "0.4.
|
|
5
|
+
__version__ = "0.4.34"
|
|
6
6
|
|
|
7
7
|
# Empirical Mode Decomposition
|
|
8
8
|
from ._emd import EMD
|
|
@@ -49,6 +49,9 @@ from ._ssa import SSA
|
|
|
49
49
|
# Swarm Decomposition
|
|
50
50
|
from ._osd import SWD, swd
|
|
51
51
|
|
|
52
|
+
# Generalized Dispersion Mode Decomposition
|
|
53
|
+
from ._gdmd import GDMD, gdmd
|
|
54
|
+
|
|
52
55
|
# Variational Mode Decomposition
|
|
53
56
|
from ._vmd import vmd, VMD
|
|
54
57
|
|
|
@@ -157,6 +160,7 @@ Local Mean Decomposition | LMD
|
|
|
157
160
|
Robust Local Mean Decomposition | RLMD
|
|
158
161
|
Singular Spectral Analysis | SSA
|
|
159
162
|
Swarm Decomposition | SWD
|
|
163
|
+
Generalized Dispersion Mode Decomposition | GDMD
|
|
160
164
|
Variational Mode Decomposition | VMD
|
|
161
165
|
Multivariate Variational Mode Decomposition | MVMD
|
|
162
166
|
Variational Mode Extraction | VME
|
|
@@ -201,6 +205,8 @@ __all__ = [
|
|
|
201
205
|
"SSA",
|
|
202
206
|
"SWD",
|
|
203
207
|
"swd",
|
|
208
|
+
"GDMD",
|
|
209
|
+
"gdmd",
|
|
204
210
|
"vmd",
|
|
205
211
|
"VMD",
|
|
206
212
|
"ACMD",
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
Created on 2025/07/31
|
|
4
|
+
@author: Whenxuan Wang
|
|
5
|
+
@email: wwhenxuan@gmail.com
|
|
6
|
+
|
|
7
|
+
Generalized Dispersion Mode Decomposition (GDMD).
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from .gdmd import (
|
|
11
|
+
GDMD,
|
|
12
|
+
gdmd,
|
|
13
|
+
gdmd_core,
|
|
14
|
+
curve_smooth,
|
|
15
|
+
differ,
|
|
16
|
+
make_dispersive_signal,
|
|
17
|
+
spectrum_to_time,
|
|
18
|
+
unilateral_spectrum,
|
|
19
|
+
tf_spec_from_gd,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
__all__ = [
|
|
23
|
+
"GDMD",
|
|
24
|
+
"gdmd",
|
|
25
|
+
"gdmd_core",
|
|
26
|
+
"curve_smooth",
|
|
27
|
+
"differ",
|
|
28
|
+
"make_dispersive_signal",
|
|
29
|
+
"spectrum_to_time",
|
|
30
|
+
"unilateral_spectrum",
|
|
31
|
+
"tf_spec_from_gd",
|
|
32
|
+
]
|
|
@@ -0,0 +1,467 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
Created on 2025/07/31
|
|
4
|
+
@author: Whenxuan Wang
|
|
5
|
+
@email: wwhenxuan@gmail.com
|
|
6
|
+
|
|
7
|
+
Generalized Dispersion Mode Decomposition (GDMD).
|
|
8
|
+
|
|
9
|
+
GDMD is a variational algorithm for separating wideband, dispersive /
|
|
10
|
+
impulse-like modes. Working in the *frequency* domain, it models each
|
|
11
|
+
mode through a slowly varying group-delay (GD) curve and recovers the
|
|
12
|
+
modes by iterative demodulation, analogous to Adaptive Chirp Mode
|
|
13
|
+
Decomposition (ACMD) in the time domain.
|
|
14
|
+
|
|
15
|
+
Chen S, Wang K, Peng Z, et al.
|
|
16
|
+
Generalized dispersive mode decomposition: Algorithm and applications.
|
|
17
|
+
Journal of Sound and Vibration, 2020.
|
|
18
|
+
|
|
19
|
+
MATLAB reference:
|
|
20
|
+
https://uk.mathworks.com/matlabcentral/fileexchange/81823-generalized-dispersive-mode-decomposition-gdmd?s_tid=FX_rc2_behav
|
|
21
|
+
"""
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
from typing import List, Optional, Tuple, Union
|
|
25
|
+
|
|
26
|
+
import numpy as np
|
|
27
|
+
from numpy.linalg import norm
|
|
28
|
+
from scipy.integrate import cumulative_trapezoid
|
|
29
|
+
from scipy.signal import hilbert
|
|
30
|
+
from scipy.sparse import block_diag, diags, eye, lil_matrix
|
|
31
|
+
from scipy.sparse.linalg import spsolve
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def differ(y: np.ndarray, delta: float) -> np.ndarray:
|
|
35
|
+
"""
|
|
36
|
+
Discrete derivative of a 1-D series (central difference interior).
|
|
37
|
+
|
|
38
|
+
Matches the MATLAB helper ``Differ.m``.
|
|
39
|
+
"""
|
|
40
|
+
y = np.asarray(y, dtype=float).ravel()
|
|
41
|
+
L = y.size
|
|
42
|
+
if L < 2:
|
|
43
|
+
return np.zeros_like(y)
|
|
44
|
+
ybar = np.empty(L, dtype=float)
|
|
45
|
+
ybar[0] = (y[1] - y[0]) / delta
|
|
46
|
+
ybar[-1] = (y[-1] - y[-2]) / delta
|
|
47
|
+
if L > 2:
|
|
48
|
+
ybar[1:-1] = (y[2:] - y[:-2]) / (2.0 * delta)
|
|
49
|
+
return ybar
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def curve_smooth(curves: np.ndarray, beta: float) -> np.ndarray:
|
|
53
|
+
"""
|
|
54
|
+
Smooth IF / GD curves with second-order difference regularisation.
|
|
55
|
+
|
|
56
|
+
Matches MATLAB ``curvesmooth.m``. Smaller ``beta`` → smoother output.
|
|
57
|
+
"""
|
|
58
|
+
f = np.atleast_2d(np.asarray(curves, dtype=float))
|
|
59
|
+
K, N = f.shape
|
|
60
|
+
e = np.ones(N)
|
|
61
|
+
oper = diags([e[:-2], -2.0 * e[1:-1], e[2:]], [0, 1, 2], shape=(N - 2, N))
|
|
62
|
+
opedoub = (oper.T @ oper).tocsc()
|
|
63
|
+
A = (2.0 / beta) * opedoub + eye(N, format="csc")
|
|
64
|
+
out = np.empty_like(f)
|
|
65
|
+
for i in range(K):
|
|
66
|
+
out[i] = spsolve(A, f[i])
|
|
67
|
+
return out
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def second_order_difference(N: int):
|
|
71
|
+
"""Build the ``(N-2) x N`` second-order difference matrix."""
|
|
72
|
+
e = np.ones(N)
|
|
73
|
+
return diags([e[:-2], -2.0 * e[1:-1], e[2:]], [0, 1, 2], shape=(N - 2, N))
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def unilateral_spectrum(signal: np.ndarray) -> np.ndarray:
|
|
77
|
+
"""Return the unilateral (non-negative) FFT of a real time signal."""
|
|
78
|
+
x = np.asarray(signal, dtype=float).ravel()
|
|
79
|
+
n = x.size
|
|
80
|
+
nf = n // 2 + 1
|
|
81
|
+
return np.fft.fft(x)[:nf]
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def spectrum_to_time(spectrum: np.ndarray, n_time: int) -> np.ndarray:
|
|
85
|
+
"""
|
|
86
|
+
Reconstruct a real time-domain signal from a unilateral spectrum.
|
|
87
|
+
|
|
88
|
+
Mirrors the MATLAB pattern::
|
|
89
|
+
|
|
90
|
+
full = [S, conj(fliplr(S(2:ceil(Nt/2))))]; ifft(full)
|
|
91
|
+
"""
|
|
92
|
+
S = np.asarray(spectrum, dtype=complex).ravel()
|
|
93
|
+
nf = n_time // 2 + 1
|
|
94
|
+
if S.size != nf:
|
|
95
|
+
raise ValueError(
|
|
96
|
+
f"unilateral spectrum length {S.size} incompatible with n_time={n_time} "
|
|
97
|
+
f"(expected {nf})"
|
|
98
|
+
)
|
|
99
|
+
full = np.concatenate([S, np.conj(S[-2:0:-1])])
|
|
100
|
+
return np.real(np.fft.ifft(full))
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def make_dispersive_signal(
|
|
104
|
+
samp_freq: float = 100.0,
|
|
105
|
+
duration: float = 15.0,
|
|
106
|
+
) -> Tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray, np.ndarray, np.ndarray]:
|
|
107
|
+
"""
|
|
108
|
+
Synthetic three-mode dispersive signal from the GDMD paper (Example 1).
|
|
109
|
+
|
|
110
|
+
:return: ``(t, signal, f, spectrum, true_gds, true_modes_time)``
|
|
111
|
+
- ``true_gds`` shape ``(3, Nf)``
|
|
112
|
+
- ``true_modes_time`` shape ``(3, Nt)``
|
|
113
|
+
- ``spectrum`` is the sum of unilateral mode spectra
|
|
114
|
+
"""
|
|
115
|
+
nt = int(round(samp_freq * duration))
|
|
116
|
+
nf = nt // 2 + 1
|
|
117
|
+
t = np.arange(nt) / samp_freq
|
|
118
|
+
f = np.arange(nf) / duration
|
|
119
|
+
|
|
120
|
+
gd1 = -1.0 / 125.0 * f**2 + 2.0 / 5.0 * f + 6.0
|
|
121
|
+
gd2 = 1.0 / 125.0 * f**2 - 2.0 / 5.0 * f + 10.5
|
|
122
|
+
gd3 = -1.0 / 250.0 * f**2 + 12.0
|
|
123
|
+
true_gds = np.vstack([gd1, gd2, gd3])
|
|
124
|
+
|
|
125
|
+
ds1 = 1.5 * np.exp(
|
|
126
|
+
-1j * 2 * np.pi * (-1.0 / 375.0 * f**3 + 1.0 / 5.0 * f**2 + 6.0 * f + 0.3)
|
|
127
|
+
)
|
|
128
|
+
ds2 = (1.0 + 0.2 * np.cos(2 * np.pi * 2.0 / 50.0 * f)) * np.exp(
|
|
129
|
+
-1j * 2 * np.pi * (1.0 / 375.0 * f**3 - 1.0 / 5.0 * f**2 + 10.5 * f + 0.5)
|
|
130
|
+
)
|
|
131
|
+
ds3 = (1.0 + 0.2 * np.sin(2 * np.pi * 2.0 / 50.0 * f)) * np.exp(
|
|
132
|
+
-1j * 2 * np.pi * (-1.0 / 750.0 * f**3 + 12.0 * f + 0.8)
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
modes_time = np.vstack(
|
|
136
|
+
[
|
|
137
|
+
spectrum_to_time(ds1, nt),
|
|
138
|
+
spectrum_to_time(ds2, nt),
|
|
139
|
+
spectrum_to_time(ds3, nt),
|
|
140
|
+
]
|
|
141
|
+
)
|
|
142
|
+
signal = np.real(modes_time.sum(axis=0))
|
|
143
|
+
spectrum = ds1 + ds2 + ds3
|
|
144
|
+
return t, signal, f, spectrum, true_gds, modes_time
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def tf_spec_from_gd(
|
|
148
|
+
gd_multi: np.ndarray,
|
|
149
|
+
ia_multi: np.ndarray,
|
|
150
|
+
time_range: Tuple[float, float],
|
|
151
|
+
n_time_bins: int = 1024,
|
|
152
|
+
) -> Tuple[np.ndarray, np.ndarray]:
|
|
153
|
+
"""
|
|
154
|
+
Build a simple TF image from estimated GDs and amplitudes (``TFspec.m``).
|
|
155
|
+
|
|
156
|
+
:return: ``(ASpec, t_bins)`` with ``ASpec`` shaped ``(Nf, n_time_bins)``.
|
|
157
|
+
"""
|
|
158
|
+
gd = np.atleast_2d(np.asarray(gd_multi, dtype=float))
|
|
159
|
+
ia = np.atleast_2d(np.asarray(ia_multi, dtype=float))
|
|
160
|
+
num, n_freq = gd.shape
|
|
161
|
+
t_bins = np.linspace(time_range[0], time_range[1], n_time_bins)
|
|
162
|
+
a_spec = np.zeros((n_time_bins, n_freq), dtype=float)
|
|
163
|
+
delta = max(1, int(n_time_bins * 0.001))
|
|
164
|
+
for kk in range(num):
|
|
165
|
+
for ii in range(n_freq):
|
|
166
|
+
idx = int(np.argmin(np.abs(t_bins - gd[kk, ii])))
|
|
167
|
+
lo = max(idx - delta, 0)
|
|
168
|
+
hi = min(idx + delta, n_time_bins - 1)
|
|
169
|
+
a_spec[lo : hi + 1, ii] = ia[kk, ii]
|
|
170
|
+
return a_spec.T, t_bins
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def gdmd_core(
|
|
174
|
+
spectrum: np.ndarray,
|
|
175
|
+
duration: float,
|
|
176
|
+
init_gd: np.ndarray,
|
|
177
|
+
alpha: float = 1e-3,
|
|
178
|
+
beta: float = 1e-7,
|
|
179
|
+
tol: float = 1e-8,
|
|
180
|
+
max_iter: int = 300,
|
|
181
|
+
) -> Tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]:
|
|
182
|
+
"""
|
|
183
|
+
Core GDMD solver (MATLAB ``GDMD.m``).
|
|
184
|
+
|
|
185
|
+
:param spectrum: unilateral FFT spectrum, shape ``(N,)`` (complex)
|
|
186
|
+
:param duration: time duration ``T`` of the original signal
|
|
187
|
+
:param init_gd: initial group delays, shape ``(K, N)``
|
|
188
|
+
:param alpha: bandwidth / roughness trade-off (smaller → narrower band)
|
|
189
|
+
:param beta: GD-increment smoothness (smaller → smoother)
|
|
190
|
+
:param tol: relative convergence tolerance on recovered spectra
|
|
191
|
+
:param max_iter: maximum ADMM / demodulation iterations
|
|
192
|
+
:return: ``(gd_final, modes_freq, gd_history, modes_history)``
|
|
193
|
+
- ``gd_final`` ``(K, N)``
|
|
194
|
+
- ``modes_freq`` ``(K, N)`` complex unilateral spectra
|
|
195
|
+
- histories have shape ``(K, N, n_iters)``
|
|
196
|
+
"""
|
|
197
|
+
s = np.asarray(spectrum, dtype=complex).ravel()
|
|
198
|
+
e_gd = np.atleast_2d(np.asarray(init_gd, dtype=float)).copy()
|
|
199
|
+
num, n = e_gd.shape
|
|
200
|
+
if s.size != n:
|
|
201
|
+
raise ValueError(f"spectrum length {s.size} must match init_gd columns {n}")
|
|
202
|
+
if duration <= 0:
|
|
203
|
+
raise ValueError("duration (T) must be positive")
|
|
204
|
+
|
|
205
|
+
freq = np.arange(n, dtype=float) / duration
|
|
206
|
+
df = 1.0 / duration
|
|
207
|
+
|
|
208
|
+
oper = second_order_difference(n)
|
|
209
|
+
opedoub = (oper.T @ oper).tocsc()
|
|
210
|
+
phim = block_diag([oper] * num).tocsc()
|
|
211
|
+
phidoubm = (phim.T @ phim).tocsc()
|
|
212
|
+
|
|
213
|
+
gd_hist: List[np.ndarray] = []
|
|
214
|
+
s_hist: List[np.ndarray] = []
|
|
215
|
+
|
|
216
|
+
s_dif = tol + 1.0
|
|
217
|
+
it = 0
|
|
218
|
+
s_prev = None
|
|
219
|
+
|
|
220
|
+
while s_dif > tol and it < max_iter:
|
|
221
|
+
# Kernel matrix K: N x (N*num), block-diagonal complex phase kernels
|
|
222
|
+
kerm = lil_matrix((n, n * num), dtype=complex)
|
|
223
|
+
for kk in range(num):
|
|
224
|
+
phase = cumulative_trapezoid(e_gd[kk], freq, initial=0.0)
|
|
225
|
+
kern = np.exp(-1j * 2.0 * np.pi * phase)
|
|
226
|
+
kerm[:, kk * n : (kk + 1) * n] = diags([kern], [0], shape=(n, n))
|
|
227
|
+
kerm = kerm.tocsc()
|
|
228
|
+
kerdoubm = (kerm.T.conj() @ kerm).tocsc()
|
|
229
|
+
|
|
230
|
+
# Demodulated signals: (1/alpha Φ'Φ + K'K) y = K' s
|
|
231
|
+
A = (1.0 / alpha) * phidoubm + kerdoubm
|
|
232
|
+
ym_all = spsolve(A, kerm.T.conj() @ s)
|
|
233
|
+
|
|
234
|
+
modes_iter = np.empty((num, n), dtype=complex)
|
|
235
|
+
for kk in range(num):
|
|
236
|
+
ym = ym_all[kk * n : (kk + 1) * n]
|
|
237
|
+
delta_phase = np.unwrap(np.angle(ym))
|
|
238
|
+
delta_gd = differ(delta_phase, df) / (2.0 * np.pi)
|
|
239
|
+
delta_gd = spsolve((1.0 / beta) * opedoub + eye(n, format="csc"), delta_gd)
|
|
240
|
+
e_gd[kk] = e_gd[kk] - delta_gd
|
|
241
|
+
modes_iter[kk] = kerm[:, kk * n : (kk + 1) * n] @ ym
|
|
242
|
+
|
|
243
|
+
gd_hist.append(e_gd.copy())
|
|
244
|
+
s_hist.append(modes_iter)
|
|
245
|
+
|
|
246
|
+
if s_prev is not None:
|
|
247
|
+
s_dif = 0.0
|
|
248
|
+
for kk in range(num):
|
|
249
|
+
den = norm(s_prev[kk])
|
|
250
|
+
if den < 1e-30:
|
|
251
|
+
continue
|
|
252
|
+
s_dif += (norm(modes_iter[kk] - s_prev[kk]) / den) ** 2
|
|
253
|
+
s_prev = modes_iter
|
|
254
|
+
it += 1
|
|
255
|
+
|
|
256
|
+
if it == 0:
|
|
257
|
+
raise RuntimeError("GDMD failed to perform any iteration")
|
|
258
|
+
|
|
259
|
+
gd_history = np.stack(gd_hist, axis=-1)
|
|
260
|
+
modes_history = np.stack(s_hist, axis=-1)
|
|
261
|
+
return e_gd, s_hist[-1], gd_history, modes_history
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
class GDMD(object):
|
|
265
|
+
"""
|
|
266
|
+
Generalized Dispersion Mode Decomposition (GDMD).
|
|
267
|
+
|
|
268
|
+
Two usage patterns (matching the MATLAB examples):
|
|
269
|
+
|
|
270
|
+
1. **Joint multi-mode** — supply initial GD curves ``init_gd`` of shape
|
|
271
|
+
``(K, Nf)`` and decompose all modes together (Example 1).
|
|
272
|
+
2. **Successive extraction** — set ``K`` and leave ``init_gd=None``; each
|
|
273
|
+
mode is initialised from the envelope peak of the current residual
|
|
274
|
+
(Example 2, impulse / bearing signals).
|
|
275
|
+
"""
|
|
276
|
+
|
|
277
|
+
def __init__(
|
|
278
|
+
self,
|
|
279
|
+
alpha: float = 1e-3,
|
|
280
|
+
beta: float = 1e-7,
|
|
281
|
+
tol: float = 1e-8,
|
|
282
|
+
max_iter: int = 300,
|
|
283
|
+
K: Optional[int] = None,
|
|
284
|
+
) -> None:
|
|
285
|
+
"""
|
|
286
|
+
:param alpha: filtering bandwidth; larger helps rough GD initialisations
|
|
287
|
+
:param beta: GD-increment smoothness; smaller → smoother / easier converge
|
|
288
|
+
:param tol: convergence tolerance (e.g. 1e-7 … 1e-9)
|
|
289
|
+
:param max_iter: maximum iterations of the demodulation loop
|
|
290
|
+
:param K: number of modes for successive extraction (ignored if
|
|
291
|
+
``init_gd`` is provided to ``fit_transform``)
|
|
292
|
+
"""
|
|
293
|
+
self.alpha = float(alpha)
|
|
294
|
+
self.beta = float(beta)
|
|
295
|
+
self.tol = float(tol)
|
|
296
|
+
self.max_iter = int(max_iter)
|
|
297
|
+
self.K = K
|
|
298
|
+
|
|
299
|
+
self.modes_freq_: Optional[np.ndarray] = None
|
|
300
|
+
self.modes_time_: Optional[np.ndarray] = None
|
|
301
|
+
self.group_delays_: Optional[np.ndarray] = None
|
|
302
|
+
self.residual_: Optional[np.ndarray] = None
|
|
303
|
+
|
|
304
|
+
def __call__(
|
|
305
|
+
self,
|
|
306
|
+
signal: np.ndarray,
|
|
307
|
+
fs: Optional[float] = None,
|
|
308
|
+
init_gd: Optional[np.ndarray] = None,
|
|
309
|
+
return_all: bool = False,
|
|
310
|
+
) -> Union[np.ndarray, Tuple[np.ndarray, ...]]:
|
|
311
|
+
return self.fit_transform(signal, fs=fs, init_gd=init_gd, return_all=return_all)
|
|
312
|
+
|
|
313
|
+
def __str__(self) -> str:
|
|
314
|
+
return "Generalized Dispersion Mode Decomposition (GDMD)"
|
|
315
|
+
|
|
316
|
+
def fit_transform(
|
|
317
|
+
self,
|
|
318
|
+
signal: np.ndarray,
|
|
319
|
+
fs: Optional[float] = None,
|
|
320
|
+
init_gd: Optional[np.ndarray] = None,
|
|
321
|
+
return_all: bool = False,
|
|
322
|
+
smooth_init_beta: Optional[float] = 1e-7,
|
|
323
|
+
) -> Union[np.ndarray, Tuple[np.ndarray, ...]]:
|
|
324
|
+
"""
|
|
325
|
+
Decompose a real time-domain signal with GDMD.
|
|
326
|
+
|
|
327
|
+
:param signal: 1-D real signal
|
|
328
|
+
:param fs: sampling frequency (Hz). If ``None``, ``fs = len(signal)``
|
|
329
|
+
so that the duration is 1 second (consistent with ACMD).
|
|
330
|
+
:param init_gd: optional initial GDs ``(K, Nf)`` in seconds. When
|
|
331
|
+
given, all ``K`` modes are estimated jointly.
|
|
332
|
+
:param return_all: if True, also return GDs and frequency-domain modes
|
|
333
|
+
:param smooth_init_beta: if not ``None``, smooth ``init_gd`` with
|
|
334
|
+
:func:`curve_smooth` before the solver
|
|
335
|
+
:return: time-domain modes ``(K, N)``, or
|
|
336
|
+
``(modes_time, group_delays, modes_freq)`` when ``return_all``
|
|
337
|
+
"""
|
|
338
|
+
x = np.asarray(signal, dtype=float).ravel()
|
|
339
|
+
if x.ndim != 1 or x.size < 8:
|
|
340
|
+
raise ValueError("signal must be a 1-D array with length >= 8")
|
|
341
|
+
|
|
342
|
+
n = x.size
|
|
343
|
+
fs_use = float(n if fs is None else fs)
|
|
344
|
+
if fs_use <= 0:
|
|
345
|
+
raise ValueError("fs must be positive")
|
|
346
|
+
duration = n / fs_use
|
|
347
|
+
nf = n // 2 + 1
|
|
348
|
+
spectrum = np.fft.fft(x)[:nf]
|
|
349
|
+
|
|
350
|
+
if init_gd is not None:
|
|
351
|
+
gd0 = np.atleast_2d(np.asarray(init_gd, dtype=float))
|
|
352
|
+
if gd0.shape[1] != nf:
|
|
353
|
+
raise ValueError(f"init_gd must have shape (K, {nf}), got {gd0.shape}")
|
|
354
|
+
if smooth_init_beta is not None:
|
|
355
|
+
gd0 = curve_smooth(gd0, smooth_init_beta)
|
|
356
|
+
gd_final, modes_f, _, _ = gdmd_core(
|
|
357
|
+
spectrum,
|
|
358
|
+
duration,
|
|
359
|
+
gd0,
|
|
360
|
+
alpha=self.alpha,
|
|
361
|
+
beta=self.beta,
|
|
362
|
+
tol=self.tol,
|
|
363
|
+
max_iter=self.max_iter,
|
|
364
|
+
)
|
|
365
|
+
modes_t = np.vstack([spectrum_to_time(m, n) for m in modes_f])
|
|
366
|
+
residual = x - modes_t.sum(axis=0)
|
|
367
|
+
else:
|
|
368
|
+
k = self.K
|
|
369
|
+
if k is None or k < 1:
|
|
370
|
+
raise ValueError(
|
|
371
|
+
"Provide init_gd for joint decomposition, or set K>=1 "
|
|
372
|
+
"for successive envelope-based extraction"
|
|
373
|
+
)
|
|
374
|
+
modes_t, gd_final, modes_f, residual = self._successive(
|
|
375
|
+
x, spectrum, duration, fs_use, k
|
|
376
|
+
)
|
|
377
|
+
|
|
378
|
+
self.modes_time_ = modes_t
|
|
379
|
+
self.modes_freq_ = modes_f
|
|
380
|
+
self.group_delays_ = gd_final
|
|
381
|
+
self.residual_ = residual
|
|
382
|
+
|
|
383
|
+
if return_all:
|
|
384
|
+
return modes_t, gd_final, modes_f
|
|
385
|
+
return modes_t
|
|
386
|
+
|
|
387
|
+
def _successive(
|
|
388
|
+
self,
|
|
389
|
+
signal: np.ndarray,
|
|
390
|
+
spectrum: np.ndarray,
|
|
391
|
+
duration: float,
|
|
392
|
+
fs: float,
|
|
393
|
+
K: int,
|
|
394
|
+
) -> Tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]:
|
|
395
|
+
"""Example-2 style successive GDMD with envelope-peak GD init."""
|
|
396
|
+
n = signal.size
|
|
397
|
+
nf = spectrum.size
|
|
398
|
+
t = np.arange(n) / fs
|
|
399
|
+
residual_t = signal.copy()
|
|
400
|
+
residual_f = spectrum.copy()
|
|
401
|
+
|
|
402
|
+
modes_t = np.zeros((K, n), dtype=float)
|
|
403
|
+
modes_f = np.zeros((K, nf), dtype=complex)
|
|
404
|
+
gds = np.zeros((K, nf), dtype=float)
|
|
405
|
+
|
|
406
|
+
for k in range(K):
|
|
407
|
+
envelope = np.abs(hilbert(residual_t))
|
|
408
|
+
peak_t = float(t[int(np.argmax(envelope))])
|
|
409
|
+
init_gd = peak_t * np.ones((1, nf))
|
|
410
|
+
gd_k, mode_f, _, _ = gdmd_core(
|
|
411
|
+
residual_f,
|
|
412
|
+
duration,
|
|
413
|
+
init_gd,
|
|
414
|
+
alpha=self.alpha,
|
|
415
|
+
beta=self.beta,
|
|
416
|
+
tol=self.tol,
|
|
417
|
+
max_iter=self.max_iter,
|
|
418
|
+
)
|
|
419
|
+
mode_t = spectrum_to_time(mode_f[0], n)
|
|
420
|
+
modes_t[k] = mode_t
|
|
421
|
+
modes_f[k] = mode_f[0]
|
|
422
|
+
gds[k] = gd_k[0]
|
|
423
|
+
residual_t = residual_t - mode_t
|
|
424
|
+
residual_f = residual_f - mode_f[0]
|
|
425
|
+
|
|
426
|
+
return modes_t, gds, modes_f, residual_t
|
|
427
|
+
|
|
428
|
+
def decompose_spectrum(
|
|
429
|
+
self,
|
|
430
|
+
spectrum: np.ndarray,
|
|
431
|
+
duration: float,
|
|
432
|
+
init_gd: np.ndarray,
|
|
433
|
+
) -> Tuple[np.ndarray, np.ndarray]:
|
|
434
|
+
"""
|
|
435
|
+
Direct frequency-domain interface matching MATLAB ``GDMD(...)``.
|
|
436
|
+
|
|
437
|
+
:return: ``(group_delays, modes_freq)`` final estimates
|
|
438
|
+
"""
|
|
439
|
+
gd, modes, _, _ = gdmd_core(
|
|
440
|
+
spectrum,
|
|
441
|
+
duration,
|
|
442
|
+
init_gd,
|
|
443
|
+
alpha=self.alpha,
|
|
444
|
+
beta=self.beta,
|
|
445
|
+
tol=self.tol,
|
|
446
|
+
max_iter=self.max_iter,
|
|
447
|
+
)
|
|
448
|
+
self.group_delays_ = gd
|
|
449
|
+
self.modes_freq_ = modes
|
|
450
|
+
return gd, modes
|
|
451
|
+
|
|
452
|
+
|
|
453
|
+
def gdmd(
|
|
454
|
+
signal: np.ndarray,
|
|
455
|
+
fs: Optional[float] = None,
|
|
456
|
+
init_gd: Optional[np.ndarray] = None,
|
|
457
|
+
alpha: float = 1e-3,
|
|
458
|
+
beta: float = 1e-7,
|
|
459
|
+
tol: float = 1e-8,
|
|
460
|
+
max_iter: int = 300,
|
|
461
|
+
K: Optional[int] = None,
|
|
462
|
+
return_all: bool = False,
|
|
463
|
+
) -> Union[np.ndarray, Tuple[np.ndarray, ...]]:
|
|
464
|
+
"""Functional wrapper around :class:`GDMD`."""
|
|
465
|
+
return GDMD(alpha=alpha, beta=beta, tol=tol, max_iter=max_iter, K=K).fit_transform(
|
|
466
|
+
signal, fs=fs, init_gd=init_gd, return_all=return_all
|
|
467
|
+
)
|
|
@@ -36,7 +36,7 @@ class SSA(object):
|
|
|
36
36
|
def __init__(
|
|
37
37
|
self,
|
|
38
38
|
K: int = 3,
|
|
39
|
-
mode="
|
|
39
|
+
mode: str = "traj",
|
|
40
40
|
lags: Optional[int] = None,
|
|
41
41
|
averaging: Optional[bool] = True,
|
|
42
42
|
extra_size: Optional[bool] = False,
|
|
@@ -50,6 +50,9 @@ class SSA(object):
|
|
|
50
50
|
:param averaging: if True, then mean of each diagonal will be taken for diagonal averaging instead of just summarizing (True, by default)
|
|
51
51
|
:param extra_size: if True, than near doubled size of output will be returned
|
|
52
52
|
"""
|
|
53
|
+
if K < 1:
|
|
54
|
+
raise ValueError("K must be a positive integer")
|
|
55
|
+
|
|
53
56
|
self.K = K
|
|
54
57
|
self.mode = mode
|
|
55
58
|
self.lags = lags
|
|
@@ -76,41 +79,56 @@ class SSA(object):
|
|
|
76
79
|
|
|
77
80
|
# Make sure the data type is correct
|
|
78
81
|
signal = np.asarray(signal)
|
|
82
|
+
if signal.ndim != 1 or signal.size < 2:
|
|
83
|
+
raise ValueError("signal must be a 1D array with at least 2 samples")
|
|
79
84
|
|
|
80
85
|
# Get the length of the signal
|
|
81
86
|
seq_len = signal.shape[0]
|
|
82
87
|
|
|
83
88
|
# for toeplitz and hankel N_lags always = N
|
|
84
89
|
if self.lags is None:
|
|
85
|
-
lags = seq_len // 2
|
|
90
|
+
lags = max(seq_len // 2, 2)
|
|
86
91
|
else:
|
|
87
|
-
lags = self.lags
|
|
92
|
+
lags = int(self.lags)
|
|
93
|
+
|
|
94
|
+
if lags < 2 or lags >= seq_len:
|
|
95
|
+
raise ValueError(
|
|
96
|
+
f"lags must satisfy 2 <= lags < len(signal); got lags={lags}, len={seq_len}"
|
|
97
|
+
)
|
|
88
98
|
|
|
89
99
|
# Whether to set the inversion
|
|
90
100
|
reverse = False
|
|
91
101
|
if self.mode in ["traj", "hankel", "trajectory", "caterpillar"]:
|
|
92
102
|
reverse = True
|
|
93
103
|
|
|
94
|
-
# Generate lag matrix
|
|
104
|
+
# Generate lag / trajectory matrix
|
|
95
105
|
base = lags_matrix(signal, lags=lags, mode=self.mode)
|
|
96
106
|
|
|
107
|
+
# R = X^H X; its eigenvectors are the right singular vectors of X
|
|
97
108
|
R = np.dot(base.T, np.conj(base))
|
|
98
109
|
|
|
99
|
-
#
|
|
100
|
-
es, ev = linalg.
|
|
101
|
-
|
|
110
|
+
# Hermitian eigendecomposition, sorted by eigenvalue magnitude (descending)
|
|
111
|
+
es, ev = linalg.eigh(R)
|
|
112
|
+
order = np.argsort(es)[::-1]
|
|
113
|
+
es = es[order]
|
|
114
|
+
ev = ev[:, order]
|
|
115
|
+
|
|
116
|
+
# Singular values σ = sqrt(λ); floor tiny / negative numerical values
|
|
117
|
+
singular = np.sqrt(np.maximum(es.real, 0.0)) + self.EPSILON
|
|
102
118
|
|
|
119
|
+
n_components = min(self.K, ev.shape[1])
|
|
103
120
|
# Array used to store decomposition results
|
|
104
121
|
imfs = np.zeros(
|
|
105
|
-
shape=(
|
|
122
|
+
shape=(n_components, base.shape[0] + base.shape[1] - 1),
|
|
123
|
+
dtype=np.result_type(signal.dtype, np.float64),
|
|
106
124
|
)
|
|
107
125
|
|
|
108
|
-
#
|
|
109
|
-
for i in range(
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
hankel = np.outer(
|
|
126
|
+
# Reconstruct each elementary component via X_i = (X v_i) v_i^H
|
|
127
|
+
for i in range(n_components):
|
|
128
|
+
v = ev[:, i]
|
|
129
|
+
# Left singular vector direction (scaled): u σ = X v
|
|
130
|
+
xv = np.dot(base, v)
|
|
131
|
+
hankel = np.outer(xv, np.conj(v))
|
|
114
132
|
|
|
115
133
|
diag = diagonal_average(
|
|
116
134
|
hankel,
|
|
@@ -124,11 +142,20 @@ class SSA(object):
|
|
|
124
142
|
# Adjust the scale of the result after the iteration
|
|
125
143
|
imfs = imfs[:, : diag.size]
|
|
126
144
|
|
|
145
|
+
# For trajectory embedding, match the original series length by default
|
|
146
|
+
if not self.extra_size and imfs.shape[1] >= seq_len:
|
|
147
|
+
if self.mode in ["traj", "trajectory", "caterpillar", "covar", "valid"]:
|
|
148
|
+
imfs = imfs[:, :seq_len]
|
|
149
|
+
|
|
127
150
|
# Handling complex mappings
|
|
128
151
|
if self.mode in ["traj", "trajectory", "caterpillar"]:
|
|
129
152
|
imfs = np.conj(imfs)
|
|
130
153
|
|
|
131
|
-
|
|
154
|
+
# Return real components when the input is real-valued
|
|
155
|
+
if np.isrealobj(signal):
|
|
156
|
+
imfs = np.real(imfs)
|
|
157
|
+
|
|
158
|
+
return np.asarray(imfs)
|
|
132
159
|
|
|
133
160
|
|
|
134
161
|
if __name__ == "__main__":
|
|
@@ -138,7 +165,7 @@ if __name__ == "__main__":
|
|
|
138
165
|
|
|
139
166
|
time, signal = test_emd()
|
|
140
167
|
|
|
141
|
-
ssa = SSA(K=2, mode="
|
|
168
|
+
ssa = SSA(K=2, mode="traj")
|
|
142
169
|
IMFs = ssa.fit_transform(signal)
|
|
143
170
|
|
|
144
171
|
print(IMFs.shape)
|