PySDKit 0.4.22__tar.gz → 0.4.24__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 (140) hide show
  1. {pysdkit-0.4.22 → pysdkit-0.4.24}/PKG-INFO +2 -2
  2. {pysdkit-0.4.22 → pysdkit-0.4.24}/PySDKit.egg-info/PKG-INFO +2 -2
  3. {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/__init__.py +6 -1
  4. {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/_emd/ceemdan.py +33 -83
  5. {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/_emd/emd.py +1 -2
  6. {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/_ewt/ewt.py +3 -3
  7. {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/_fmd/__init__.py +1 -0
  8. pysdkit-0.4.24/pysdkit/_fmd/fmd.py +302 -0
  9. {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/_jmd/jmd.py +1 -3
  10. {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/_lmd/rlmd.py +9 -5
  11. {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/_vmd/base.py +3 -3
  12. {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/_vmd/svmd.py +1 -2
  13. {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/_vmd2d/cvmd2d.py +8 -14
  14. {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/data/_test_univariate.py +4 -14
  15. {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/tests/models/test_knn.py +6 -2
  16. {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/tests/models/test_pca.py +6 -2
  17. {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/tests/test_ceemdan.py +29 -38
  18. {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/tests/test_ewt.py +9 -3
  19. {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/tests/test_faemd.py +12 -4
  20. {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/tests/test_moving_decomp.py +6 -2
  21. {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/tests/test_vmd2d.py +12 -4
  22. {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/tsa/_stl.py +2 -0
  23. {pysdkit-0.4.22 → pysdkit-0.4.24}/setup.py +2 -2
  24. pysdkit-0.4.22/pysdkit/_fmd/fmd.py +0 -149
  25. {pysdkit-0.4.22 → pysdkit-0.4.24}/LICENSE +0 -0
  26. {pysdkit-0.4.22 → pysdkit-0.4.24}/PySDKit.egg-info/SOURCES.txt +0 -0
  27. {pysdkit-0.4.22 → pysdkit-0.4.24}/PySDKit.egg-info/dependency_links.txt +0 -0
  28. {pysdkit-0.4.22 → pysdkit-0.4.24}/PySDKit.egg-info/requires.txt +0 -0
  29. {pysdkit-0.4.22 → pysdkit-0.4.24}/PySDKit.egg-info/top_level.txt +0 -0
  30. {pysdkit-0.4.22 → pysdkit-0.4.24}/README.md +0 -0
  31. {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/_emd/__init__.py +0 -0
  32. {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/_emd/_find_extrema.py +0 -0
  33. {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/_emd/_prepare_points.py +0 -0
  34. {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/_emd/_splines.py +0 -0
  35. {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/_emd/eemd.py +0 -0
  36. {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/_emd/efd.py +0 -0
  37. {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/_emd/hht/__init__.py +0 -0
  38. {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/_emd/hht/frequency.py +0 -0
  39. {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/_emd/hht/hht.py +0 -0
  40. {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/_emd/memd.py +0 -0
  41. {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/_emd/remd.py +0 -0
  42. {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/_emd/tvf_emd.py +0 -0
  43. {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/_emd2d/__init__.py +0 -0
  44. {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/_emd2d/bemd.py +0 -0
  45. {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/_emd2d/bmemd.py +0 -0
  46. {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/_emd2d/emd2d.py +0 -0
  47. {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/_esmd/__init__.py +0 -0
  48. {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/_esmd/esmd.py +0 -0
  49. {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/_ewt/__init__.py +0 -0
  50. {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/_ewt/ewt2d.py +0 -0
  51. {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/_faemd/__init__.py +0 -0
  52. {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/_faemd/extrema.py +0 -0
  53. {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/_faemd/faemd.py +0 -0
  54. {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/_faemd/faemd2d.py +0 -0
  55. {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/_faemd/faemd3d.py +0 -0
  56. {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/_faemd/filter.py +0 -0
  57. {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/_fmd/nfmd.py +0 -0
  58. {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/_hvd/__init__.py +0 -0
  59. {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/_hvd/hvd.py +0 -0
  60. {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/_ifd/__init__.py +0 -0
  61. {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/_itd/__init__.py +0 -0
  62. {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/_itd/itd.py +0 -0
  63. {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/_jmd/__init__.py +0 -0
  64. {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/_jmd/mjmd.py +0 -0
  65. {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/_lmd/__init__.py +0 -0
  66. {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/_lmd/lmd.py +0 -0
  67. {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/_osd/__init__.py +0 -0
  68. {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/_ssa/__init__.py +0 -0
  69. {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/_ssa/ssa.py +0 -0
  70. {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/_vmd/__init__.py +0 -0
  71. {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/_vmd/acmd.py +0 -0
  72. {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/_vmd/avmd.py +0 -0
  73. {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/_vmd/ba_acmd.py +0 -0
  74. {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/_vmd/mvmd.py +0 -0
  75. {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/_vmd/vmd_c.py +0 -0
  76. {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/_vmd/vmd_f.py +0 -0
  77. {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/_vmd/vme.py +0 -0
  78. {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/_vmd2d/__init__.py +0 -0
  79. {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/_vmd2d/vmd2d.py +0 -0
  80. {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/_vncmd/__init__.py +0 -0
  81. {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/_vncmd/avncmd.py +0 -0
  82. {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/_vncmd/incmd.py +0 -0
  83. {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/_vncmd/mncmd.py +0 -0
  84. {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/_vncmd/vncmd.py +0 -0
  85. {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/data/__init__.py +0 -0
  86. {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/data/_add_noise.py +0 -0
  87. {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/data/_cube.py +0 -0
  88. {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/data/_generator.py +0 -0
  89. {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/data/_image.py +0 -0
  90. {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/data/_models.py +0 -0
  91. {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/data/_time_series.py +0 -0
  92. {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/data/texture.txt +0 -0
  93. {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/entropy/__init__.py +0 -0
  94. {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/entropy/_approxiamte_entropy.py +0 -0
  95. {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/entropy/_permutation_entropy.py +0 -0
  96. {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/entropy/_sample_entropy.py +0 -0
  97. {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/models/__init__.py +0 -0
  98. {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/models/_base.py +0 -0
  99. {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/models/_kmeans.py +0 -0
  100. {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/models/_knn.py +0 -0
  101. {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/models/_pca.py +0 -0
  102. {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/plot/__init__.py +0 -0
  103. {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/plot/_fourier_spectra.py +0 -0
  104. {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/plot/_functions.py +0 -0
  105. {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/plot/_plot_images.py +0 -0
  106. {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/plot/_plot_imfs.py +0 -0
  107. {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/plot/_plot_signal.py +0 -0
  108. {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/tests/__init__.py +0 -0
  109. {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/tests/data/__init__.py +0 -0
  110. {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/tests/data/test_generator_cube.py +0 -0
  111. {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/tests/data/test_generator_image.py +0 -0
  112. {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/tests/data/test_generator_signal.py +0 -0
  113. {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/tests/data/test_generator_univariate_signal.py +0 -0
  114. {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/tests/models/__init__.py +0 -0
  115. {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/tests/test_all.py +0 -0
  116. {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/tests/test_emd.py +0 -0
  117. {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/tests/test_faemd2d.py +0 -0
  118. {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/tests/test_faemd3d.py +0 -0
  119. {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/tests/test_lmd.py +0 -0
  120. {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/tests/test_rlmd.py +0 -0
  121. {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/tests/test_stl.py +0 -0
  122. {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/tests/test_vmd.py +0 -0
  123. {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/tsa/__init__.py +0 -0
  124. {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/tsa/_dtw.py +0 -0
  125. {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/tsa/_moving_decomp.py +0 -0
  126. {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/tsa/_mstl.py +0 -0
  127. {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/utils/__init__.py +0 -0
  128. {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/utils/_cite.py +0 -0
  129. {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/utils/_correlation.py +0 -0
  130. {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/utils/_diagnalization.py +0 -0
  131. {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/utils/_differ.py +0 -0
  132. {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/utils/_fft.py +0 -0
  133. {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/utils/_function.py +0 -0
  134. {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/utils/_hilbert.py +0 -0
  135. {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/utils/_instantaneous.py +0 -0
  136. {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/utils/_kernel_matrix.py +0 -0
  137. {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/utils/_mirror.py +0 -0
  138. {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/utils/_process.py +0 -0
  139. {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/utils/_smooth1d.py +0 -0
  140. {pysdkit-0.4.22 → pysdkit-0.4.24}/setup.cfg +0 -0
@@ -1,9 +1,9 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PySDKit
3
- Version: 0.4.22
3
+ Version: 0.4.24
4
4
  Summary: A Python library for signal decomposition algorithms with a unified interface.
5
5
  Home-page: https://github.com/wwhenxuan/PySDKit
6
- Author: whenxuan, changewam, josefinez, Yuan Feng
6
+ Author: whenxuan, changewam, josefinez, Yuan Feng, Wentong Zhao, JacktheFowler, Deeksha Manjunath
7
7
  Author-email: wwhenxuan@gmail.com
8
8
  Keywords: signal decomposition,signal processing,machine learning
9
9
  Classifier: Development Status :: 3 - Alpha
@@ -1,9 +1,9 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PySDKit
3
- Version: 0.4.22
3
+ Version: 0.4.24
4
4
  Summary: A Python library for signal decomposition algorithms with a unified interface.
5
5
  Home-page: https://github.com/wwhenxuan/PySDKit
6
- Author: whenxuan, changewam, josefinez, Yuan Feng
6
+ Author: whenxuan, changewam, josefinez, Yuan Feng, Wentong Zhao, JacktheFowler, Deeksha Manjunath
7
7
  Author-email: wwhenxuan@gmail.com
8
8
  Keywords: signal decomposition,signal processing,machine learning
9
9
  Classifier: Development Status :: 3 - Alpha
@@ -2,7 +2,7 @@
2
2
  A Python library for signal decomposition algorithms.
3
3
  """
4
4
 
5
- __version__ = "0.4.22"
5
+ __version__ = "0.4.24"
6
6
 
7
7
  # Empirical Mode Decomposition
8
8
  from ._emd import EMD
@@ -76,6 +76,9 @@ from ._ewt import ewt, EWT
76
76
  # Jump Plus AM-FM Mode Decomposition
77
77
  from ._jmd import JMD
78
78
 
79
+ # Feature Mode Decomposition
80
+ from ._fmd import FMD
81
+
79
82
  # Moving Average Decomposition
80
83
  from .tsa import Moving_Decomp
81
84
 
@@ -132,6 +135,7 @@ Variational Nonlinear Chirp Mode Decomposition | VNCMD
132
135
  Iterative Nonlinear Chirp Mode Decomposition | INCMD
133
136
  Empirical Wavelet Transform | EWT
134
137
  Jump Plus AM-FM Mode Decomposition | JMD
138
+ Feature Mode Decomposition | FMD
135
139
  Moving Average Decomposition | Moving
136
140
  Seasonal-Trend decomposition using LOESS | STL
137
141
  Hilbert-Huang Transform | HHT
@@ -167,6 +171,7 @@ __all__ = [
167
171
  "ewt",
168
172
  "EWT",
169
173
  "JMD",
174
+ "FMD",
170
175
  "Moving_Decomp",
171
176
  "STL",
172
177
  "models",
@@ -71,49 +71,38 @@ class CEEMDAN(object):
71
71
  :param max_iter: maximum number of decomposition iterations
72
72
  :param random_seed: create a random seed for the random number generator
73
73
  """
74
- # Fixed configuration of the EMD algorithm
75
74
  self.max_imfs = max_imfs
75
+ self._max_imf_iters = 100
76
76
  self.trials = trials
77
77
  self.epsilon = epsilon
78
78
  self.max_iter = max_iter
79
79
 
80
- # Configuration parameters related to multi-process
81
80
  self.parallel = parallel
82
81
  self.processes = processes
83
82
 
84
- # Parameters for creating noise
85
83
  self.noise_scale = noise_scale
86
84
  self.noise_kind = noise_kind
87
85
  self.noise_list = ["normal", "uniform"]
88
- # Whether to normalize the decomposed noise
89
86
  self.beta_progress = beta_progress
90
87
 
91
- # Thresholds related to algorithm stopping
92
88
  self.range_thr = range_thr
93
89
  self.total_power_thr = total_power_thr
94
90
 
95
- # Create the EMD algorithm to be integrated
96
91
  self.EMD = (
97
92
  EMD(max_imfs=max_imfs, random_seed=random_seed, max_iteration=self.max_iter)
98
93
  if ext_EMD is None
99
94
  else ext_EMD
100
95
  )
101
96
 
102
- # Creating a random number generator
103
97
  self.random_seed = random_seed
104
98
  self.rng = np.random.RandomState(seed=random_seed)
105
99
 
106
- # Generated noise sequence
107
100
  self.all_noises = None
108
-
109
- # List of noises to be decomposed
110
101
  self.all_noise_EMD = []
111
102
 
112
- # Record the results of this algorithm decomposition
113
103
  self.imfs = None
114
104
  self.residue = None
115
105
 
116
- # Used to update the trial
117
106
  self._signal, self._time, self._seq_len, self._scale = None, None, None, None
118
107
 
119
108
  def __call__(
@@ -171,9 +160,8 @@ class CEEMDAN(object):
171
160
  else:
172
161
  all_noise_EMD = [self.EMD(noise, max_imfs=-1) for noise in self.all_noises]
173
162
 
174
- # Normalize the decomposed noise sequences
175
163
  if self.beta_progress:
176
- all_stds = [np.var(imfs[0]) for imfs in all_noise_EMD]
164
+ all_stds = [np.std(imfs[0]) for imfs in all_noise_EMD]
177
165
  all_noise_EMD = [
178
166
  imfs / imfs_std for (imfs, imfs_std) in zip(all_noise_EMD, all_stds)
179
167
  ]
@@ -188,57 +176,45 @@ class CEEMDAN(object):
188
176
  progress: Optional[bool] = True,
189
177
  ) -> np.ndarray:
190
178
  """Perform the specified EEMD algorithm to obtain the corresponding signal decomposition results."""
191
- # Length of the signal
192
179
  seq_len = len(signal)
193
180
 
194
- # Generate the time array
195
181
  if time is None:
196
182
  time = get_timeline(seq_len, dtype=signal.dtype)
197
183
 
198
- # Store the current signal state
199
184
  self._signal, self._time, self._seq_len = signal, time, seq_len
185
+ self._eemd_max_imfs = max_imfs if max_imfs is not None else self.max_imfs
200
186
 
201
- if max_imfs is not None:
202
- self.max_imfs = max_imfs
203
-
204
- # For the specified number of trials, perform EMD on the signal with added white noise
205
187
  if self.parallel:
206
188
  pool = Pool(processes=self.processes)
207
189
  map_pool = pool.imap_unordered
208
- else: # Not parallel
190
+ else:
209
191
  map_pool = map
210
192
 
211
- # Create an array to store the decomposition results
212
- self.imfs = np.zeros((1, seq_len))
193
+ eemd_imfs = np.zeros((1, seq_len))
213
194
 
214
- # Create an iterator for the decomposition process
215
195
  it = (
216
196
  iter
217
197
  if not progress
218
198
  else lambda x: tqdm(x, desc="Decomposing noise", total=self.trials)
219
199
  )
220
200
 
221
- # Perform signal decomposition and store the results
222
201
  for IMFs in it(map_pool(self._update_trial, range(self.trials))):
223
- if self.imfs.shape[0] < IMFs.shape[0]:
224
- num_new_layers = IMFs.shape[0] - self.imfs.shape[0]
225
- self.imfs = np.vstack(
226
- (self.imfs, np.zeros(shape=(num_new_layers, seq_len)))
202
+ if eemd_imfs.shape[0] < IMFs.shape[0]:
203
+ num_new_layers = IMFs.shape[0] - eemd_imfs.shape[0]
204
+ eemd_imfs = np.vstack(
205
+ (eemd_imfs, np.zeros(shape=(num_new_layers, seq_len)))
227
206
  )
228
- self.imfs[: IMFs.shape[0]] += IMFs
207
+ eemd_imfs[: IMFs.shape[0]] += IMFs
229
208
 
230
209
  if self.parallel:
231
210
  pool.close()
232
211
 
233
- return self.imfs / self.trials
212
+ return eemd_imfs / self.trials
234
213
 
235
214
  def _update_trial(self, trial: int) -> np.ndarray:
236
215
  """A single trial evaluation, i.e., EMD(signal + noise)."""
237
- # Generate noise
238
216
  noise = self.epsilon * self.all_noise_EMD[trial][0]
239
-
240
- # Return the result of a single EMD execution
241
- return self._run_emd(self._signal + noise, self._time, self.max_imfs)
217
+ return self._run_emd(self._signal + noise, self._time, self._eemd_max_imfs)
242
218
 
243
219
  def _run_emd(
244
220
  self,
@@ -302,41 +278,34 @@ class CEEMDAN(object):
302
278
  """
303
279
  Test for end condition for CEEMDAN method.
304
280
 
305
- The algorithm's performance can be enhanced by adjusting the decomposition parameters.
281
+ Procedure stops if:
282
+ * number of components reaches provided `max_imf`, or
283
+ * residue has insufficient extrema for further decomposition, or
284
+ * residue amplitude range falls below `range_thr`, or
285
+ * residue total power falls below `total_power_thr`.
306
286
 
307
287
  :param signal: the original input signal as a numpy ndarray
308
288
  :param cIMFs: the signal decomposition results
309
- :param max_imf: the maximum number of IMFs obtained
289
+ :param max_imf: the maximum number of IMFs to extract (-1 means no limit)
310
290
  :return: a boolean stop flag indicating whether to stop the CEEMDAN method
311
291
  """
312
- # Number of IMFs currently decomposed
313
292
  imf_number = cIMFs.shape[0]
314
293
 
315
- # Check if the maximum number of cIMFs has been reached
316
294
  if 0 < max_imf <= imf_number:
317
295
  return True
318
296
 
319
- # Compute the Empirical Mode Decomposition (EMD) for the residue
320
297
  R = signal - np.sum(cIMFs, axis=0)
321
- # _test_imfs = self._emd(signal=R, time=None, max_imfs=2)
322
- # print("test imfs", _test_imfs.shape)
323
- #
324
- # # Check if the residue is an IMF or has no extrema
325
- # if _test_imfs.shape[0] == 1:
326
- # print("Not enough extrema")
327
- # return True
328
-
329
- # Check for range threshold
298
+
299
+ _test_imfs = self._run_emd(signal=R, time=None, max_imfs=1)
300
+ if _test_imfs.shape[0] == 1:
301
+ return True
302
+
330
303
  if np.max(R) - np.min(R) < self.range_thr:
331
- print("Finished by Range")
332
304
  return True
333
305
 
334
- # Check for power threshold
335
306
  if np.sum(np.abs(R)) < self.total_power_thr:
336
- print("Finished by Power")
337
307
  return True
338
308
 
339
- # The algorithm did not stop
340
309
  return False
341
310
 
342
311
  def fit_transform(
@@ -351,81 +320,62 @@ class CEEMDAN(object):
351
320
 
352
321
  :param signal: the original signal on which CEEMDAN is to be performed
353
322
  :param time: the time array of the original input signal
354
- :param max_imfs: the maximum number of components to extract
323
+ :param max_imfs: the maximum number of components to extract (-1 for no limit)
355
324
  :param progress: whether to print out '.' every 1s to indicate progress
356
325
  :return: the intrinsic mode functions (IMFs) after signal decomposition
357
326
  """
358
- # Normalize the signal's amplitude
359
327
  scale_s = np.std(signal)
360
328
  signal = signal / scale_s
361
329
 
362
- # Begin executing the specific decomposition algorithm
363
330
  max_imfs = self.max_imfs if max_imfs is None else max_imfs
364
- total = (max_imfs - 1) if max_imfs != -1 else None
365
331
 
366
- # Define the noise sequences to be added
367
332
  self.all_noises = self._generate_noise(
368
333
  self.noise_scale, size=(self.trials, signal.size)
369
334
  )
370
-
371
- # Decompose all noise and remember the standard deviation of the 1st IMF
372
335
  self.all_noise_EMD = self._decompose_noise()
373
336
 
374
- # Create the first IMF
375
337
  last_imf = self._run_eemd(signal, time, max_imfs=1, progress=progress)[0]
376
338
 
377
339
  all_cimfs = last_imf.reshape((-1, last_imf.size))
378
340
  prev_res = signal - last_imf
379
341
 
380
- # Create an iterator object for signal decomposition
342
+ total = (max_imfs - 1) if max_imfs != -1 else None
381
343
  it = (
382
344
  iter
383
345
  if not progress
384
346
  else lambda x: tqdm(x, desc="cIMF decomposition", total=total)
385
347
  )
386
348
 
387
- # Begin the algorithm's iteration
388
- for _ in it(range(max_imfs - 1)):
389
- # Number of IMFs currently decomposed
390
- imf_number = all_cimfs.shape[0]
349
+ for _ in it(range(self._max_imf_iters)):
350
+ if self.end_condition(signal=signal, cIMFs=all_cimfs, max_imf=max_imfs):
351
+ break
391
352
 
353
+ imf_number = all_cimfs.shape[0]
392
354
  beta = self.epsilon * np.std(prev_res)
393
-
394
355
  local_mean = np.zeros(signal.size)
395
356
 
396
357
  for trial in range(self.trials):
397
- # Skip if noise[trial] didn't have k'th mode
398
358
  noise_imf = self.all_noise_EMD[trial]
399
359
  res = prev_res.copy()
400
360
 
401
- # add noise for every imf to be decomposed.
402
361
  if len(noise_imf) > imf_number:
403
362
  res += beta * noise_imf[imf_number]
404
363
 
405
- # Extract the local mean, which is at the 2nd position
406
364
  imfs = self._run_emd(res, time, max_imfs=1)
407
365
  local_mean += imfs[-1] / self.trials
408
366
 
409
- # Record the results of this decomposition
410
367
  last_imf = prev_res - local_mean
411
- # vstack the new imf
412
368
  all_cimfs = np.vstack((all_cimfs, last_imf))
413
369
  prev_res = local_mean.copy()
414
370
 
415
- # Determine whether the decomposition algorithm should stop iterating
416
- if self.end_condition(signal=signal, cIMFs=all_cimfs, max_imf=max_imfs):
417
- # Reached the stopping condition
418
- # print("End Decomposition")
419
- break
371
+ residue = signal - np.sum(all_cimfs, axis=0)
372
+ all_cimfs = np.vstack((all_cimfs, residue))
373
+
374
+ all_cimfs = all_cimfs * scale_s
420
375
 
421
- # Clear all IMF noise
422
- # The noise will be initialized for the new input signal
423
376
  del self.all_noise_EMD[:]
424
377
 
425
- # Record the results of this decomposition
426
378
  self.imfs = all_cimfs
427
-
428
- # Calculate the remaining residual components
429
379
  self.residue = signal * scale_s - np.sum(self.imfs, axis=0)
430
380
 
431
381
  return all_cimfs
@@ -519,8 +519,7 @@ class EMD(object):
519
519
  IMF = np.vstack((IMF, imf.copy()))
520
520
  imfNo += 1
521
521
 
522
- if self.end_condition(signal, IMF) or imfNo == max_imfs - 1:
523
- # Determine whether to stop iterative decomposition based on the original input signal and the intrinsic mode function
522
+ if self.end_condition(signal, IMF) or (max_imfs > 0 and imfNo >= max_imfs):
524
523
  break
525
524
 
526
525
  # If the last sifting had 2 or less extrema then that's a trend (residue)
@@ -251,9 +251,9 @@ def EWT_Boundaries_Detect(
251
251
 
252
252
  elif reg == "gaussian":
253
253
  regFilter = np.zeros(lengthFilter)
254
- regFilter[
255
- regFilter.size // 2
256
- ] = 1 # Ensure center is set for Gaussian filtering
254
+ regFilter[regFilter.size // 2] = (
255
+ 1 # Ensure center is set for Gaussian filtering
256
+ )
257
257
  presig = np.convolve(
258
258
  ff, gaussian_filter(regFilter, sigmaFilter), mode="same"
259
259
  ) # Gaussian filter
@@ -4,3 +4,4 @@ Created on 2025/01/12 00:05:25
4
4
  @author: Whenxuan Wang
5
5
  @email: wwhenxuan@gmail.com
6
6
  """
7
+ from .fmd import FMD
@@ -0,0 +1,302 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ Created on 2025/01/12 00:05:39
4
+ @author: Whenxuan Wang
5
+ @email: wwhenxuan@gmail.com
6
+ """
7
+ import numpy as np
8
+
9
+ import scipy.signal as signal
10
+ from scipy.fftpack import fft
11
+ from scipy.signal import firwin, hilbert
12
+
13
+ from typing import Optional, Union, Tuple
14
+
15
+
16
+ class FMD(object):
17
+ """
18
+ Feature Mode Decomposition (FMD) class for decomposing a signal into a predefined
19
+ number of modes using iterative filter bank and kurtosis-based optimization.
20
+
21
+ Yonghao, Miao, et al. “Feature Mode Decomposition: New Decomposition Theory for Rotating Machinery Fault Diagnosis.”
22
+ IEEE Transactions on Industrial Electronics, Institute of Electrical and Electronics Engineers (IEEE), 2022, pp. 1–1, doi:10.1109/tie.2022.3156156.
23
+ Matlab Code: https://www.mathworks.com/matlabcentral/fileexchange/108099-feature-mode-decomposition-fmd
24
+ """
25
+
26
+ def __init__(
27
+ self,
28
+ fs: Union[int, float],
29
+ mode_num: int = 2,
30
+ filter_size: Union[int, float] = 30,
31
+ cut_num: Optional[int] = 7,
32
+ max_iter_num: Optional[int] = 20,
33
+ ) -> None:
34
+ """
35
+ Initialize the FMD instance with specified parameters.
36
+
37
+ :param alpha: (Union[int, float]),
38
+ Sampling frequency of the input signal.
39
+ :param mode_num: (int, optional),
40
+ Number of modes to extract from the input signal. Defaults to 2.
41
+ :param filter_size: (Union[int, float], optional),
42
+ Length of the FIR filters used in the filter bank. Defaults to 30.
43
+ :param cut_num: (Optional[int], optional),
44
+ Number of equally spaced sub-bands to split the frequency range [0, fs/2]. Defaults to 7.
45
+ :param max_iter_num: (Optional[int], optional),
46
+ Maximum number of iterations for filter optimization. Defaults to 20.
47
+ """
48
+ self.fs = fs
49
+ self.filter_size = filter_size
50
+ self.cut_num = cut_num
51
+ self.mode_num = mode_num
52
+ self.max_iter_num = max_iter_num
53
+
54
+ def fit_transform(
55
+ self, x: np.ndarray, fs: Optional[Union[int, float]] = None
56
+ ) -> np.ndarray:
57
+ """
58
+ Decompose the input signal into modes using FMD.
59
+
60
+ :param x: (array-like),
61
+ Input signal to be decomposed.
62
+ :param fs: (Optional[Union[int, float]], optional),
63
+ Sampling frequency of the input signal. If None, uses the instance's fs. Defaults to None.
64
+
65
+ :return: (ndarray, shape (mode_num, len(x))),
66
+ Extracted modes after decomposition.
67
+ """
68
+ # Initialize the input signal and parameters
69
+ x = np.asarray(x).flatten()
70
+
71
+ if fs is not None:
72
+ self.fs = fs
73
+
74
+ freq_bound = np.arange(0, 1, 1 / self.cut_num)
75
+ temp_filters = np.zeros((self.filter_size, self.cut_num))
76
+
77
+ # Initialize a filter bank with Hann-windowed FIR bandpass filters
78
+ for n, fb in enumerate(freq_bound):
79
+ b = firwin(
80
+ self.filter_size,
81
+ [fb + np.finfo(float).eps, fb + 1 / self.cut_num - np.finfo(float).eps],
82
+ window="hann",
83
+ pass_zero=False,
84
+ )
85
+ temp_filters[:, n] = b
86
+
87
+ temp_sig = np.tile(x[:, None], (1, self.cut_num))
88
+ itercount = 2
89
+
90
+ while True:
91
+ iternum = 2
92
+ if itercount == 2:
93
+ # Reduce iterations for non-target bands to speed convergence
94
+ iternum = self.max_iter_num - (self.cut_num - self.mode_num) * iternum
95
+
96
+ X = []
97
+ for n in range(temp_filters.shape[1]):
98
+ # Apply Multi-Cycle Kurtosis Deconvolution (MCKD) to refine each filter output
99
+ y_iter, f_iter, k_iter, T_iter = self._xxc_mckd(
100
+ temp_sig[:, n], temp_filters[:, n], iternum, M=1
101
+ )
102
+ freq_resp = np.abs(fft(f_iter))[: self.filter_size // 2]
103
+ peak_freq = (np.argmax(freq_resp)) * (self.fs / self.filter_size)
104
+ X.append(
105
+ [
106
+ y_iter[:, -1],
107
+ f_iter[:, -1],
108
+ k_iter[-1],
109
+ freq_resp,
110
+ peak_freq,
111
+ T_iter,
112
+ ]
113
+ )
114
+
115
+ # Update signal matrix and filter bank using optimized filters
116
+ temp_sig = np.column_stack([xi[0] for xi in X])
117
+ temp_filters = np.column_stack([xi[1] for xi in X])
118
+
119
+ # Compute pairwise correlation and remove the most redundant component
120
+ corr_matrix = np.abs(np.corrcoef(temp_sig, rowvar=False))
121
+ corr_matrix = np.triu(corr_matrix, 1)
122
+ I, J, _ = _max_IJ(corr_matrix)
123
+
124
+ XI = temp_sig[:, I] - np.mean(temp_sig[:, I])
125
+ XJ = temp_sig[:, J] - np.mean(temp_sig[:, J])
126
+
127
+ # Compare cyclic kurtosis (CK) values to decide which component to drop
128
+ KI = _CK(XI, X[I][5], 1)
129
+ KJ = _CK(XJ, X[J][5], 1)
130
+
131
+ output = J if KI > KJ else I
132
+ temp_sig = np.delete(temp_sig, output, axis=1)
133
+ temp_filters = np.delete(temp_filters, output, axis=1)
134
+
135
+ if temp_filters.shape[1] == self.mode_num - 1:
136
+ break
137
+
138
+ itercount += 1
139
+
140
+ final_modes = np.column_stack([xi[0] for xi in X])
141
+ return final_modes.T
142
+
143
+ def _xxc_mckd(
144
+ self,
145
+ x: np.ndarray,
146
+ f_init: np.ndarray,
147
+ term_iter: int,
148
+ T: Optional[int] = None,
149
+ M: Optional[int] = 3,
150
+ ) -> Tuple[np.ndarray, np.ndarray, np.ndarray, int]:
151
+ """
152
+ Multi-Cycle Kurtosis Deconvolution (MCKD) optimization for filter refinement.
153
+
154
+ :param x: (ndarray),
155
+ Input signal segment.
156
+ :param f_init: (ndarray),
157
+ Initial filter coefficients.
158
+ :param term_iter: (int),
159
+ Number of iterations to optimize filter.
160
+ :param T: (int, optional),
161
+ Cycle period (if None, estimated from signal envelope autocorrelation).
162
+ :param M: (int, optional, default=3),
163
+ Order of cyclic kurtosis.
164
+
165
+ :return: (Tuple[np.ndarray, np.ndarray, np.ndarray, int]),
166
+ y_final: Filtered signal at each iteration.
167
+ f_final: Optimized filter coefficients at each iteration.
168
+ ck_iter: CK values over iterations.
169
+ T: Estimated cycle period.
170
+ """
171
+ if T is None:
172
+ env = np.abs(hilbert(x)) - np.mean(np.abs(hilbert(x)))
173
+ T = _TT(env)
174
+
175
+ T = int(round(T))
176
+ x = np.asarray(x).flatten()
177
+ L = len(f_init)
178
+ N = len(x)
179
+
180
+ # Build delayed signal matrix XmT for MCKD optimization
181
+ XmT = np.zeros((L, N, M + 1))
182
+ for m in range(M + 1):
183
+ for l in range(L):
184
+ if l == 0:
185
+ XmT[l, m * T :, m] = x[: N - m * T]
186
+ else:
187
+ XmT[l, 1:, m] = XmT[l - 1, :-1, m]
188
+
189
+ Xinv = np.linalg.inv(XmT[:, :, 0] @ XmT[:, :, 0].T)
190
+
191
+ f = f_init.copy()
192
+ y_final = []
193
+ f_final = []
194
+ ck_iter = []
195
+
196
+ for _ in range(term_iter):
197
+ y = XmT[:, :, 0].T @ f # filter output
198
+
199
+ # Construct shifted versions for cyclic statistics
200
+ yt = np.zeros((N, M + 1))
201
+ for m in range(M + 1):
202
+ yt[:, m] = (
203
+ y if m == 0 else np.concatenate([np.zeros(T), yt[:-T, m - 1]])
204
+ )
205
+
206
+ # Compute alpha (partial derivatives) and beta for filter update
207
+ alpha = np.zeros_like(yt)
208
+ for m in range(M + 1):
209
+ idx = [i for i in range(M + 1) if i != m]
210
+ alpha[:, m] = (np.prod(yt[:, idx], axis=1) ** 2) * yt[:, m]
211
+
212
+ beta = np.prod(yt, axis=1)
213
+
214
+ Xalpha = np.zeros(L)
215
+ for m in range(M + 1):
216
+ Xalpha += XmT[:, :, m] @ alpha[:, m]
217
+
218
+ # Update filter using gradient ascent on CK criterion
219
+ f = (np.sum(y**2) / (2 * np.sum(beta**2))) * (Xinv @ Xalpha)
220
+ f /= np.sqrt(np.sum(f**2))
221
+
222
+ # Compute CK value for convergence monitoring
223
+ ck_val = np.sum(np.prod(yt, axis=1) ** 2) / (np.sum(y**2) ** (M + 1))
224
+ ck_iter.append(ck_val)
225
+
226
+ # Update delay matrix with new cycle period estimate
227
+ env_y = np.abs(hilbert(y)) - np.mean(np.abs(hilbert(y)))
228
+ T = int(round(_TT(env_y)))
229
+
230
+ XmT = np.zeros((L, N, M + 1))
231
+ for m in range(M + 1):
232
+ for l in range(L):
233
+ if l == 0:
234
+ XmT[l, m * T :, m] = x[: N - m * T]
235
+ else:
236
+ XmT[l, 1:, m] = XmT[l - 1, :-1, m]
237
+
238
+ Xinv = np.linalg.inv(XmT[:, :, 0] @ XmT[:, :, 0].T)
239
+
240
+ y_final.append(signal.lfilter(f, [1], x))
241
+ f_final.append(f.copy())
242
+
243
+ y_final = np.column_stack(y_final) if len(y_final) > 1 else y_final[0][:, None]
244
+ f_final = np.column_stack(f_final) if len(f_final) > 1 else f_final[0][:, None]
245
+
246
+ return y_final, f_final, np.array(ck_iter), T
247
+
248
+
249
+ def _TT(y: np.ndarray) -> int:
250
+ """Estimate signal period T using autocorrelation zero-crossing and peak."""
251
+ NA = signal.correlate(y, y, mode="full") / np.dot(y, y)
252
+ NA = NA[len(NA) // 2 :]
253
+ zeroposi = 1
254
+ for lag in range(1, len(NA)):
255
+ if (NA[lag - 1] > 0 and NA[lag] < 0) or NA[lag] == 0:
256
+ zeroposi = lag
257
+ break
258
+
259
+ if zeroposi >= len(NA): # fallback if no zero-crossing is found
260
+ zeroposi = 1
261
+
262
+ NA = NA[zeroposi:]
263
+ max_position = np.argmax(NA)
264
+ return zeroposi + max_position
265
+
266
+
267
+ def _CK(x: np.ndarray, T: int, M: Optional[int] = 2) -> Union[int, float, np.ndarray]:
268
+ """Compute cyclic kurtosis (CK) of the signal for a given period T and order M."""
269
+ x = np.asarray(x).flatten()
270
+ N = len(x)
271
+ x_shift = np.zeros((M + 1, N))
272
+ x_shift[0] = x
273
+ for m in range(1, M + 1):
274
+ x_shift[m, T:] = x_shift[m - 1, :-T]
275
+ return np.sum(np.prod(x_shift, axis=0) ** 2) / (np.sum(x**2) ** (M + 1))
276
+
277
+
278
+ def _max_IJ(
279
+ X: np.ndarray,
280
+ ) -> Tuple[
281
+ Union[int, float, np.ndarray],
282
+ Union[int, float, np.ndarray],
283
+ Union[int, float, np.ndarray],
284
+ ]:
285
+ """Find the indices (I, J) of the maximum value in the upper triangle of correlation matrix X."""
286
+ tempI = np.argmax(X, axis=0)
287
+ temp = np.max(X, axis=0)
288
+ J = np.argmax(temp)
289
+ I = tempI[J]
290
+ return I, J, X[I, J]
291
+
292
+
293
+ if __name__ == "__main__":
294
+ from pysdkit.data import test_emd
295
+ from pysdkit.plot import plot_IMFs
296
+ from matplotlib import pyplot as plt
297
+
298
+ time, sig = test_emd()
299
+ fmd = FMD(fs=1000, mode_num=2)
300
+ modes = fmd.fit_transform(sig)
301
+ plot_IMFs(sig, modes)
302
+ plt.show()
@@ -77,9 +77,7 @@ class JMD(object):
77
77
  """Get the full name and abbreviation of the algorithm"""
78
78
  return "Jump Plus AM-FM Mode Decomposition (JMD)"
79
79
 
80
- def jump_step(
81
- self, freqs: np.ndarray, T: int
82
- ) -> Tuple[
80
+ def jump_step(self, freqs: np.ndarray, T: int) -> Tuple[
83
81
  float,
84
82
  np.ndarray,
85
83
  np.ndarray,