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.
Files changed (166) hide show
  1. {pysdkit-0.4.33 → pysdkit-0.4.35}/PKG-INFO +2 -2
  2. {pysdkit-0.4.33 → pysdkit-0.4.35}/PySDKit.egg-info/PKG-INFO +2 -2
  3. {pysdkit-0.4.33 → pysdkit-0.4.35}/PySDKit.egg-info/SOURCES.txt +5 -0
  4. {pysdkit-0.4.33 → pysdkit-0.4.35}/README.md +1 -1
  5. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/__init__.py +12 -1
  6. pysdkit-0.4.35/pysdkit/_gdmd/__init__.py +32 -0
  7. pysdkit-0.4.35/pysdkit/_gdmd/gdmd.py +467 -0
  8. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_ssa/ssa.py +43 -16
  9. pysdkit-0.4.35/pysdkit/tests/test_gdmd.py +160 -0
  10. pysdkit-0.4.35/pysdkit/tests/test_mstl.py +160 -0
  11. pysdkit-0.4.35/pysdkit/tests/test_ssa.py +153 -0
  12. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/tests/test_stl.py +64 -57
  13. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/tsa/__init__.py +10 -1
  14. pysdkit-0.4.35/pysdkit/tsa/_mstl.py +286 -0
  15. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/tsa/_stl.py +143 -96
  16. pysdkit-0.4.33/pysdkit/tsa/_mstl.py +0 -25
  17. {pysdkit-0.4.33 → pysdkit-0.4.35}/LICENSE +0 -0
  18. {pysdkit-0.4.33 → pysdkit-0.4.35}/PySDKit.egg-info/dependency_links.txt +0 -0
  19. {pysdkit-0.4.33 → pysdkit-0.4.35}/PySDKit.egg-info/requires.txt +0 -0
  20. {pysdkit-0.4.33 → pysdkit-0.4.35}/PySDKit.egg-info/top_level.txt +0 -0
  21. {pysdkit-0.4.33 → pysdkit-0.4.35}/pyproject.toml +0 -0
  22. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_alif/__init__.py +0 -0
  23. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_alif/alif.py +0 -0
  24. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_alif/data/__init__.py +0 -0
  25. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_alif/data/prefixed_double_filter.npy +0 -0
  26. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_alif/iterative_filtering.py +0 -0
  27. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_apmd/__init__.py +0 -0
  28. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_apmd/apmd.py +0 -0
  29. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_emd/__init__.py +0 -0
  30. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_emd/_find_extrema.py +0 -0
  31. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_emd/_prepare_points.py +0 -0
  32. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_emd/_splines.py +0 -0
  33. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_emd/ceemdan.py +0 -0
  34. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_emd/eemd.py +0 -0
  35. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_emd/efd.py +0 -0
  36. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_emd/emd.py +0 -0
  37. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_emd/hht/__init__.py +0 -0
  38. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_emd/hht/frequency.py +0 -0
  39. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_emd/hht/hht.py +0 -0
  40. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_emd/memd.py +0 -0
  41. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_emd/remd.py +0 -0
  42. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_emd/tvf_emd.py +0 -0
  43. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_emd2d/__init__.py +0 -0
  44. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_emd2d/bemd.py +0 -0
  45. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_emd2d/bmemd.py +0 -0
  46. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_emd2d/emd2d.py +0 -0
  47. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_esmd/__init__.py +0 -0
  48. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_esmd/esmd.py +0 -0
  49. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_ewt/__init__.py +0 -0
  50. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_ewt/ewt.py +0 -0
  51. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_ewt/ewt2d.py +0 -0
  52. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_faemd/__init__.py +0 -0
  53. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_faemd/extrema.py +0 -0
  54. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_faemd/faemd.py +0 -0
  55. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_faemd/faemd2d.py +0 -0
  56. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_faemd/faemd3d.py +0 -0
  57. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_faemd/filter.py +0 -0
  58. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_fmd/__init__.py +0 -0
  59. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_fmd/fmd.py +0 -0
  60. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_fmd/nfmd.py +0 -0
  61. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_hvd/__init__.py +0 -0
  62. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_hvd/hvd.py +0 -0
  63. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_itd/__init__.py +0 -0
  64. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_itd/itd.py +0 -0
  65. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_jmd/__init__.py +0 -0
  66. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_jmd/jmd.py +0 -0
  67. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_jmd/mjmd.py +0 -0
  68. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_jmd/sjmd.py +0 -0
  69. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_lmd/__init__.py +0 -0
  70. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_lmd/lmd.py +0 -0
  71. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_lmd/rlmd.py +0 -0
  72. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_osd/__init__.py +0 -0
  73. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_osd/swd.py +0 -0
  74. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_ssa/__init__.py +0 -0
  75. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_vmd/__init__.py +0 -0
  76. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_vmd/acmd.py +0 -0
  77. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_vmd/avmd.py +0 -0
  78. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_vmd/ba_acmd.py +0 -0
  79. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_vmd/base.py +0 -0
  80. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_vmd/mvmd.py +0 -0
  81. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_vmd/ovmd.py +0 -0
  82. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_vmd/stvmd.py +0 -0
  83. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_vmd/svmd.py +0 -0
  84. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_vmd/vmd_c.py +0 -0
  85. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_vmd/vmd_f.py +0 -0
  86. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_vmd/vme.py +0 -0
  87. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_vmd2d/__init__.py +0 -0
  88. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_vmd2d/cvmd2d.py +0 -0
  89. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_vmd2d/vmd2d.py +0 -0
  90. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_vncmd/__init__.py +0 -0
  91. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_vncmd/avncmd.py +0 -0
  92. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_vncmd/incmd.py +0 -0
  93. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/_vncmd/vncmd.py +0 -0
  94. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/data/__init__.py +0 -0
  95. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/data/_add_noise.py +0 -0
  96. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/data/_cube.py +0 -0
  97. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/data/_generator.py +0 -0
  98. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/data/_image.py +0 -0
  99. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/data/_models.py +0 -0
  100. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/data/_test_univariate.py +0 -0
  101. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/data/_time_series.py +0 -0
  102. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/data/texture.txt +0 -0
  103. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/entropy/__init__.py +0 -0
  104. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/entropy/_approxiamte_entropy.py +0 -0
  105. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/entropy/_permutation_entropy.py +0 -0
  106. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/entropy/_sample_entropy.py +0 -0
  107. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/models/__init__.py +0 -0
  108. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/models/_base.py +0 -0
  109. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/models/_kmeans.py +0 -0
  110. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/models/_knn.py +0 -0
  111. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/models/_pca.py +0 -0
  112. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/plot/__init__.py +0 -0
  113. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/plot/_fourier_spectra.py +0 -0
  114. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/plot/_functions.py +0 -0
  115. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/plot/_plot_images.py +0 -0
  116. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/plot/_plot_imfs.py +0 -0
  117. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/plot/_plot_signal.py +0 -0
  118. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/tests/__init__.py +0 -0
  119. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/tests/data/__init__.py +0 -0
  120. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/tests/data/test_generator_cube.py +0 -0
  121. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/tests/data/test_generator_image.py +0 -0
  122. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/tests/data/test_generator_signal.py +0 -0
  123. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/tests/data/test_generator_univariate_signal.py +0 -0
  124. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/tests/models/__init__.py +0 -0
  125. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/tests/models/test_knn.py +0 -0
  126. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/tests/models/test_pca.py +0 -0
  127. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/tests/run_all.py +0 -0
  128. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/tests/test_alif.py +0 -0
  129. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/tests/test_apmd.py +0 -0
  130. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/tests/test_avncmd.py +0 -0
  131. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/tests/test_ceemdan.py +0 -0
  132. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/tests/test_emd.py +0 -0
  133. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/tests/test_ewt.py +0 -0
  134. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/tests/test_ewt2d.py +0 -0
  135. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/tests/test_faemd.py +0 -0
  136. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/tests/test_faemd2d.py +0 -0
  137. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/tests/test_faemd3d.py +0 -0
  138. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/tests/test_itd.py +0 -0
  139. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/tests/test_jmd.py +0 -0
  140. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/tests/test_lmd.py +0 -0
  141. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/tests/test_moving_decomp.py +0 -0
  142. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/tests/test_ovmd.py +0 -0
  143. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/tests/test_rlmd.py +0 -0
  144. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/tests/test_sjmd.py +0 -0
  145. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/tests/test_stvmd.py +0 -0
  146. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/tests/test_svmd.py +0 -0
  147. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/tests/test_swd.py +0 -0
  148. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/tests/test_vmd.py +0 -0
  149. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/tests/test_vmd2d.py +0 -0
  150. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/tests/test_vncmd.py +0 -0
  151. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/tsa/_dtw.py +0 -0
  152. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/tsa/_moving_decomp.py +0 -0
  153. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/utils/__init__.py +0 -0
  154. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/utils/_cite.py +0 -0
  155. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/utils/_correlation.py +0 -0
  156. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/utils/_diagnalization.py +0 -0
  157. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/utils/_differ.py +0 -0
  158. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/utils/_fft.py +0 -0
  159. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/utils/_function.py +0 -0
  160. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/utils/_hilbert.py +0 -0
  161. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/utils/_instantaneous.py +0 -0
  162. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/utils/_kernel_matrix.py +0 -0
  163. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/utils/_mirror.py +0 -0
  164. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/utils/_process.py +0 -0
  165. {pysdkit-0.4.33 → pysdkit-0.4.35}/pysdkit/utils/_smooth1d.py +0 -0
  166. {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.33
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://www.nniiem.ru/file/news/2016/stl-statistical-model.pdf) | [[code]](https://www.statsmodels.org/stable/examples/notebooks/generated/mstl_decomposition.html) | ✖️ |
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.33
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://www.nniiem.ru/file/news/2016/stl-statistical-model.pdf) | [[code]](https://www.statsmodels.org/stable/examples/notebooks/generated/mstl_decomposition.html) | ✖️ |
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://www.nniiem.ru/file/news/2016/stl-statistical-model.pdf) | [[code]](https://www.statsmodels.org/stable/examples/notebooks/generated/mstl_decomposition.html) | ✖️ |
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.33"
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
+ )