A unified library of modal decomposition methods — 15 classical and modern signal-decomposition algorithms behind one consistent API.
There are many methods of modal decomposition, but there is no library that covers them all with a single, coherent interface. Modal-Decomposition integrates the most commonly used decomposition algorithms into one package: every method shares the same class contract, the same return type, and the same validation / seeding / memory conventions, so switching between algorithms (or benchmarking them against each other) is a one-line change.
| Method | Description | Use | Reference (DOI) |
|---|---|---|---|
| CEEMDAN | Complete Ensemble Empirical Mode Decomposition with Adaptive Noise | Function.CEEMDAN(S) |
10.1109/ICASSP.2011.5947265 |
| CEEFD | Cyclic Envelop Empirical Fourier Decomposition | Function.CEEFD(S) |
10.3969/j.issn.1001-4551.2023.07.001 |
| CEEMD | Complementary Ensemble Empirical Mode Decomposition | Function.CEEMD(S) |
10.1016/j.jhydrol.2020.124647 |
| EEMD | Ensemble Empirical Mode Decomposition | Function.EEMD(S) |
10.1142/S1793536909000047 |
| EFD | Empirical Fourier Decomposition | Function.EFD(S) |
10.1016/j.ymssp.2021.108155 |
| EMD | Empirical Mode Decomposition | Function.EMD(S) |
10.1098/rspa.1998.0193 |
| EWT | Empirical Wavelet Transform | Function.EWT(S) |
10.48550/arXiv.2304.06274 |
| EWTpy | Empirical Wavelet Transform (optional ewtpy backend) |
Function.EWTpy(S) |
10.48550/arXiv.2304.06274 |
| FMD | Filtered Mode Decomposition | Function.FMD(S) |
10.1109/TIE.2022.3156156 |
| ICEEMDAN | Improved Complete Ensemble Empirical Mode Decomposition with Adaptive Noise | Function.ICEEMDAN(S) |
10.1007/s10470-021-01901-3 |
| LMD | Local Mean Decomposition | Function.LMD(S) |
10.1098/rsif.2005.0058 |
| MEMD | Multivariate Empirical Mode Decomposition | Function.MEMD(S) |
10.48550/arXiv.2206.00926 |
| RPSEMD | Random Phase Sinusoidal Assisted Empirical Mode Decomposition | Function.RPSEMD(S) |
10.1109/LSP.2016.2537376 |
| SSA | Singular Spectrum Analysis | Function.SSA(S) |
10.1016/j.mex.2020.101015 |
| SVMD | Successive Variational Mode Decomposition | Function.SVMD(S) |
10.1016/j.sigpro.2020.107610 |
| VMD | Variational Mode Decomposition | Function.VMD(S) |
10.1109/TSP.2013.2288675 |
pip install Modal-DecompositionOr install from source:
git clone https://github.com/a-raining-day/Modal-Decomposition.git
cd Modal-Decomposition
pip install -r requirements.txt # pinned build/dev environment
pip install -e . # editable install (compiles the FHT kernel)The published platform wheels (Windows / macOS / Linux) ship the optional
Cython-compiled FHT acceleration kernel _fht_native (C source from the
Smithsonian am project, see Acknowledgement); it is used
automatically when present and the library falls back to the pure NumPy
implementation otherwise, so it runs in any environment. To compile the
kernel manually inside a source tree:
python setup.py build_ext --inplace # repository rootRequires Python >= 3.10.
| Package | Used by / for |
|---|---|
| numpy | core arrays and vectorized kernels |
| scipy | splines, envelope / Hilbert, filtering, peaks, FFT |
| EMD-signal (PyEMD) | EEMD and CEEMDAN only — the last remaining third-party decomposition dependency |
| psutil | available-memory reading for the memmap policy |
Please install
EMD-signal, notPyEMD(the latter is an unrelated older package).
PyEMD is the only third-party decomposition backend left. Every other
method — EMD, CEEMD, ICEEMDAN, RPSEMD, VMD, EWT, LMD, FMD,
EFD, CEEFD, SSA, SVMD, MEMD — is implemented inside this library and
needs nothing beyond numpy/scipy. Only EEMD and CEEMDAN still delegate to
PyEMD, and that dependency is being removed: the 0.3.x line exists
precisely to finish porting those two remaining branches, after which the
library stands on its own (see Roadmap).
vmdpy and ewtpy are not required: the native VMD / EWT replace them
entirely. vmdpy survives only as an optional parity-check backend
(Class.VMD(vmdpy=True)) and EWTpy / ewtpy only as an optional
reference-implementation adapter. Both are imported lazily, so neither is a
runtime dependency.
EWT (Empirical Wavelet Transform) is implemented inside this library and
needs no third-party package. The ewtpy-backed variant EWTpy is optional:
ewtpy is imported lazily (via cache.import_module) only when you actually
call Function.EWTpy / Class.EWTpy(...).decompose(...), so a missing ewtpy
never affects the rest of the library — install it with
pip install Modal-Decomposition[ewtpy] (or plain pip install ewtpy) if you
want that entry.
Optional extras (not required at runtime): [dev] (pytest, black) and
[plot] (matplotlib). numba may be installed to activate the optional
"numba" backend of Utils.Peaks / SVMD; everything degrades gracefully
without it.
Accessing any method other than EEMD / CEEMDAN therefore never triggers a
PyEMD import at all — EMD in particular is the native sifting engine.
import numpy as np
from Modal_Decomposition import Function as f
fs = 1000
t = np.linspace(0, 1, 1000, endpoint=False)
S = np.sin(2 * np.pi * 37 * t) + 0.5 * np.sin(2 * np.pi * 180 * t)
IMFs, Res, Info = f.EMD(S) # returns a DecompositionResult tupleAll entrances live in Modal_Decomposition/__init__.py, exposed as two
read-only namespaces plus a small set of global helpers:
-
Class— decomposer classes. Construct with parameters, then call.decompose(S, T=None)(also available as__call__):from Modal_Decomposition import Class r = Class.VMD(alpha=3000, K=4).decompose(S)
-
Function— function facades, strictly equivalent to the class form (Function.X(S, T=None, **params)=Class.X(**params).decompose(S, T)):r = f.LMD(S) # r is a DecompositionResult, not a tuple
-
Global helpers —
set_seed(seed)/get_seed(),set_absolute_limit(bytes)/set_memmap_ratio(ratio)(see Memory policy).
Every method returns a DecompositionResult (also unpackable as a
3-tuple IMFs, Res, info):
| Field | Meaning |
|---|---|
.IMFs |
decomposed modes, shape (K, N) (univariate) or (K, d, N) (MEMD) |
.Res |
residual, or None for methods without a residual concept (SSA, VMD) |
.info |
method-specific diagnostics dict (e.g. FFT spectra, boundaries) |
.config |
frozen dataclass snapshot of the effective parameters of this run |
Plus helpers: .n_imfs, .shape and .reconstruct() (sum(IMFs) + Res).
r = f.CEEMDAN(S, trials=30, seed=0)
print(r.n_imfs, r.reconstruct().shape)
print(r.config) # effective parameter snapshotMethods that use randomness accept a local seed parameter. A process-level
global seed overrides every local seed (with a UserWarning) — call
Modal_Decomposition.set_seed(n) once at the start of a script to make all
downstream runs reproducible.
T is optional; when omitted an index axis is used. Pass a non-uniformly
sampled axis when the method needs physical frequencies (e.g. fs-related
parameters); duplicate or descending axes are validated (descending axes are
reordered with a warning).
Large-input support is built into the input layer
(Utils.Check_Time_and_Signal): inputs above the policy threshold are served
from disk-backed np.memmap working copies instead of being materialized in
RAM, so a machine can process signals far larger than its physical memory.
set_memmap_ratio(ratio)— memmap when the projected usage reachesratio(default 0.6) of the remaining available memory;set_absolute_limit(n_bytes)— memmap above a fixed byte limit (default 2 GiB).
The two strategies are mutually exclusive; the latest call wins. Monotonicity
checks and chunked utilities (Utils.Chunk) follow the same policy.
src/Modal_Decomposition/
├── __init__.py Class / Function namespaces + global API
├── <METHOD>.py 1 Config dataclass + 1 Decomposer per method
├── EMD.py native EMD engine (PyEMD-free; original EMD_new,
│ registered as the public "EMD"). Only EEMD / CEEMDAN
│ still reach PyEMD; the 0.3.x line removes that too
├── _Registry.py class registry (registration at import time)
├── Base/ Decomposer ABC, DecompositionResult, Config,
│ import Cache, metadata tables, size constants
└── Utils/ Check / Chunk / Peaks / Mirror / Spline / Envelope /
Hilbert / Monotonicity / Memory / Seed / FFT / Slepian
(+ _Hilbert FHT and _Slepian C backends)
tests/ pytest suite + benchmarking harnesses (comparison/,
ssa/, test_memory/) against PyEMD and PySDKit
0.3.0 is the current release. PyEMD is the only third-party
decomposition backend still in use, and it is on the way out; the version
plan splits the remaining work into two lines:
| Line | Scope |
|---|---|
0.3.x |
Finish removing PyEMD. Port the two remaining PyEMD-backed branches (EEMD, CEEMDAN) onto the native engine, so the whole package runs on numpy/scipy alone. Nothing else is planned for this line — bug fixups are deliberately deferred. |
0.4.0 |
First fully self-developed release. With every method native, this line takes on the remaining bug fixups and correctness work across the library. |
- Version line opened. PyEMD is now the only third-party decomposition
dependency:
EEMDandCEEMDANare the sole methods still backed by it, and removing that dependency is the entire scope of the0.3.xline. Everything else —EMD,CEEMD,ICEEMDAN,RPSEMD,VMD,EWT,LMD,FMD,EFD,CEEFD,SSA,SVMD,MEMD— is already native. vmdpyandewtpyare demoted to optional parity-check / reference backends (Class.VMD(vmdpy=True),Class.EWTpy), no longer runtime dependencies ofVMD/EWT.EWTis fully re-implemented in-library (boundary detection, filter bank, transform and inverse) and no longer requiresewtpy; theOperatorpackage supplies the Daubechies transition functions.- New
Utils.Slepian(DPSS / Slepian sequences) with three backends —scipy(default),C(self-contained compiled core, loaded viactypes) andnumpy— plus a bounded process-wide LRU cache; used by the EWTpre_deal="Slepian-Optimize"multitaper branch. Utils.FFTgained a backend dispatcher (numpy/scipy/pyfftw/tiled/cupy, withautosplitting onBIG_ARRAY); all backends remain optional and fall back to numpy.- Remaining work for this line: port
EEMD/CEEMDANoff PyEMD (tracked as the0.3.xscope above). Bug fixups are out of scope until0.4.0.
EMDis now the native self-implemented sifting engine (ex-EMD_new, registered as the publicEMD; the former PyEMD wrapper was removed) — faster than PyEMD at equal mode quality, cold start ~ms vs ~0.5 s (docs/EMD_vs_EMD_new_Performance_Report.md).EMD(faster=...)two-branch stopping policy:faster=False(default, quality branch) additionally requires the classic narrowband balance|zc - ext| <= 1before accepting each IMF (clean signals cost nothing; noisy / long signals trade speed for row-level purity comparable with PyEMD);faster=Truekeeps the legacy fast branch (energy Cauchy SD stops sifting). Full four-way comparison (MD-quality / MD-fast / PyEMD / PySDKit) indocs/EMD_faster_Branch_Comparison_Report.md. Speed ratios are always quoted same-process/same-script (median timing in the bench scripts), never cross-day absolute timings.EMDdefaults (CubicSpline envelope +sd_thr=0.01+faster=False) chosen by the mode-level validation (docs/EMD_Validation_and_Comparison_Report.md) and the mechanism/optimization analysis (docs/EMD_Quality_Gap_and_Optimization.md);spline_kindaccepts the canonicalUtils.Splinekinds.- Temporary memmap backing files are now cleaned up: the input-layer memmap
registry deletes its files at interpreter exit (
atexit, handle closed first — required on Windows), andUtils.Chunk.exo_chunksdeletes its files when each iteration ends; seedocs/EMD_Large_Signal_Memory_Report.md§5. - Timing / quality / memory benchmarks re-run on the native engine and
reported under
docs/(four new reports +docs/EXPERIMENTS_INDEX.md); stale test files removed and legacy benchmark data archived undertests/comparison/results/_legacy_pyemd_wrapper/. - LMD defaults to the extrema (Spline) interpolation envelope; the analytic
Hilbert envelope stays as an explicit opt-in only. The Hilbert-vs-Spline
envelope concepts are separated again (
Utils.Enveloperestored as its own module). - New
Utils.Mirror(endpoint mirror extension, EMD nbsym + LMD boundary modes) shared by EMD / LMD / future consumers.
- Unified utility layer (
Utils.Chunk/Peaks/Check/Cache/Memory/Spline/Envelope) with documented conventions and speed reports underdocs/. - Memory-bounded processing for very large signals: policy-driven
memmapinput layer, adaptive chunking, dtype-preserving pipelines. - Optional C-accelerated FHT/Hilbert kernel (
_fht_native, compiled wheels for Windows / macOS / Linux, cp310–cp313) with automatic pure-NumPy fallback; CI verifies native-vs-pure numerical identity. EMD_new: experimental self-implemented EMD engine (prototype of the PyEMD-free roadmap).- Dependency cleanup: removed unused packages (
calorine,tqdmand stale pinned entries); unified resultconfigsnapshots and seed handling.
The FHT Hilbert backend under
src/Modal_Decomposition/Utils/_Hilbert/_C/_fht/ contains third-party C
code:
- Originally written by the Smithsonian Astrophysical Observatory, Submillimeter Receiver Laboratory (Scott Paine), as part of the am atmospheric model: https://www.cfa.harvard.edu/~spaine/am/
- Acquired from waddafunk/Smithsonians_Discrete_Hilbert_Fourier_Hartley_Transforms (Jacopo Piccirillo, 13/10/2020), where the transform routines were isolated for standalone C/C++ use.
Smithsonian am license notice:
This computer program containing an atmospheric propagation model for the submillimeter band is a work of the United States and may be used freely, with attribution and credit to the Smithsonian Astrophysical Observatory. The program is intended for educational, scholarly or research purposes. In connection with any commercial use of the program, the user should disclose clearly and conspicuously all of the information contained in the first sentence of this notice.
Hilbert-transform phase convention: the SAO implementation shifts the
phase by +90° — not −90° as in MATLAB/SciPy. To obtain the
MATLAB-compatible result, multiply the component orthogonal to the input by
exp(j·π) = −1, i.e. multiply the imaginary part of the analytic signal by
−1 for a real input (matlab_phase=True in
Modal_Decomposition.Utils._Hilbert._fht).
Apache-2.0 — see LICENSE.
Project homepage / repository: https://github.com/a-raining-day/Modal-Decomposition