Generated reference › Time Frequency Ridges — Control Systems/Time Frequency
kind: generated#block#control-systems-time-frequency

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 tfridge reads 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 NumFrequencyBins 1 (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#

FactValue
registered typeControl_Systems/Time_Frequency/Time_Frequency_Ridges
familyControl_Systems/Time_Frequency
solver environment classICoreBlock_0_Control_Systems_1_Time_Frequency_2_Time_Frequency_Ridges
sourcesrc/ICoreBlocks/ICoreBlockLibrary/Blocks/Control_Systems/Time_Frequency/Time_Frequency_Ridges/ICoreBlock_0_Control_Systems_1_Time_Frequency_2_Time_Frequency_Ridges.cpp
headersrc/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 canvas160 × 100 px
ports at insert1 in, 3 out
code generators implementedPython, MATLAB, Java, Rust, C, C++, VHDL, Verilog, SystemVerilog, PLC Structured Text

Ports#

#DirectionSignal typeDescription label
1inICoreDoubleP
2outICoreDoublef
3outICoreDoubleir
4outICoreDoublefr

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 variableDefaultSimulink parameter
Window Columns8—
Penalty0—
Number of Ridges1—
Number of Frequency Bins4—
Bin Spacing1—

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.

supportSupport::None
Simulink path—
port-count rulePortsParam::None
SampleTime parameteryes

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#

Time Frequency Ridges — Sine Wave, [3,1]: amplitudes 1/2/3 at 2 rad/s (tried only because every scalar stimulus was refused)Time Frequency Ridges — Sine Wave, [3,1]: amplitudes 1/2/3 at 2 rad/s (tried only because every scalar stimulus was refused)-10123012345t (s)in ICoreDouble-Out-0 [3x1] entry 0out ICoreDouble-Out-0out ICoreDouble-Out-1out ICoreDouble-Out-2 [8x1] entry 0

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).