Empirical Mode Decomposition — Control Systems/Time Frequency
Control_Systems/Time_Frequency/Empirical_Mode_Decomposition · 1 input / 5 output port(s) at insert · exports to Python, MATLAB, Java, Rust, C, C++, VHDL, Verilog, SystemVerilog, PLC Structured Text
Description#
The block's own DESCRIPTION_HTML, rendered verbatim — the same text the config dialog's info panel and the library navigator show. Fix a wrong sentence in the block's .cpp (R-D9), never here.
Empirical Mode Decomposition
Control Systems / Time Frequency
Sifts the last L samples into intrinsic mode functions and a residual
– MATLAB's emd – and reports each mode's instantaneous
frequency and energy, which is what hht builds its Hilbert spectrum
from. A sift is: find the local maxima and minima, run an envelope through each
set, subtract the mean of the two envelopes, and repeat until
‖rprev − r‖² ÷ ‖rprev‖² < Sift Relative Tolerance
or the sift count runs out. What is left is one mode; it is subtracted and the next is sifted out of the remainder. Unlike a filter bank, the modes come from the signal's own extrema, so a mode can change frequency along the frame.
Ports
- u – the sampled signal, scalar: one channel and its own frame.
- IMF – the modes, [L, Max Number of IMFs]: column j is mode j over the frame, oldest sample first. Columns past the number actually found are zero, because a wire has one size where MATLAB returns a narrower matrix.
- r – the residual, [L, 1]: the frame minus every mode found.
- n – [1, 1], how many modes were found (how many columns of IMF carry one).
- insf – instantaneous frequency, [L, Max Number of IMFs], in hertz: fs/(2π) times the centred difference of each mode's unwrapped analytic phase. Zero in the columns with no mode.
- inse – instantaneous energy, [L, Max Number of IMFs]: the squared magnitude of each mode's analytic signal. Zero in the columns with no mode.
Parameters
- Frame Length – L, the samples one decomposition is made from: a whole number from 8 to 256. The three HDL targets hold one register per sample.
- Hop Size – the samples from one decomposition to the next, 1 to L. Between them all five outputs hold.
- Max Number of IMFs – MATLAB's
MaxNumIMF, 1 to 16: the ceiling on the decomposition loop and the width of the three matrix outputs. The default 10 is MATLAB's. - Sift Max Iterations – MATLAB's
SiftMaxIterations, 1 to 400: the ceiling on the sift loop. The default 100 is MATLAB's, and a frame of a few dozen samples typically stops after 1 to 5 sifts. - Sift Relative Tolerance – MATLAB's
SiftRelativeTolerance, greater than 0 and less than 1; default 0.2. The first test is made against a tolerance of exactly 1, so every mode is sifted at least once. - Max Number of Extrema – MATLAB's
MaxNumExtrema, 0 or more; default 1. The decomposition stops when the residue has fewer extrema than this. - Max Energy Ratio (dB) – MATLAB's
MaxEnergyRatio; default 20. The decomposition stops when 10·log10(‖x‖÷‖residue‖) passes it, i.e. when the residue is that many decibels below the frame. - Interpolation – how the envelope runs through the extrema:
- spline – the not-a-knot cubic, MATLAB's default and its
interp1(..., 'spline'). - pchip – MATLAB's shape-preserving cubic, which cannot overshoot between two extrema and usually yields one more mode.
- spline – the not-a-knot cubic, MATLAB's default and its
- Sample Rate (Hz) – fs, read only by insf.
MATLAB's
hht(imf)with no rate is this block at 2π, which puts the instantaneous frequency in radians per sample. - Sampling Time (s) – zero or less inherits the solver's rate; a positive value runs the block at that period.
Frames and timing
The frame is the last L samples, oldest first. The first decomposition is
made when the register fills – at sample L − 1, counting from 0
– and then every Hop Size samples; between them the outputs hold, and
before the first one they are all zero. The decomposition is of the WHOLE frame, so
the newest sample is at its edge, where the envelope is an extrapolation rather than
a fit; that is the same edge effect MATLAB's emd has, and it is the
reason a frame is decomposed rather than a single sample published.
Code export
All ten targets: Python, MATLAB, Java, Rust, C, C++, VHDL, Verilog, SystemVerilog and PLC Structured Text. The sift loop, the decomposition loop and the envelope solve are emitted as loops whose bounds are the parameters above, so the generated file does not grow with the frame length and the worst-case cost of a frame is fixed – a decomposition MATLAB describes as "until converged" becomes a bounded program.
The three HDL targets carry the whole decomposition in real
arithmetic and are therefore simulation-only: a tridiagonal solve, a
division by an envelope difference and an arctangent do not belong in a Q16.16
datapath. The frame itself is held in the port's fixed-point format, so
quantization happens only at the port boundary.
⚠ Sifting makes DISCRETE decisions – which samples are extrema, when the tolerance is met, how many modes are found – so a frame in which one of those decisions is within a quantum of going the other way decomposes differently in fixed point than in double precision, and the modes then differ by much more than the quantum. Measured on this block's own reference over 40 runs of 30 frames each with the input quantized to Q16.16: about 2.5% of runs contain such a frame. That is arithmetic, not a code-generation defect, and a re-run is the first thing to try when one of the three HDL columns is red.
Simulink bridge
None (Support::None). emd and hht
are functions of the Signal Processing and Wavelet toolboxes and neither ships a
Simulink library block for them, so there is no path a diagram could name. The
bridge reports this block rather than dropping it silently, and it therefore has
no parity testbench; code export verification still covers it across all ten
languages. Sampling Time (s) → SampleTime, as on every block, has
no counterpart here for the same reason.
Notes
- Stateful, and discrete by nature: a register of L samples, a frame counter and the held decomposition.
- Verified against R2026a: 44 frames over eight option sets, worst 1.6e-15 on the modes, 7.2e-16 on the residual, with the same mode count and sift count on every frame (the source banner carries what was checked).
- It is a frame decomposition, not a filter. Each frame is decomposed on its own, so two overlapping frames may split a signal differently; that is a property of the method, and it is why the hop size is worth choosing deliberately.
- No state space. Sifting is not linear in the frame, so model reduction correctly declines to merge it.
Code facts#
| Fact | Value |
|---|---|
| registered type | Control_Systems/Time_Frequency/Empirical_Mode_Decomposition |
| family | Control_Systems/Time_Frequency |
| solver environment class | ICoreBlock_0_Control_Systems_1_Time_Frequency_2_Empirical_Mode_Decomposition |
| source | src/ICoreBlocks/ICoreBlockLibrary/Blocks/Control_Systems/Time_Frequency/Empirical_Mode_Decomposition/ICoreBlock_0_Control_Systems_1_Time_Frequency_2_Empirical_Mode_Decomposition.cpp |
| header | src/ICoreBlocks/ICoreBlockLibrary/Blocks/Control_Systems/Time_Frequency/Empirical_Mode_Decomposition/ICoreBlock_0_Control_Systems_1_Time_Frequency_2_Empirical_Mode_Decomposition.h |
| default size on canvas | 170 × 120 px |
| ports at insert | 1 in, 5 out |
| code generators implemented | Python, MATLAB, Java, Rust, C, C++, VHDL, Verilog, SystemVerilog, PLC Structured Text |
Ports#
| # | Direction | Signal type | Description label |
|---|---|---|---|
| 1 | in | ICoreDouble | u |
| 2 | out | ICoreDouble | IMF |
| 3 | out | ICoreDouble | r |
| 4 | out | ICoreDouble | n |
| 5 | out | ICoreDouble | insf |
| 6 | out | ICoreDouble | inse |
Ports the constructor creates. A block whose port list changes with its configuration adds or removes ports at load time; the count above is the one a freshly inserted block has.
Configuration variables#
| Config variable | Default | Simulink parameter |
|---|---|---|
Frame Length | 64 | — |
Hop Size | 32 | — |
Max Number of IMFs | 10 | — |
Sift Max Iterations | 100 | — |
Sift Relative Tolerance | 0.2 | — |
Max Number of Extrema | 1 | — |
Max Energy Ratio (dB) | 20 | — |
Interpolation | spline%~%pchip~~spline | — |
Sample Rate (Hz) | 1 | — |
Every block also carries Sampling Time (s) from ICoreBlockSolverEnvironment: zero or less inherits the solver's rate, a positive value runs the block at that period.
Simulink bridge#
| support | Support::None |
| Simulink path | — |
| port-count rule | PortsParam::None |
SampleTime parameter | yes |
Caveat (shown to the user): emd and hht are Signal Processing / Wavelet toolbox functions, not Simulink library blocks -- neither toolbox ships a library block that decomposes a signal into intrinsic mode functions -- so there is no path a diagram could name; the block is reported rather than dropped when a model crosses
Catalog contract: src/ICoreBlocks/ICoreCoder/ICoreCommandSystem/SimulinkBridge/ICoreSimulinkBlockCatalog.h
Description vs code#
The lists agree. check_block_descriptions.py finds no disagreement between the description's Ports, Parameters, Code export and Simulink bridge lists and the code's.
The verdict above is
tools/docs/check_block_descriptions.py(P7.1), which compares LISTS. It cannot read a sentence: "stateless" on a block with a state, an initial-value semantic the recursion does not implement, a "not synthesizable" caveat the HDL banner contradicts. That is the agent audit (P7.3) on BLOCK_DESCRIPTION_AUDIT.md, and this tool's green is not a substitute for one.
File banner (developer view)#
The top comment of the block's .cpp — the maths, the realization and the export strategy, addressed to whoever changes it. It must not contradict the description above (P7.5).
Empirical Mode Decomposition -- MATLAB's emd over a frame, with hht's transform on top The last L samples are sifted: the local maxima and minima are found, a spline (or pchip) envelope is run through each set, their mean is subtracted, and that repeats until the relative tolerance ||prev - now||^2 / ||prev||^2 falls below SiftRelativeTolerance or the sift count reaches SiftMaxIterations. What is left is one intrinsic mode function; it is subtracted and the next mode is sifted out of the remainder, until MaxNumIMF modes are found, the residue has fewer than MaxNumExtrema extrema, or 10*log10(||x||/||residue||) passes MaxEnergyRatio. Every mode then gets hht's own transform: the analytic signal's squared magnitude as the instantaneous energy, and fs/(2*pi) times the centred difference of its unwrapped phase as the instantaneous frequency.
MEASURED AGAINST R2026a BEFORE A LINE WAS WRITTEN, and against the FULL output rather than a summary: over 44 frames (lengths 33 to 128, eight option sets covering pchip, capped sift counts, a tightened tolerance, an early energy-ratio stop and a raised extrema floor) this block's reference run reproduces emd's IMF matrix to a worst 1.6e-15 and its residual to 7.2e-16, WITH THE SAME NUMBER OF MODES AND THE SAME SIFT COUNT ON EVERY FRAME, and hht's instantaneous frequency to 4.6e-13 at fs = 50 (2e-14 relative) and energy to 2.5e-15 relative. The facts a wrong implementation would get wrong, each one measured:
- a maximum is a sample whose left difference is > 0 and right difference <= 0 (a minimum
the mirror), so a flat pair belongs to whichever side the strict test falls on;
- both envelopes are extended past the frame by emdWaveExtension -- a sine of amplitude
|peak - trough|/2 and period 2*|peak location - trough location|, contributing up to three knots per side, dropped where one would land exactly on the end sample;
- the envelope is interp1's 'spline', which is the NOT-A-KNOT cubic (its end conditions
come from MATLAB's spline(), not from a natural or clamped one), evaluated by ppval's Horner form;
- the first sift test compares a tolerance of exactly 1, so a mode is always sifted at
least once, whatever the tolerance;
- the residual is x minus the modes found, and the unfilled columns of the IMF matrix are
zeros rather than a shorter matrix, because a wire has one size.
Support::None: emd and hht are Signal Processing / Wavelet functions and neither toolbox ships a Simulink library block for them, so there is no counterpart to bridge to.
Sample results#
| t | in ICoreDouble-Out-0 | out ICoreDouble-Out-0 [64x10] entry 0 | out ICoreDouble-Out-1 [64x1] entry 0 | out ICoreDouble-Out-2 |
|---|---|---|---|---|
| 0 | -2 | [0, 0, 0, 0]… | [0, 0, 0, 0]… | 0 |
| 0.4 | 0.5 | [0, 0, 0, 0]… | [0, 0, 0, 0]… | 0 |
| 0.8 | -2 | [0, 0, 0, 0]… | [0, 0, 0, 0]… | 0 |
| 1.2 | 0.5 | [0, 0, 0, 0]… | [0, 0, 0, 0]… | 0 |
| 1.6 | -2 | [0, 0, 0, 0]… | [0, 0, 0, 0]… | 0 |
| 2 | 0.5 | [0, 0, 0, 0]… | [0, 0, 0, 0]… | 0 |
| 2.4 | -2 | [0, 0, 0, 0]… | [0, 0, 0, 0]… | 0 |
| 2.8 | 0.5 | [0, 0, 0, 0]… | [0, 0, 0, 0]… | 0 |
| 3.2 | -2 | [0, 0, 0, 0]… | [0, 0, 0, 0]… | 0 |
| 3.6 | 0.5 | [0, 0, 0, 0]… | [0, 0, 0, 0]… | 0 |
| 4 | -2 | [0, 0, 0, 0]… | [0, 0, 0, 0]… | 0 |
| 4.4 | 0.5 | [0, 0, 0, 0]… | [0, 0, 0, 0]… | 0 |
| 4.8 | -2 | [0, 0, 0, 0]… | [0, 0, 0, 0]… | 0 |
| 5.2 | 0.5 | [0, 0, 0, 0]… | [0, 0, 0, 0]… | 0 |
Every 4th of 60 samples, from the table stimulus.
The same rig also ran:
| Stimulus | What it is | Output range |
|---|---|---|
impulse | Impulse: one sample of 1 at k = 5, 0 elsewhere (Repeating Sequence Stair) | 0 … 0 |
ramp | Ramp: slope 1 from t = 0 | 0 … 0 |
sine | Sine Wave: amplitude 1, 2 rad/s, no phase, no bias | 0 … 0 |
step | Step: 0 -> 1 at t = 1 s | 0 … 0 |
Plotted: table — Repeating Sequence Stair: [-2 -1 -0.5 0 0.5 1 2 3], one entry per sample
Category static · sample time 0.1 · 60 steps · commit 23d8841c6561ca4bb64cd9b5b64da9638de12629 · produced by docsSample --out <folder> --blocks Fixed_Wing_Point_Mass Kernel_Classifier_Predictor Kernel_Regression_Predictor Rotor Rotor_With_Flap_Effects Multirotor Multirotor_With_Flap_Effects Dynamic_Inflow_3_State Kurtogram Empirical_Mode_Decomposition Modal_FRF Order_Spectrum --steps 60 · data docs/generated/samples/Control_Systems__Time_Frequency__Empirical_Mode_Decomposition.json · the SVG is generated from those numbers by tools/docs/plot_svg.py, so it is a run and not a drawing (R-D10).