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