PySDKit 0.4.29__tar.gz → 0.4.30__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 (154) hide show
  1. {pysdkit-0.4.29 → pysdkit-0.4.30}/PKG-INFO +2 -1
  2. {pysdkit-0.4.29 → pysdkit-0.4.30}/PySDKit.egg-info/PKG-INFO +2 -1
  3. {pysdkit-0.4.29 → pysdkit-0.4.30}/PySDKit.egg-info/SOURCES.txt +2 -0
  4. {pysdkit-0.4.29 → pysdkit-0.4.30}/README.md +1 -0
  5. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/__init__.py +7 -1
  6. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/_jmd/__init__.py +2 -1
  7. pysdkit-0.4.30/pysdkit/_jmd/sjmd.py +464 -0
  8. pysdkit-0.4.30/pysdkit/tests/test_sjmd.py +160 -0
  9. {pysdkit-0.4.29 → pysdkit-0.4.30}/LICENSE +0 -0
  10. {pysdkit-0.4.29 → pysdkit-0.4.30}/PySDKit.egg-info/dependency_links.txt +0 -0
  11. {pysdkit-0.4.29 → pysdkit-0.4.30}/PySDKit.egg-info/requires.txt +0 -0
  12. {pysdkit-0.4.29 → pysdkit-0.4.30}/PySDKit.egg-info/top_level.txt +0 -0
  13. {pysdkit-0.4.29 → pysdkit-0.4.30}/pyproject.toml +0 -0
  14. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/_alif/__init__.py +0 -0
  15. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/_alif/alif.py +0 -0
  16. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/_alif/data/__init__.py +0 -0
  17. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/_alif/data/prefixed_double_filter.npy +0 -0
  18. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/_alif/iterative_filtering.py +0 -0
  19. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/_apmd/__init__.py +0 -0
  20. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/_apmd/apmd.py +0 -0
  21. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/_emd/__init__.py +0 -0
  22. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/_emd/_find_extrema.py +0 -0
  23. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/_emd/_prepare_points.py +0 -0
  24. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/_emd/_splines.py +0 -0
  25. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/_emd/ceemdan.py +0 -0
  26. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/_emd/eemd.py +0 -0
  27. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/_emd/efd.py +0 -0
  28. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/_emd/emd.py +0 -0
  29. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/_emd/hht/__init__.py +0 -0
  30. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/_emd/hht/frequency.py +0 -0
  31. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/_emd/hht/hht.py +0 -0
  32. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/_emd/memd.py +0 -0
  33. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/_emd/remd.py +0 -0
  34. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/_emd/tvf_emd.py +0 -0
  35. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/_emd2d/__init__.py +0 -0
  36. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/_emd2d/bemd.py +0 -0
  37. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/_emd2d/bmemd.py +0 -0
  38. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/_emd2d/emd2d.py +0 -0
  39. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/_esmd/__init__.py +0 -0
  40. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/_esmd/esmd.py +0 -0
  41. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/_ewt/__init__.py +0 -0
  42. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/_ewt/ewt.py +0 -0
  43. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/_ewt/ewt2d.py +0 -0
  44. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/_faemd/__init__.py +0 -0
  45. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/_faemd/extrema.py +0 -0
  46. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/_faemd/faemd.py +0 -0
  47. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/_faemd/faemd2d.py +0 -0
  48. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/_faemd/faemd3d.py +0 -0
  49. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/_faemd/filter.py +0 -0
  50. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/_fmd/__init__.py +0 -0
  51. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/_fmd/fmd.py +0 -0
  52. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/_fmd/nfmd.py +0 -0
  53. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/_hvd/__init__.py +0 -0
  54. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/_hvd/hvd.py +0 -0
  55. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/_itd/__init__.py +0 -0
  56. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/_itd/itd.py +0 -0
  57. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/_jmd/jmd.py +0 -0
  58. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/_jmd/mjmd.py +0 -0
  59. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/_lmd/__init__.py +0 -0
  60. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/_lmd/lmd.py +0 -0
  61. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/_lmd/rlmd.py +0 -0
  62. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/_osd/__init__.py +0 -0
  63. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/_ssa/__init__.py +0 -0
  64. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/_ssa/ssa.py +0 -0
  65. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/_vmd/__init__.py +0 -0
  66. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/_vmd/acmd.py +0 -0
  67. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/_vmd/avmd.py +0 -0
  68. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/_vmd/ba_acmd.py +0 -0
  69. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/_vmd/base.py +0 -0
  70. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/_vmd/mvmd.py +0 -0
  71. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/_vmd/ovmd.py +0 -0
  72. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/_vmd/svmd.py +0 -0
  73. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/_vmd/vmd_c.py +0 -0
  74. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/_vmd/vmd_f.py +0 -0
  75. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/_vmd/vme.py +0 -0
  76. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/_vmd2d/__init__.py +0 -0
  77. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/_vmd2d/cvmd2d.py +0 -0
  78. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/_vmd2d/vmd2d.py +0 -0
  79. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/_vncmd/__init__.py +0 -0
  80. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/_vncmd/avncmd.py +0 -0
  81. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/_vncmd/incmd.py +0 -0
  82. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/_vncmd/mncmd.py +0 -0
  83. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/_vncmd/vncmd.py +0 -0
  84. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/data/__init__.py +0 -0
  85. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/data/_add_noise.py +0 -0
  86. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/data/_cube.py +0 -0
  87. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/data/_generator.py +0 -0
  88. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/data/_image.py +0 -0
  89. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/data/_models.py +0 -0
  90. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/data/_test_univariate.py +0 -0
  91. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/data/_time_series.py +0 -0
  92. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/data/texture.txt +0 -0
  93. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/entropy/__init__.py +0 -0
  94. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/entropy/_approxiamte_entropy.py +0 -0
  95. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/entropy/_permutation_entropy.py +0 -0
  96. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/entropy/_sample_entropy.py +0 -0
  97. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/models/__init__.py +0 -0
  98. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/models/_base.py +0 -0
  99. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/models/_kmeans.py +0 -0
  100. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/models/_knn.py +0 -0
  101. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/models/_pca.py +0 -0
  102. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/plot/__init__.py +0 -0
  103. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/plot/_fourier_spectra.py +0 -0
  104. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/plot/_functions.py +0 -0
  105. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/plot/_plot_images.py +0 -0
  106. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/plot/_plot_imfs.py +0 -0
  107. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/plot/_plot_signal.py +0 -0
  108. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/tests/__init__.py +0 -0
  109. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/tests/data/__init__.py +0 -0
  110. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/tests/data/test_generator_cube.py +0 -0
  111. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/tests/data/test_generator_image.py +0 -0
  112. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/tests/data/test_generator_signal.py +0 -0
  113. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/tests/data/test_generator_univariate_signal.py +0 -0
  114. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/tests/models/__init__.py +0 -0
  115. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/tests/models/test_knn.py +0 -0
  116. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/tests/models/test_pca.py +0 -0
  117. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/tests/run_all.py +0 -0
  118. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/tests/test_alif.py +0 -0
  119. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/tests/test_apmd.py +0 -0
  120. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/tests/test_avncmd.py +0 -0
  121. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/tests/test_ceemdan.py +0 -0
  122. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/tests/test_emd.py +0 -0
  123. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/tests/test_ewt.py +0 -0
  124. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/tests/test_faemd.py +0 -0
  125. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/tests/test_faemd2d.py +0 -0
  126. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/tests/test_faemd3d.py +0 -0
  127. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/tests/test_itd.py +0 -0
  128. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/tests/test_jmd.py +0 -0
  129. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/tests/test_lmd.py +0 -0
  130. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/tests/test_moving_decomp.py +0 -0
  131. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/tests/test_ovmd.py +0 -0
  132. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/tests/test_rlmd.py +0 -0
  133. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/tests/test_stl.py +0 -0
  134. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/tests/test_vmd.py +0 -0
  135. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/tests/test_vmd2d.py +0 -0
  136. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/tsa/__init__.py +0 -0
  137. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/tsa/_dtw.py +0 -0
  138. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/tsa/_moving_decomp.py +0 -0
  139. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/tsa/_mstl.py +0 -0
  140. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/tsa/_stl.py +0 -0
  141. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/utils/__init__.py +0 -0
  142. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/utils/_cite.py +0 -0
  143. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/utils/_correlation.py +0 -0
  144. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/utils/_diagnalization.py +0 -0
  145. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/utils/_differ.py +0 -0
  146. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/utils/_fft.py +0 -0
  147. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/utils/_function.py +0 -0
  148. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/utils/_hilbert.py +0 -0
  149. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/utils/_instantaneous.py +0 -0
  150. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/utils/_kernel_matrix.py +0 -0
  151. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/utils/_mirror.py +0 -0
  152. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/utils/_process.py +0 -0
  153. {pysdkit-0.4.29 → pysdkit-0.4.30}/pysdkit/utils/_smooth1d.py +0 -0
  154. {pysdkit-0.4.29 → pysdkit-0.4.30}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PySDKit
3
- Version: 0.4.29
3
+ Version: 0.4.30
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>
@@ -183,6 +183,7 @@ plot_IMFs_amplitude_spectra(IMFs, smooth="exp") # use exp smooth
183
183
  | [`BA-ACMD`](https://github.com/wwhenxuan/PySDKit/blob/main/pysdkit/_vmd/ba_acmd.py) (Bandwidth-aware adaptive chirp mode decomposition) | [[paper]](https://journals.sagepub.com/doi/abs/10.1177/14759217231174699) | [[code]](https://www.mathworks.com/matlabcentral/fileexchange/132792-bandwidth-aware-adaptive-chirp-mode-decomposition-ba-acmd?s_tid=srchtitle) | ✖️ |
184
184
  | [`JMD`](https://github.com/wwhenxuan/PySDKit/blob/main/pysdkit/_jmd/jmd.py) (Jump Plus AM-FM Mode Decomposition) | [[paper]](https://arxiv.org/abs/2407.07800) | [[code]](https://www.mathworks.com/matlabcentral/fileexchange/169388-jump-plus-am-fm-mode-decomposition-jmd?s_tid=prof_contriblnk) | ️✔️ |
185
185
  | [`MJMD`](https://github.com/wwhenxuan/PySDKit/blob/main/pysdkit/_jmd/mjmd.py) (Multivariate Jump Plus AM-FM Mode Decomposition) | [[paper]](https://arxiv.org/abs/2407.07800) | [[code]](https://www.mathworks.com/matlabcentral/fileexchange/169393-multivariate-jump-plus-am-fm-mode-decomposition-mjmd?s_tid=prof_contriblnk) | ✔️ |
186
+ | [`SJMD`](https://github.com/wwhenxuan/PySDKit/blob/main/pysdkit/_jmd/sjmd.py) / `SMJMD` (Successive Jump and Mode Decomposition) | [[paper]](https://arxiv.org/abs/2504.08453) | [[code]]() | ✔️ |
186
187
  | [`ESMD`]() (Extreme-Point Symmetric Mode Decomposition) | [[paper]](https://arxiv.org/abs/1303.6540) | [[code]](https://github.com/WuShichao/esmd) | ✖️ |
187
188
  | [`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) | ✖️ |
188
189
  | [`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) | ✔️ |
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PySDKit
3
- Version: 0.4.29
3
+ Version: 0.4.30
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>
@@ -183,6 +183,7 @@ plot_IMFs_amplitude_spectra(IMFs, smooth="exp") # use exp smooth
183
183
  | [`BA-ACMD`](https://github.com/wwhenxuan/PySDKit/blob/main/pysdkit/_vmd/ba_acmd.py) (Bandwidth-aware adaptive chirp mode decomposition) | [[paper]](https://journals.sagepub.com/doi/abs/10.1177/14759217231174699) | [[code]](https://www.mathworks.com/matlabcentral/fileexchange/132792-bandwidth-aware-adaptive-chirp-mode-decomposition-ba-acmd?s_tid=srchtitle) | ✖️ |
184
184
  | [`JMD`](https://github.com/wwhenxuan/PySDKit/blob/main/pysdkit/_jmd/jmd.py) (Jump Plus AM-FM Mode Decomposition) | [[paper]](https://arxiv.org/abs/2407.07800) | [[code]](https://www.mathworks.com/matlabcentral/fileexchange/169388-jump-plus-am-fm-mode-decomposition-jmd?s_tid=prof_contriblnk) | ️✔️ |
185
185
  | [`MJMD`](https://github.com/wwhenxuan/PySDKit/blob/main/pysdkit/_jmd/mjmd.py) (Multivariate Jump Plus AM-FM Mode Decomposition) | [[paper]](https://arxiv.org/abs/2407.07800) | [[code]](https://www.mathworks.com/matlabcentral/fileexchange/169393-multivariate-jump-plus-am-fm-mode-decomposition-mjmd?s_tid=prof_contriblnk) | ✔️ |
186
+ | [`SJMD`](https://github.com/wwhenxuan/PySDKit/blob/main/pysdkit/_jmd/sjmd.py) / `SMJMD` (Successive Jump and Mode Decomposition) | [[paper]](https://arxiv.org/abs/2504.08453) | [[code]]() | ✔️ |
186
187
  | [`ESMD`]() (Extreme-Point Symmetric Mode Decomposition) | [[paper]](https://arxiv.org/abs/1303.6540) | [[code]](https://github.com/WuShichao/esmd) | ✖️ |
187
188
  | [`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) | ✖️ |
188
189
  | [`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) | ✔️ |
@@ -53,6 +53,7 @@ pysdkit/_itd/itd.py
53
53
  pysdkit/_jmd/__init__.py
54
54
  pysdkit/_jmd/jmd.py
55
55
  pysdkit/_jmd/mjmd.py
56
+ pysdkit/_jmd/sjmd.py
56
57
  pysdkit/_lmd/__init__.py
57
58
  pysdkit/_lmd/lmd.py
58
59
  pysdkit/_lmd/rlmd.py
@@ -119,6 +120,7 @@ pysdkit/tests/test_lmd.py
119
120
  pysdkit/tests/test_moving_decomp.py
120
121
  pysdkit/tests/test_ovmd.py
121
122
  pysdkit/tests/test_rlmd.py
123
+ pysdkit/tests/test_sjmd.py
122
124
  pysdkit/tests/test_stl.py
123
125
  pysdkit/tests/test_vmd.py
124
126
  pysdkit/tests/test_vmd2d.py
@@ -129,6 +129,7 @@ plot_IMFs_amplitude_spectra(IMFs, smooth="exp") # use exp smooth
129
129
  | [`BA-ACMD`](https://github.com/wwhenxuan/PySDKit/blob/main/pysdkit/_vmd/ba_acmd.py) (Bandwidth-aware adaptive chirp mode decomposition) | [[paper]](https://journals.sagepub.com/doi/abs/10.1177/14759217231174699) | [[code]](https://www.mathworks.com/matlabcentral/fileexchange/132792-bandwidth-aware-adaptive-chirp-mode-decomposition-ba-acmd?s_tid=srchtitle) | ✖️ |
130
130
  | [`JMD`](https://github.com/wwhenxuan/PySDKit/blob/main/pysdkit/_jmd/jmd.py) (Jump Plus AM-FM Mode Decomposition) | [[paper]](https://arxiv.org/abs/2407.07800) | [[code]](https://www.mathworks.com/matlabcentral/fileexchange/169388-jump-plus-am-fm-mode-decomposition-jmd?s_tid=prof_contriblnk) | ️✔️ |
131
131
  | [`MJMD`](https://github.com/wwhenxuan/PySDKit/blob/main/pysdkit/_jmd/mjmd.py) (Multivariate Jump Plus AM-FM Mode Decomposition) | [[paper]](https://arxiv.org/abs/2407.07800) | [[code]](https://www.mathworks.com/matlabcentral/fileexchange/169393-multivariate-jump-plus-am-fm-mode-decomposition-mjmd?s_tid=prof_contriblnk) | ✔️ |
132
+ | [`SJMD`](https://github.com/wwhenxuan/PySDKit/blob/main/pysdkit/_jmd/sjmd.py) / `SMJMD` (Successive Jump and Mode Decomposition) | [[paper]](https://arxiv.org/abs/2504.08453) | [[code]]() | ✔️ |
132
133
  | [`ESMD`]() (Extreme-Point Symmetric Mode Decomposition) | [[paper]](https://arxiv.org/abs/1303.6540) | [[code]](https://github.com/WuShichao/esmd) | ✖️ |
133
134
  | [`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) | ✖️ |
134
135
  | [`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) | ✔️ |
@@ -2,7 +2,7 @@
2
2
  A Python library for signal decomposition algorithms.
3
3
  """
4
4
 
5
- __version__ = "0.4.29"
5
+ __version__ = "0.4.30"
6
6
 
7
7
  # Empirical Mode Decomposition
8
8
  from ._emd import EMD
@@ -91,6 +91,9 @@ from ._jmd import JMD
91
91
  # Multivariate Jump Plus AM-FM Mode Decomposition
92
92
  from ._jmd import MJMD
93
93
 
94
+ # Successive Jump and Mode Decomposition (univariate / multivariate)
95
+ from ._jmd import SJMD, SMJMD
96
+
94
97
  # Feature Mode Decomposition
95
98
  from ._fmd import FMD
96
99
 
@@ -155,6 +158,7 @@ Adaptive Polymorphic Mode Decomposition | APMD
155
158
  Empirical Wavelet Transform | EWT
156
159
  Jump Plus AM-FM Mode Decomposition | JMD
157
160
  Multivariate Jump Plus AM-FM Mode Decomposition | MJMD
161
+ Successive Jump and Mode Decomposition | SJMD / SMJMD
158
162
  Feature Mode Decomposition | FMD
159
163
  Moving Average Decomposition | Moving
160
164
  Seasonal-Trend decomposition using LOESS | STL
@@ -196,6 +200,8 @@ __all__ = [
196
200
  "EWT",
197
201
  "JMD",
198
202
  "MJMD",
203
+ "SJMD",
204
+ "SMJMD",
199
205
  "FMD",
200
206
  "Moving_Decomp",
201
207
  "STL",
@@ -6,5 +6,6 @@ Created on 2025/02/05 13:32:00
6
6
  """
7
7
  from .jmd import JMD
8
8
  from .mjmd import MJMD
9
+ from .sjmd import SJMD, SMJMD
9
10
 
10
- __all__ = ["JMD", "MJMD"]
11
+ __all__ = ["JMD", "MJMD", "SJMD", "SMJMD"]
@@ -0,0 +1,464 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ Created on 2025/07/20
4
+ @author: Rongkun Zhu
5
+ @email: 25481568@life.hkbu.edu.hk
6
+
7
+ Successive Jump and Mode Decomposition (SJMD / SMJMD).
8
+
9
+ Faithful port of the authors' MATLAB ``SJMD.m``.
10
+ One implementation covers both univariate (SJMD) and multivariate (SMJMD).
11
+ """
12
+ from __future__ import annotations
13
+
14
+ from typing import List, Optional, Tuple, Union
15
+
16
+ import numpy as np
17
+ import scipy.sparse as sp
18
+
19
+
20
+ class SJMD(object):
21
+ """
22
+ Successive Jump and Mode Decomposition (SJMD / SMJMD).
23
+
24
+ Successively extracts AM–FM modes and a jump component without a
25
+ predefined mode count ``K``. The same routine accepts:
26
+
27
+ - univariate data ``(N,)`` or ``(1, N)`` → SJMD
28
+ - multivariate data ``(C, N)`` (or ``(N, C)`` if ``N > C``) → SMJMD
29
+
30
+ Nazari, Korshøj, ur Rehman, "Successive Jump and Mode Decomposition,"
31
+ arXiv:2504.08453.
32
+ """
33
+
34
+ def __init__(
35
+ self,
36
+ max_alpha: float = 80000.0,
37
+ tau: float = 50.0,
38
+ beta: float = 0.5,
39
+ b_bar: float = 0.9,
40
+ stopc: int = 4,
41
+ tol: float = 1e-5,
42
+ kdm: Optional[int] = None,
43
+ max_inner: int = 100,
44
+ min_alpha: float = 10.0,
45
+ ) -> None:
46
+ """
47
+ :param max_alpha: upper bound on the bandwidth parameter α
48
+ :param tau: jump-ADMM step used to form ``gamma``
49
+ :param beta: jump-constraint weight
50
+ :param b_bar: expected minimal jump height
51
+ :param stopc: stopping criterion
52
+ 1 exact reconstruction, 2 Bayesian, 3 k most dominant modes,
53
+ 4 power of the last mode (recommended / default)
54
+ :param tol: inner-loop convergence tolerance
55
+ :param kdm: number of dominant modes when ``stopc == 3``
56
+ :param max_inner: max ADMM iterations per mode / alpha stage
57
+ :param min_alpha: initial alpha for each successive mode
58
+ """
59
+ self.max_alpha = float(max_alpha)
60
+ self.tau = float(tau)
61
+ self.beta = float(beta)
62
+ self.b_bar = float(b_bar)
63
+ self.stopc = int(stopc)
64
+ self.tol = float(tol)
65
+ self.kdm = kdm
66
+ self.max_inner = int(max_inner)
67
+ self.min_alpha = float(min_alpha)
68
+
69
+ self.u: Optional[np.ndarray] = None
70
+ self.j: Optional[np.ndarray] = None
71
+ self.omega: Optional[np.ndarray] = None
72
+ self.n_channels: Optional[int] = None
73
+
74
+ def __call__(
75
+ self, signal: np.ndarray, return_all: bool = False
76
+ ) -> Union[np.ndarray, Tuple[np.ndarray, np.ndarray]]:
77
+ return self.fit_transform(signal=signal, return_all=return_all)
78
+
79
+ def __str__(self) -> str:
80
+ return "Successive Jump and Mode Decomposition (SJMD / SMJMD)"
81
+
82
+ @staticmethod
83
+ def _resolve_input(signal: np.ndarray) -> Tuple[np.ndarray, int, int, bool]:
84
+ """Return ``(data CxN, C, N, univariate)`` matching MATLAB layout."""
85
+ arr = np.asarray(signal, dtype=float)
86
+ if arr.ndim == 1:
87
+ data = arr.reshape(1, -1)
88
+ uni = True
89
+ elif arr.ndim == 2:
90
+ x, y = arr.shape
91
+ if x > y:
92
+ data = arr.T.copy()
93
+ uni = data.shape[0] == 1
94
+ else:
95
+ data = arr.copy()
96
+ uni = data.shape[0] == 1
97
+ else:
98
+ raise ValueError("SJMD expects 1-D or 2-D input")
99
+
100
+ # MATLAB: if mod(2*save_T,2)~=0 → trim (always false for integer);
101
+ # keep even-length guard used elsewhere in the toolbox.
102
+ if data.shape[1] % 2 == 1:
103
+ data = data[:, :-1]
104
+ c, n = data.shape
105
+ if n < 8:
106
+ raise ValueError("signal length must be at least 8 samples")
107
+ return data, c, n, uni
108
+
109
+ @staticmethod
110
+ def _mirror(signal: np.ndarray) -> np.ndarray:
111
+ """Mirror extension as in MATLAB ``SJMD.m`` (overlap + append last)."""
112
+ t = signal.shape[1]
113
+ half = t // 2
114
+ f_mir = np.zeros((signal.shape[0], 2 * t - 1), dtype=float)
115
+ # f_mir(:,1:T/2) = signal(:,T/2:-1:1)
116
+ f_mir[:, 0:half] = signal[:, half - 1 :: -1]
117
+ # f_mir(:,T/2:3*T/2-1) = signal (overwrites column T/2)
118
+ f_mir[:, half - 1 : half - 1 + t] = signal
119
+ # f_mir(:,3*T/2:2*T-1) = signal(:,T:-1:T/2+1)
120
+ f_mir[:, 3 * half - 1 : 2 * t - 1] = signal[:, t - 1 : half - 1 : -1]
121
+ # f_mir = [f_mir f_mir(:,end)]
122
+ return np.concatenate([f_mir, f_mir[:, -1:]], axis=1)
123
+
124
+ @staticmethod
125
+ def _soft_jump(h: np.ndarray, mu: float, b: float) -> np.ndarray:
126
+ abs_h = np.abs(h) + np.finfo(float).eps
127
+ scale = (1.0 / (1.0 - mu * b)) - (
128
+ mu * np.sqrt(2.0 * b) / (1.0 - mu * b)
129
+ ) / abs_h
130
+ return np.clip(scale, 0.0, 1.0) * h
131
+
132
+ @staticmethod
133
+ def _hermitian_from_onesided(
134
+ onesided: np.ndarray, half: int, t_len: int
135
+ ) -> np.ndarray:
136
+ """Build full Hermitian spectrum from one-sided FFT bins."""
137
+ out = np.zeros((t_len,) + onesided.shape[1:], dtype=complex)
138
+ out[half:t_len, ...] = onesided[half:t_len, ...]
139
+ out[half:0:-1, ...] = np.conj(onesided[half:t_len, ...])
140
+ out[0, ...] = np.conj(out[-1, ...])
141
+ return out
142
+
143
+ def _check_stop(
144
+ self,
145
+ k: int,
146
+ f_mir: np.ndarray,
147
+ sum_u: np.ndarray,
148
+ u_hat_i: np.ndarray,
149
+ f_hat_temp: np.ndarray,
150
+ polm: List[float],
151
+ polm_temp: Optional[float],
152
+ bic: List[float],
153
+ t_len: int,
154
+ ) -> Tuple[bool, List[float], Optional[float], List[float]]:
155
+ """MATLAB Part 5 stopping criteria."""
156
+ stop = False
157
+ stopc = self.stopc
158
+
159
+ if stopc == 1:
160
+ numer = (1.0 / t_len) * (np.linalg.norm(f_mir - sum_u) ** 2)
161
+ denom = (1.0 / t_len) * (np.linalg.norm(f_mir) ** 2) + np.finfo(float).eps
162
+ if numer / denom <= 0.02:
163
+ stop = True
164
+ elif stopc == 2:
165
+ if u_hat_i.shape[2] == 1:
166
+ err = np.linalg.norm(f_hat_temp - u_hat_i[:, :, 0].T) ** 2
167
+ else:
168
+ err = np.linalg.norm(f_hat_temp - np.sum(u_hat_i, axis=2).T) ** 2
169
+ bic.append(
170
+ 2 * t_len * np.log(err + np.finfo(float).eps)
171
+ + (3 * k) * np.log(2 * t_len)
172
+ )
173
+ if k > 1 and bic[-1] > bic[-2]:
174
+ stop = True
175
+ elif stopc == 3:
176
+ if self.kdm is None:
177
+ raise ValueError("kdm must be set when stopc == 3")
178
+ if k == self.kdm:
179
+ stop = True
180
+ else:
181
+ # case 4 / default: power of the last mode
182
+ power = float(
183
+ np.mean(
184
+ np.abs(
185
+ np.sum(
186
+ (1.0 / t_len)
187
+ * u_hat_i[:, :, k - 1]
188
+ * np.conj(u_hat_i[:, :, k - 1]),
189
+ axis=0,
190
+ )
191
+ )
192
+ )
193
+ )
194
+ if k < 2:
195
+ polm_temp = power
196
+ polm.append(1.0)
197
+ else:
198
+ polm.append(power / (polm_temp + np.finfo(float).eps))
199
+ if abs(polm[-1] - polm[-2]) < 0.01:
200
+ stop = True
201
+
202
+ return stop, polm, polm_temp, bic
203
+
204
+ def fit_transform(
205
+ self, signal: np.ndarray, return_all: bool = False
206
+ ) -> Union[np.ndarray, Tuple[np.ndarray, np.ndarray]]:
207
+ """
208
+ Run SJMD / SMJMD.
209
+
210
+ :param signal: ``(N,)``, ``(1, N)``, or ``(C, N)``
211
+ :param return_all: if True, return ``(modes, jump)``
212
+ :return:
213
+ - univariate: modes ``(K, N)``, jump ``(N,)``
214
+ - multivariate: modes ``(K, N, C)``, jump ``(C, N)``
215
+ """
216
+ data, n_ch, save_t, univariate = self._resolve_input(signal)
217
+ self.n_channels = n_ch
218
+
219
+ shift = data.mean(axis=1, keepdims=True)
220
+ data = data - shift
221
+
222
+ f_mir = self._mirror(data)
223
+ f = f_mir.copy()
224
+ t_len = f.shape[1]
225
+ half = t_len // 2
226
+ t = np.arange(1, t_len + 1, dtype=float) / t_len
227
+ omega_freqs = t - 0.5 - 1.0 / t_len
228
+
229
+ f_hat = np.fft.fftshift(np.fft.fft(f, axis=1), axes=1)
230
+ f_hat_onesided = f_hat.copy()
231
+ f_hat_onesided[:, :half] = 0.0
232
+ f_hat_temp = f_hat_onesided.copy()
233
+
234
+ n_max = self.max_inner
235
+ b = 2.0 / (self.b_bar**2)
236
+ gamma = self.tau * (0.5 * b * self.beta)
237
+ d = np.ones(t_len)
238
+ D = sp.diags([-d, d], [0, 1], shape=(t_len, t_len), format="lil")
239
+ D[-1, :] = 0
240
+ D = D.tocsr()
241
+ D_arr = D.toarray()
242
+ DTD = (D.T @ D).tocsc()
243
+ coef1 = 1.0 / gamma
244
+ mu = 2.0 * self.beta / gamma
245
+ A_jump = sp.eye(t_len, format="csc") + gamma * DTD
246
+ solve_jump = sp.linalg.factorized(A_jump)
247
+
248
+ x = np.zeros((t_len, n_ch), dtype=float)
249
+ rho = np.zeros((t_len, n_ch), dtype=float)
250
+ j_comp = np.zeros((n_ch, t_len), dtype=float)
251
+ j_hat_plus = np.zeros((t_len, n_ch, n_max), dtype=complex)
252
+ g_hat_plus = np.zeros((t_len, n_ch, n_max), dtype=complex)
253
+
254
+ u_hat_temp_list: List[np.ndarray] = []
255
+ omega_d_temp: List[float] = []
256
+ u_hat_i = np.zeros((t_len, n_ch, 0), dtype=complex)
257
+
258
+ sum_u = np.zeros((n_ch, t_len), dtype=float)
259
+ sc2 = False
260
+ k = 1
261
+ polm: List[float] = []
262
+ polm_temp: Optional[float] = None
263
+ bic: List[float] = []
264
+
265
+ while not sc2:
266
+ # ---- Part 2/3: extract one mode while increasing alpha ----
267
+ omega_l = np.zeros(n_max, dtype=float)
268
+ u_hat_l = np.zeros((t_len, n_ch, n_max), dtype=complex)
269
+ udiff = self.tol + np.spacing(1)
270
+ n = 0 # 0-based; MATLAB starts at 1 and writes to n+1
271
+ m = 0.0
272
+ bf = 0
273
+ alpha_val = self.min_alpha
274
+
275
+ while alpha_val < (self.max_alpha + 1):
276
+ while udiff > self.tol and n < n_max - 1:
277
+ for c in range(n_ch):
278
+ denom = 1.0 + alpha_val * (omega_freqs - omega_l[n]) ** 2
279
+ u_hat_l[:, c, n + 1] = (
280
+ f_hat_onesided[c, :]
281
+ - j_hat_plus[:, c, n]
282
+ - g_hat_plus[:, c, n]
283
+ ) / denom
284
+
285
+ num_g = (
286
+ (alpha_val**2)
287
+ * (omega_freqs - omega_l[n]) ** 4
288
+ * (
289
+ f_hat_onesided[c, :]
290
+ - j_hat_plus[:, c, n]
291
+ - u_hat_l[:, c, n + 1]
292
+ )
293
+ )
294
+ den_g = (
295
+ 1.0 + 2.0 * (alpha_val**2) * (omega_freqs - omega_l[n]) ** 4
296
+ )
297
+ g_hat_plus[:, c, n + 1] = num_g / den_g
298
+
299
+ # shared / single center frequency
300
+ abs2 = np.abs(u_hat_l[half:t_len, :, n + 1]) ** 2
301
+ num_w = omega_freqs[half:t_len] @ abs2
302
+ omega_l[n + 1] = float(
303
+ np.sum(num_w) / (np.sum(abs2) + np.finfo(float).eps)
304
+ )
305
+
306
+ # back to time domain
307
+ u_hat = self._hermitian_from_onesided(
308
+ u_hat_l[:, :, n + 1], half=half, t_len=t_len
309
+ )
310
+ g_hat = self._hermitian_from_onesided(
311
+ g_hat_plus[:, :, n + 1], half=half, t_len=t_len
312
+ )
313
+
314
+ u_td = np.zeros((n_ch, t_len), dtype=float)
315
+ fr_td = np.zeros((n_ch, t_len), dtype=float)
316
+ for c in range(n_ch):
317
+ u_td[c, :] = np.real(np.fft.ifft(np.fft.ifftshift(u_hat[:, c])))
318
+ fr_td[c, :] = np.real(
319
+ np.fft.ifft(np.fft.ifftshift(g_hat[:, c]))
320
+ )
321
+
322
+ for c in range(n_ch):
323
+ temp = u_td[c, :] + fr_td[c, :]
324
+ f1 = f[c, :]
325
+ rhs = (
326
+ gamma * (D_arr.T @ x[:, c])
327
+ - (D_arr.T @ rho[:, c])
328
+ + f1
329
+ - temp
330
+ )
331
+ v = solve_jump(rhs)
332
+ dv = D_arr @ v
333
+ h = dv + coef1 * rho[:, c]
334
+ x[:, c] = self._soft_jump(h, mu=mu, b=b)
335
+ rho[:, c] = rho[:, c] - gamma * (x[:, c] - dv)
336
+ v = v - (np.mean(v) - np.mean(f1))
337
+ j_comp[c, :] = v
338
+ j_hat_plus[:, c, n + 1] = np.fft.fftshift(np.fft.fft(v))
339
+ j_hat_plus[:half, c, n + 1] = 0.0
340
+
341
+ n += 1
342
+ ud = u_hat_l[:, :, n] - u_hat_l[:, :, n - 1]
343
+ udiff_m = (1.0 / t_len) * (ud.conj().T @ ud)
344
+ jd = j_hat_plus[:, :, n] - j_hat_plus[:, :, n - 1]
345
+ jdiff_m = (1.0 / t_len) * (jd.conj().T @ jd)
346
+ udiff = float(
347
+ np.spacing(1)
348
+ + np.abs(np.sum(udiff_m))
349
+ + np.abs(np.sum(jdiff_m))
350
+ )
351
+
352
+ # ---- Part 3: increase alpha ----
353
+ if abs(m - np.log(self.max_alpha)) > 1:
354
+ m = m + 1.0
355
+ else:
356
+ m = m + 0.05
357
+ bf += 1
358
+ if bf >= 2:
359
+ alpha_val = alpha_val + 1.0
360
+
361
+ if alpha_val <= (self.max_alpha - 1):
362
+ if bf == 1:
363
+ alpha_val = self.max_alpha - 1
364
+ else:
365
+ alpha_val = float(np.exp(m))
366
+
367
+ # MATLAB: omega_L = omega_L(n) → keep current omega as scalar start
368
+ omega_keep = float(omega_l[n])
369
+ udiff = self.tol + np.spacing(1)
370
+ temp_ud = u_hat_l[:, :, n].copy()
371
+ n = 0
372
+ u_hat_l = np.zeros((t_len, n_ch, n_max), dtype=complex)
373
+ u_hat_l[:, :, 0] = temp_ud
374
+ omega_l = np.zeros(n_max, dtype=float)
375
+ omega_l[0] = omega_keep
376
+
377
+ # ---- Part 4: save mode ----
378
+ # MATLAB: omega_L=omega_L(omega_L>0); omega_d_Temp(k)=omega_L(n-1)
379
+ # With a leading zero at omega_l[0], filtering + (n-1) selects the last
380
+ # positive center frequency written during ADMM.
381
+ omega_pos = omega_l[omega_l > 0]
382
+ omega_save = float(omega_pos[-1]) if omega_pos.size else 0.0
383
+
384
+ mode_spec = u_hat_l[:, :, n].copy()
385
+ u_hat_temp_list.append(mode_spec)
386
+ omega_d_temp.append(omega_save)
387
+
388
+ u_hat_i = np.concatenate([u_hat_i, mode_spec[:, :, None]], axis=2)
389
+
390
+ u_hat = self._hermitian_from_onesided(mode_spec, half=half, t_len=t_len)
391
+ u_td = np.zeros((n_ch, t_len), dtype=float)
392
+ for c in range(n_ch):
393
+ u_td[c, :] = np.real(np.fft.ifft(np.fft.ifftshift(u_hat[:, c])))
394
+
395
+ f = f - u_td
396
+ sum_u = sum_u + u_td
397
+
398
+ f_hat = np.fft.fftshift(np.fft.fft(f, axis=1), axes=1)
399
+ f_hat_onesided = f_hat.copy()
400
+ f_hat_onesided[:, :half] = 0.0
401
+
402
+ x = np.zeros((t_len, n_ch), dtype=float)
403
+ rho = np.zeros((t_len, n_ch), dtype=float)
404
+
405
+ sc2, polm, polm_temp, bic = self._check_stop(
406
+ k=k,
407
+ f_mir=f_mir,
408
+ sum_u=sum_u,
409
+ u_hat_i=u_hat_i,
410
+ f_hat_temp=f_hat_temp,
411
+ polm=polm,
412
+ polm_temp=polm_temp,
413
+ bic=bic,
414
+ t_len=t_len,
415
+ )
416
+
417
+ # Part 6: reset for next mode
418
+ k += 1
419
+ if k > 50:
420
+ sc2 = True
421
+
422
+ # ---- Part 7: reconstruct ----
423
+ n_modes = len(omega_d_temp)
424
+ if n_modes == 0:
425
+ raise RuntimeError("SJMD failed to extract any mode")
426
+
427
+ u_hat_temp = np.stack(u_hat_temp_list, axis=2) # (T, C, K)
428
+ omega = np.asarray(omega_d_temp, dtype=float)
429
+
430
+ u_hat = self._hermitian_from_onesided(u_hat_temp, half=half, t_len=t_len)
431
+
432
+ u_time = np.zeros((t_len, n_ch, n_modes), dtype=float)
433
+ for c in range(n_ch):
434
+ for ki in range(n_modes):
435
+ u_time[:, c, ki] = np.real(
436
+ np.fft.ifft(np.fft.ifftshift(u_hat[:, c, ki]))
437
+ )
438
+
439
+ order = np.argsort(omega)
440
+ u_time = u_time[:, :, order]
441
+ omega = omega[order]
442
+
443
+ # remove mirror: MATLAB T/4+1:3*T/4
444
+ u_crop = u_time[t_len // 4 : 3 * t_len // 4, :, :]
445
+ j_crop = j_comp[:, t_len // 4 : 3 * t_len // 4] + shift
446
+
447
+ if univariate:
448
+ modes = np.transpose(u_crop[:, 0, :]) # (K, N)
449
+ jump = j_crop[0, :]
450
+ else:
451
+ modes = np.transpose(u_crop, (2, 0, 1)) # (K, N, C)
452
+ jump = j_crop
453
+
454
+ self.u = modes
455
+ self.j = jump
456
+ self.omega = omega
457
+
458
+ if return_all:
459
+ return modes, jump
460
+ return modes
461
+
462
+
463
+ # Paper name for the multivariate case (same implementation)
464
+ SMJMD = SJMD