PySDKit 0.4.33__tar.gz → 0.4.35__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.35}/PKG-INFO +2 -2
- {pysdkit-0.4.33 → pysdkit-0.4.35}/PySDKit.egg-info/PKG-INFO +2 -2
- {pysdkit-0.4.33 → pysdkit-0.4.35}/PySDKit.egg-info/SOURCES.txt +5 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/README.md +1 -1
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/__init__.py +12 -1
- pysdkit-0.4.35/pysdkit/_gdmd/__init__.py +32 -0
- pysdkit-0.4.35/pysdkit/_gdmd/gdmd.py +467 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_ssa/ssa.py +43 -16
- pysdkit-0.4.35/pysdkit/tests/test_gdmd.py +160 -0
- pysdkit-0.4.35/pysdkit/tests/test_mstl.py +160 -0
- pysdkit-0.4.35/pysdkit/tests/test_ssa.py +153 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/tests/test_stl.py +64 -57
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/tsa/__init__.py +10 -1
- pysdkit-0.4.35/pysdkit/tsa/_mstl.py +286 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/tsa/_stl.py +143 -96
- pysdkit-0.4.33/pysdkit/tsa/_mstl.py +0 -25
- {pysdkit-0.4.33 → pysdkit-0.4.35}/LICENSE +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/PySDKit.egg-info/dependency_links.txt +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/PySDKit.egg-info/requires.txt +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/PySDKit.egg-info/top_level.txt +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pyproject.toml +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_alif/__init__.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_alif/alif.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_alif/data/__init__.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_alif/data/prefixed_double_filter.npy +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_alif/iterative_filtering.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_apmd/__init__.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_apmd/apmd.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_emd/__init__.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_emd/_find_extrema.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_emd/_prepare_points.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_emd/_splines.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_emd/ceemdan.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_emd/eemd.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_emd/efd.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_emd/emd.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_emd/hht/__init__.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_emd/hht/frequency.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_emd/hht/hht.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_emd/memd.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_emd/remd.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_emd/tvf_emd.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_emd2d/__init__.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_emd2d/bemd.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_emd2d/bmemd.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_emd2d/emd2d.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_esmd/__init__.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_esmd/esmd.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_ewt/__init__.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_ewt/ewt.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_ewt/ewt2d.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_faemd/__init__.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_faemd/extrema.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_faemd/faemd.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_faemd/faemd2d.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_faemd/faemd3d.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_faemd/filter.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_fmd/__init__.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_fmd/fmd.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_fmd/nfmd.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_hvd/__init__.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_hvd/hvd.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_itd/__init__.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_itd/itd.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_jmd/__init__.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_jmd/jmd.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_jmd/mjmd.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_jmd/sjmd.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_lmd/__init__.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_lmd/lmd.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_lmd/rlmd.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_osd/__init__.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_osd/swd.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_ssa/__init__.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_vmd/__init__.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_vmd/acmd.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_vmd/avmd.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_vmd/ba_acmd.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_vmd/base.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_vmd/mvmd.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_vmd/ovmd.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_vmd/stvmd.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_vmd/svmd.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_vmd/vmd_c.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_vmd/vmd_f.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_vmd/vme.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_vmd2d/__init__.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_vmd2d/cvmd2d.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_vmd2d/vmd2d.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_vncmd/__init__.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_vncmd/avncmd.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_vncmd/incmd.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_vncmd/vncmd.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/data/__init__.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/data/_add_noise.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/data/_cube.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/data/_generator.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/data/_image.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/data/_models.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/data/_test_univariate.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/data/_time_series.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/data/texture.txt +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/entropy/__init__.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/entropy/_approxiamte_entropy.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/entropy/_permutation_entropy.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/entropy/_sample_entropy.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/models/__init__.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/models/_base.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/models/_kmeans.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/models/_knn.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/models/_pca.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/plot/__init__.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/plot/_fourier_spectra.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/plot/_functions.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/plot/_plot_images.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/plot/_plot_imfs.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/plot/_plot_signal.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/tests/__init__.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/tests/data/__init__.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/tests/data/test_generator_cube.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/tests/data/test_generator_image.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/tests/data/test_generator_signal.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/tests/data/test_generator_univariate_signal.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/tests/models/__init__.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/tests/models/test_knn.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/tests/models/test_pca.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/tests/run_all.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/tests/test_alif.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/tests/test_apmd.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/tests/test_avncmd.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/tests/test_ceemdan.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/tests/test_emd.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/tests/test_ewt.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/tests/test_ewt2d.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/tests/test_faemd.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/tests/test_faemd2d.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/tests/test_faemd3d.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/tests/test_itd.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/tests/test_jmd.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/tests/test_lmd.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/tests/test_moving_decomp.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/tests/test_ovmd.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/tests/test_rlmd.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/tests/test_sjmd.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/tests/test_stvmd.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/tests/test_svmd.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/tests/test_swd.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/tests/test_vmd.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/tests/test_vmd2d.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/tests/test_vncmd.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/tsa/_dtw.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/tsa/_moving_decomp.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/utils/__init__.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/utils/_cite.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/utils/_correlation.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/utils/_diagnalization.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/utils/_differ.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/utils/_fft.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/utils/_function.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/utils/_hilbert.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/utils/_instantaneous.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/utils/_kernel_matrix.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/utils/_mirror.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/utils/_process.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/utils/_smooth1d.py +0 -0
- {pysdkit-0.4.33 → pysdkit-0.4.35}/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.35
|
|
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>
|
|
@@ -189,7 +189,7 @@ plot_IMFs_amplitude_spectra(IMFs, smooth="exp") # use exp smooth
|
|
|
189
189
|
| [`STNBMD`]() (Short-Time Narrow-Band Mode Decomposition) | [[paper]](https://www.sciencedirect.com/science/article/pii/S0022460X16002443?via%3Dihub) | [[code]](https://ww2.mathworks.cn/matlabcentral/fileexchange/56226-short-time-narrow-band-mode-decomposition-stnbmd-toolbox) | ✖️ |
|
|
190
190
|
| [`SWD`](https://github.com/wwhenxuan/PySDKit/blob/main/pysdkit/_osd/swd.py) (Swarm Decomposition) | [[paper]](https://doi.org/10.1016/j.sigpro.2016.09.004) | [[code]](https://github.com/gkaposto/Swarm-Decomposition) | ✔️ |
|
|
191
191
|
| [`STL`](https://github.com/wwhenxuan/PySDKit/blob/main/pysdkit/tsa/_stl.py) (Seasonal-Trend decomposition using LOESS) | [[paper]](https://www.nniiem.ru/file/news/2016/stl-statistical-model.pdf) | [[code]](https://www.statsmodels.org/stable/examples/notebooks/generated/stl_decomposition.html) | ✔️ |
|
|
192
|
-
| [`MSTL`]() (Multivariate Seasonal-Trend decomposition using LOESS) | [[paper]](https://
|
|
192
|
+
| [`MSTL`]() (Multivariate Seasonal-Trend decomposition using LOESS) | [[paper]](https://arxiv.org/abs/2107.13462) | [[code]](https://github.com/KishManani/MSTL) | ✔️ |
|
|
193
193
|
|
|
194
194
|
## Acknowledgements 🎖️ <a id="Acknowledgements"></a>
|
|
195
195
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: PySDKit
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.35
|
|
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>
|
|
@@ -189,7 +189,7 @@ plot_IMFs_amplitude_spectra(IMFs, smooth="exp") # use exp smooth
|
|
|
189
189
|
| [`STNBMD`]() (Short-Time Narrow-Band Mode Decomposition) | [[paper]](https://www.sciencedirect.com/science/article/pii/S0022460X16002443?via%3Dihub) | [[code]](https://ww2.mathworks.cn/matlabcentral/fileexchange/56226-short-time-narrow-band-mode-decomposition-stnbmd-toolbox) | ✖️ |
|
|
190
190
|
| [`SWD`](https://github.com/wwhenxuan/PySDKit/blob/main/pysdkit/_osd/swd.py) (Swarm Decomposition) | [[paper]](https://doi.org/10.1016/j.sigpro.2016.09.004) | [[code]](https://github.com/gkaposto/Swarm-Decomposition) | ✔️ |
|
|
191
191
|
| [`STL`](https://github.com/wwhenxuan/PySDKit/blob/main/pysdkit/tsa/_stl.py) (Seasonal-Trend decomposition using LOESS) | [[paper]](https://www.nniiem.ru/file/news/2016/stl-statistical-model.pdf) | [[code]](https://www.statsmodels.org/stable/examples/notebooks/generated/stl_decomposition.html) | ✔️ |
|
|
192
|
-
| [`MSTL`]() (Multivariate Seasonal-Trend decomposition using LOESS) | [[paper]](https://
|
|
192
|
+
| [`MSTL`]() (Multivariate Seasonal-Trend decomposition using LOESS) | [[paper]](https://arxiv.org/abs/2107.13462) | [[code]](https://github.com/KishManani/MSTL) | ✔️ |
|
|
193
193
|
|
|
194
194
|
## Acknowledgements 🎖️ <a id="Acknowledgements"></a>
|
|
195
195
|
|
|
@@ -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,13 +118,16 @@ 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
|
|
122
125
|
pysdkit/tests/test_moving_decomp.py
|
|
126
|
+
pysdkit/tests/test_mstl.py
|
|
123
127
|
pysdkit/tests/test_ovmd.py
|
|
124
128
|
pysdkit/tests/test_rlmd.py
|
|
125
129
|
pysdkit/tests/test_sjmd.py
|
|
130
|
+
pysdkit/tests/test_ssa.py
|
|
126
131
|
pysdkit/tests/test_stl.py
|
|
127
132
|
pysdkit/tests/test_stvmd.py
|
|
128
133
|
pysdkit/tests/test_svmd.py
|
|
@@ -135,7 +135,7 @@ plot_IMFs_amplitude_spectra(IMFs, smooth="exp") # use exp smooth
|
|
|
135
135
|
| [`STNBMD`]() (Short-Time Narrow-Band Mode Decomposition) | [[paper]](https://www.sciencedirect.com/science/article/pii/S0022460X16002443?via%3Dihub) | [[code]](https://ww2.mathworks.cn/matlabcentral/fileexchange/56226-short-time-narrow-band-mode-decomposition-stnbmd-toolbox) | ✖️ |
|
|
136
136
|
| [`SWD`](https://github.com/wwhenxuan/PySDKit/blob/main/pysdkit/_osd/swd.py) (Swarm Decomposition) | [[paper]](https://doi.org/10.1016/j.sigpro.2016.09.004) | [[code]](https://github.com/gkaposto/Swarm-Decomposition) | ✔️ |
|
|
137
137
|
| [`STL`](https://github.com/wwhenxuan/PySDKit/blob/main/pysdkit/tsa/_stl.py) (Seasonal-Trend decomposition using LOESS) | [[paper]](https://www.nniiem.ru/file/news/2016/stl-statistical-model.pdf) | [[code]](https://www.statsmodels.org/stable/examples/notebooks/generated/stl_decomposition.html) | ✔️ |
|
|
138
|
-
| [`MSTL`]() (Multivariate Seasonal-Trend decomposition using LOESS) | [[paper]](https://
|
|
138
|
+
| [`MSTL`]() (Multivariate Seasonal-Trend decomposition using LOESS) | [[paper]](https://arxiv.org/abs/2107.13462) | [[code]](https://github.com/KishManani/MSTL) | ✔️ |
|
|
139
139
|
|
|
140
140
|
## Acknowledgements 🎖️ <a id="Acknowledgements"></a>
|
|
141
141
|
|
|
@@ -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.35"
|
|
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
|
|
|
@@ -115,6 +118,9 @@ from .tsa import Moving_Decomp
|
|
|
115
118
|
# Seasonal-Trend decomposition using LOESS (STL)
|
|
116
119
|
from .tsa import STL
|
|
117
120
|
|
|
121
|
+
# Multiple Seasonal-Trend decomposition using LOESS (MSTL)
|
|
122
|
+
from .tsa import MSTL
|
|
123
|
+
|
|
118
124
|
# Hilbert-Huang Transform
|
|
119
125
|
from ._emd import HHT
|
|
120
126
|
|
|
@@ -157,6 +163,7 @@ Local Mean Decomposition | LMD
|
|
|
157
163
|
Robust Local Mean Decomposition | RLMD
|
|
158
164
|
Singular Spectral Analysis | SSA
|
|
159
165
|
Swarm Decomposition | SWD
|
|
166
|
+
Generalized Dispersion Mode Decomposition | GDMD
|
|
160
167
|
Variational Mode Decomposition | VMD
|
|
161
168
|
Multivariate Variational Mode Decomposition | MVMD
|
|
162
169
|
Variational Mode Extraction | VME
|
|
@@ -178,6 +185,7 @@ Successive Jump and Mode Decomposition | SJMD / SMJMD
|
|
|
178
185
|
Feature Mode Decomposition | FMD
|
|
179
186
|
Moving Average Decomposition | Moving
|
|
180
187
|
Seasonal-Trend decomposition using LOESS | STL
|
|
188
|
+
Multiple Seasonal-Trend decomposition (LOESS) | MSTL
|
|
181
189
|
Hilbert-Huang Transform | HHT
|
|
182
190
|
_______________________________________________________________
|
|
183
191
|
"""
|
|
@@ -201,6 +209,8 @@ __all__ = [
|
|
|
201
209
|
"SSA",
|
|
202
210
|
"SWD",
|
|
203
211
|
"swd",
|
|
212
|
+
"GDMD",
|
|
213
|
+
"gdmd",
|
|
204
214
|
"vmd",
|
|
205
215
|
"VMD",
|
|
206
216
|
"ACMD",
|
|
@@ -229,6 +239,7 @@ __all__ = [
|
|
|
229
239
|
"FMD",
|
|
230
240
|
"Moving_Decomp",
|
|
231
241
|
"STL",
|
|
242
|
+
"MSTL",
|
|
232
243
|
"models",
|
|
233
244
|
"data",
|
|
234
245
|
"entropy",
|
|
@@ -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
|
+
)
|