Time Frequency Ridges — Control Systems/Time Frequency
Control_Systems/Time_Frequency/Time_Frequency_Ridges · 1 input / 3 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.
Time-Frequency Ridges
Control Systems / Time Frequency
The ridge of a time-frequency map – MATLAB's tfridge. A ridge
is not the largest value of each column: it is the path that collects the most
energy while paying for every change of frequency, which is why it is found by a
dynamic program over the last W columns rather than by a maximum:
E[i,j] = log(Σ|P|) − log(|P[i,j]| + 10−8·Σ|P|)
fVal[i,j] = mink(fVal[k,j−1] + λ·(i−k)²) + E[i,j]
The ridge ends at the smallest cost of the newest column and is traced back through the arg-minima; a second ridge is found by raising the first one's rows to the largest finite cost and running the program again.
Ports
- P – one column of the map, [M, 1] with M between 2 and
64: one value per frequency row, at this instant. Its magnitude is what is used, so a
signed map is read as
tfridgereads a complex one. Spectrogram and the Fourier Synchrosqueezed Transform both publish exactly this shape. - f – [R, 1], the frequency of each ridge at the newest column: (row − 1)·Bin Spacing, strongest ridge first.
- ir – [R, 1], the same answer as a 1-based row index,
which is MATLAB's
iridge. - fr – [W, R], the whole traced ridge over the window as
frequencies, oldest column first: column r of this output is
tfridge's own answer for the last W columns. The newest row is the same number as f; the earlier rows are the program's view of the past, and they change as new columns arrive – that is what a dynamic program does.
Parameters
- Window Columns – W, the columns the program runs over, a whole number from 2 to 32. A longer window buys a smoother ridge and costs W·M² comparisons per ridge per sample.
- Penalty – λ, the price of a jump, zero or more. At zero the answer is the largest value of each column; as it grows the ridge is pulled straight and follows a moving component with a lag. MATLAB's default is 0.
- Number of Ridges – R, how many ridges to extract, 1 to 4, strongest first. Each one is found by removing the previous one from the costs.
- Number of Frequency Bins – the rows either side of a found ridge that
are removed with it, 1 to 16 (MATLAB's
NumFrequencyBins, default 4). Read only when Number of Ridges is more than one – with a single ridge nothing is ever removed. - Bin Spacing – Δf, the frequency step between rows of the map, so row i (1-based) is at (i−1)·Δf. It is a label: it scales f and fr and changes no decision the program makes. For a one-sided spectrum of length L at sample rate fs it is fs/(2(L−1)); leave it at 1 to read the outputs as zero-based row numbers.
- Sampling Time (s) – zero or less inherits the solver's rate; a positive value runs the block at that period.
Frames and timing
The window is the last W columns. All three outputs are zero until it first fills – at column W − 1, counting from 0 – and a zero on ir is the one value that is not a row, so "no ridge yet" is not mistaken for row 1. From then on every sample carries the answer for the window ending at that sample: no latency, unlike the transforms upstream of it, because a ridge is read off the newest column rather than the middle of a window.
Code export
All ten targets: Python, MATLAB, Java, Rust, C, C++, VHDL, Verilog, SystemVerilog and PLC Structured Text. The program is written once as a statement list and rendered ten times, so the emitted file does not grow with the window or the row count; the penalty, the removal cost and the bin spacing are inlined as numbers. All five settings are structural – re-export after changing one.
The three HDL targets carry the program in real arithmetic and
are therefore simulation-only: the costs are logarithms and the comparisons are
between sums of them, neither of which belongs in a Q16.16 datapath. The map itself is
held in the port's fixed-point format, so quantization happens only at the port
boundary.
⚠ A RIDGE IS A DISCRETE ANSWER. Two candidate paths whose costs are within a rounding of each other can change places, and the output then moves by a whole row. On a map whose ridge is unambiguous that never happens – measured on this block's own reference over 1560 windows, a map quantized to Q16.16 gives the identical ridge, index for index – but on a map of noise, where no path is better than its neighbours, the answer is a coin toss that no code generator can make repeatable. Feed it a map with a ridge in it.
Simulink bridge
None (Support::None). tfridge is a Signal Processing
Toolbox function and that toolbox ships no Simulink library, 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 nothing to cross to for the same reason.
Notes
- Stateful, and discrete by nature: a register of the last W columns and the answer computed from it.
- Verified against R2026a: every ridge index identical and every frequency to
0.0 on a 6×7 map at penalty 0, 0.5 and 8, and with two ridges at
NumFrequencyBins1 (the source banner lists what was checked). - The normalizing sum is over the whole window, which is what makes the costs comparable between columns – and what makes the answer depend on the window length, not only on the columns near the newest one.
- An empty window has no ridge: if every value in it is zero the costs are all
equal and every output is row 1, which is the answer MATLAB's own program gives when its
costs come out as
NaN. - Not reproduced:
tfridge's third output (the linear indices into the matrix, which are an index arithmetic on ir), and a frequency vector that is not evenly spaced – Bin Spacing is one step, not a list. - No state space. A minimum over paths is not linear in the map, so model reduction correctly declines to merge it.
Code facts#
| Fact | Value |
|---|---|
| registered type | Control_Systems/Time_Frequency/Time_Frequency_Ridges |
| family | Control_Systems/Time_Frequency |
| solver environment class | ICoreBlock_0_Control_Systems_1_Time_Frequency_2_Time_Frequency_Ridges |
| source | src/ICoreBlocks/ICoreBlockLibrary/Blocks/Control_Systems/Time_Frequency/Time_Frequency_Ridges/ICoreBlock_0_Control_Systems_1_Time_Frequency_2_Time_Frequency_Ridges.cpp |
| header | src/ICoreBlocks/ICoreBlockLibrary/Blocks/Control_Systems/Time_Frequency/Time_Frequency_Ridges/ICoreBlock_0_Control_Systems_1_Time_Frequency_2_Time_Frequency_Ridges.h |
| default size on canvas | 160 × 100 px |
| ports at insert | 1 in, 3 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 | P |
| 2 | out | ICoreDouble | f |
| 3 | out | ICoreDouble | ir |
| 4 | out | ICoreDouble | fr |
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 |
|---|---|---|
Window Columns | 8 | — |
Penalty | 0 | — |
Number of Ridges | 1 | — |
Number of Frequency Bins | 4 | — |
Bin Spacing | 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): tfridge is a Signal Processing Toolbox function, not a Simulink library block -- that toolbox ships no Simulink library at all -- 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).
Time-Frequency Ridges -- the maximum-energy path through a time-frequency map, MATLAB's tfridge A ridge is not the largest value of each column: it is the PATH through the map that collects the most energy while paying for every change of frequency, and finding it is a dynamic program over the whole window,
E[i][j] = log(SUM|P|) - log(|P[i][j]| + 1e-8*SUM|P|) fVal[i][j] = min over k of (fVal[k][j-1] + penalty*(i-k)^2) + E[i][j]
ending at the smallest cost of the last column and traced back through the stored arg-minima. For several ridges the traced curve, plus a few rows either side, is raised to -log(realmin) and the program is run again.
TRANSCRIBED FROM R2026a's signalwavelet.internal.tfridge.extractRidges / .extractCurve (the compiled path tfridge itself takes is a MEX of that same file), and verified against tfridge on a 6-by-7 map at four settings -- penalty 0, 0.5 and 8 with one ridge, and penalty 0.5 with NumRidges 2 / NumFrequencyBins 1: every ridge index identical, every frequency to 0.0. The details that decide the answer, each one measured rather than assumed:
- the normalizing sum runs over the WHOLE window, not per column, so a quiet column is
compared on the same scale as a loud one;
- the penalty is accumulated (penalty -= pDelta; pDelta -= 2*lambda) rather than squared per
candidate, which is what makes the rounding MATLAB's;
- every comparison is a STRICT >, so a tie keeps the LOWEST row -- in the column loop, in the
last column's minimum, and therefore in the traceback;
- the removal between ridges raises both the row and its mirror to -log(realmin) = 708.3964,
the largest finite cost, rather than deleting them.
Support::None: tfridge is a Signal Processing Toolbox FUNCTION and that toolbox ships no Simulink library, so there is no counterpart to bridge to or run a parity testbench against.
Sample results#
Plotted: vector — Sine Wave, [3,1]: amplitudes 1/2/3 at 2 rad/s (tried only because every scalar stimulus was refused)
Category dynamic · sample time 0.1 · 60 steps · commit 875fdbf564cf31a145283edf6a75dc1d64d1090a · produced by docsSample --out <folder> --blocks Wigner_Ville_Distribution Cross_Wigner_Ville_Distribution Inverse_STFT Fourier_Synchrosqueezed_Transform Time_Frequency_Ridges Frequency_Domain_Filter_Identification Fill_Gaps EOM_6DOF_ECEF_Quaternion EOM_6DOF_Custom_Variable_Mass_ECEF_Quaternion EOM_6DOF_Simple_Variable_Mass_ECEF_Quaternion ECI_To_ECEF_Rotation_Matrix ECI_Position_To_LLA LLA_To_ECI_Position ECI_Position_To_AER --steps 60 · data docs/generated/samples/Control_Systems__Time_Frequency__Time_Frequency_Ridges.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).