Spectral Entropy — Control Systems/Spectral Measurements
Control_Systems/Spectral_Measurements/Spectral_Entropy · 1 input / 1 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.
Spectral Entropy
Control Systems / Spectral Measurements
Reads a one-sided power spectrum as a probability distribution over
frequency and reports its Shannon entropy. With
qk = pk / Σp, the answer is
H = −Σ qk·log₂qk, in bits,
divided by log₂M over M selected bins unless that is turned
off. This is MATLAB's pentropy(P, F, T) at a single instant,
transcribed from its source.
It measures how spread the power is, not how much of it there is: a flat spectrum answers exactly 1, a single occupied bin answers 0, and scaling the whole spectrum by any positive constant changes nothing. A falling reading is power gathering into fewer bins – a tone emerging from noise.
Ports
- p – the one-sided power spectral density, an [N,1] column or a [1,N] row of 2 to 256 bins. Bin k sits at frequency k·Δ, so the first bin is DC. Values are expected non-negative, as MATLAB requires; an empty bin contributes exactly nothing.
- H – the entropy, a scalar. Between 0 and 1 under the default normalization, and between 0 and log₂M bits without it.
Parameters
- Bin Spacing – Δ, the frequency step between neighbouring bins, a single positive number. The whole frequency axis follows from it, and it is used only to place the band below.
- Frequency Range – which bins take part.
- Full spectrum – every bin on the port.
- Frequency band – only the bins inside Frequency Band.
- Frequency Band – [f_lo f_hi], in the same units as the spacing, read only in the banded mode. The band is snapped inward: the first bin at or above f_lo to the last bin at or below f_hi. It must select at least two bins.
- Normalization – what the entropy is divided by.
- Normalized (0 to 1) – divided by log₂M, where M is the number of SELECTED bins. This is MATLAB's default.
- Unnormalized (bits) – the raw entropy in bits.
- Sampling Time (s) – zero or less inherits the solver's rate; a positive value runs the block at that period.
Code export
All ten targets: Python, MATLAB, Java, Rust, C, C++, VHDL, Verilog, SystemVerilog and PLC Structured Text. Which bins take part and what the answer is divided by are decided at export time, so every emitted core is two unrolled sums with no branch, no loop and no moving array index. Nothing is exposed as a tunable parameter on the generated core: the selection is structural, and changing it changes which bins are summed rather than a number.
There is no base-two logarithm in these dialects, so log₂x is emitted as log₁₀(x) × 3.3219280948873622 in all ten – one shared rounding rather than ten different ones, which is what keeps the columns agreeing with each other.
The three HDL targets are simulation-only: they carry the
arithmetic in real and quantize only at the port boundary. A
logarithm and the reciprocal of a run-time sum do not belong in a Q16.16
datapath, and VHDL has no real maximum before 2008, so the floor
under the logarithm is spelled arithmetically there – exact enough for a
simulation and not bit-exact, which is why those three columns are compared in a
band.
Simulink bridge
No equivalent (Support::None), so nothing crosses in
either direction, and no parity testbench is owed. Measured rather than
assumed: pentropy is a Signal Processing Toolbox function,
that toolbox ships no Simulink library at all, and a find_system
sweep over the DSP System Toolbox and Simulink library roots matched no entropy,
octave or coherence block anywhere. No configuration of it crosses either,
including "Sampling Time (s)", which has no counterpart to be written to. Code
export verification still covers the block across all ten languages.
Notes
- Algebraic, with no state: the answer depends on this sample's spectrum and nothing else.
- An empty bin contributes exactly zero, with no branch. MATLAB spells
that
'omitnan'– 0·log₂0 is a NaN and the sum skips it. Here the logarithm's argument is floored at realmin, so the term is 0·log₂(realmin), an exact zero. The two agree on every spectrum. - The divisor counts SELECTED bins, not port bins. Over a band it is
log₂ of the bins inside the band, because MATLAB takes
size(P,1)after cutting. That is what keeps a banded reading on 0–1 as well. - Fewer than two selected bins is refused. log₂1 is zero and MATLAB answers a NaN; this block reports the configuration instead.
- It is scale-invariant. Multiplying the whole spectrum by a positive constant leaves the answer unchanged, so the block says nothing about total power – Band Power is what reports that.
- The band is snapped INWARD, as Mean Frequency does and Band Power does not. Both conventions come from the same toolbox, on the same spectrum.
Code facts#
| Fact | Value |
|---|---|
| registered type | Control_Systems/Spectral_Measurements/Spectral_Entropy |
| family | Control_Systems/Spectral_Measurements |
| solver environment class | ICoreBlock_0_Control_Systems_1_Spectral_Measurements_2_Spectral_Entropy |
| source | src/ICoreBlocks/ICoreBlockLibrary/Blocks/Control_Systems/Spectral_Measurements/Spectral_Entropy/ICoreBlock_0_Control_Systems_1_Spectral_Measurements_2_Spectral_Entropy.cpp |
| header | src/ICoreBlocks/ICoreBlockLibrary/Blocks/Control_Systems/Spectral_Measurements/Spectral_Entropy/ICoreBlock_0_Control_Systems_1_Spectral_Measurements_2_Spectral_Entropy.h |
| default size on canvas | 150 × 76 px |
| ports at insert | 1 in, 1 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 | H |
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 |
|---|---|---|
Bin Spacing | 1 | — |
Frequency Range | Full spectrum%~%Frequency band~~Full spectrum | — |
Frequency Band | [0 1] | — |
Normalization | Normalized (0 to 1)%~%Unnormalized (bits)~~Normalized (0 … | — |
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): no Simulink equivalent. pentropy is a Signal Processing Toolbox MATLAB function, not a block; that toolbox ships no Simulink library, and a find_system sweep with LookUnderMasks across the DSP System Toolbox and Simulink library roots carries no spectral-entropy block
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).
Spectral Entropy -- the Shannon entropy of a one-sided spectrum read as a probability distribution over frequency (MATLAB pentropy(P, F, T) at a single instant). q(k) = p(k) / SUM p , H = -SUM q(k)*log2 q(k) , optionally / log2(M)
ONE PASS TO NORMALISE, ONE PASS TO ACCUMULATE, TEN IDENTICAL BODIES. Which bins take part and what the answer is divided by are settled before the run starts -- they follow from the bin count, the spacing and the band alone -- so every target unrolls the same two sums, with no branch and no moving array index anywhere.
TRANSCRIBED FROM R2026a's pentropy.m, whose whole computation is four lines (
P = S./sum(S,1); SE = sum(-P.*log2(P),1,'omitnan'); SE = SE./log2(size(P,1))), and MEASURED against a run of it before any of this was written. On the family's eight-bin spectrump = [0.12 0.85 2.40 1.10 0.35 0.60 0.18 0.07] at Delta = 0.5
R2026a answers 0.77977403034578385 normalised, 2.3393220910373516 in bits, and 0.82855924296806893 normalised over the band [0.6, 2.9]. A Python stand-in of the statement list below reproduces all three -- the middle one bit for bit, the other two to 1.4e-16, which is the base conversion described next.
⚠ THERE IS NO log2 IN THE TEN DIALECTS, so this block computes one. Every target here offers a base-TEN logarithm and nothing else (VHDL LOG10, Structured Text LOG, Verilog $log10), so log2 x is spelled log10(x) * 3.3219280948873622, and that constant is the whole difference from MATLAB's own log2 -- one rounding, worth about 1.4e-16 relative. The alternative, a per-target log2 where one exists and a conversion where it does not, would make the ten columns disagree with EACH OTHER, which is the thing export verification is there to catch.
⚠ A ZERO BIN CONTRIBUTES AN EXACT ZERO, WITH NO BRANCH. MATLAB reaches that with
'omitnan': 0*log2(0) is NaN and the sum skips it. Here the logarithm's argument is floored at realmin, so the term is 0 * log2(realmin) = 0 exactly -- the same answer, and no conditional in a VHDL expression that has nowhere to put one. Measured on [0 0 3 0 0 1 0 0]: 0.27042604148637767 normalised, with five of the eight bins empty.⚠ THE DIVISOR IS log2 OF THE SELECTED BIN COUNT. Over a band that is the number of bins INSIDE the band, because MATLAB takes
size(P,1)after cutting. A flat spectrum therefore answers exactly 1 whichever range is chosen, which is what the normalisation is for.⚠ AND THE BAND IS SNAPPED INWARD, by MATLAB's own getEffectiveRangeIdx: the first bin at or
Sample results#
28 sample(s) were non-finite (nan/inf) and are absent from the plot; they are in the table below and in the JSON.
| t | in ICoreDouble-Out-0 [3x1] entry 0 | out ICoreDouble-Out-0 |
|---|---|---|
| 0 | [0, 0, 0] | 0 |
| 0.4 | [0.7174, 1.435, 2.152] | 0.9206 |
| 0.8 | [0.9996, 1.999, 2.999] | 0.9206 |
| 1.2 | [0.6755, 1.351, 2.026] | 0.9206 |
| 1.6 | [-0.05837, -0.1167, -0.1751] | -inf |
| 2 | [-0.7568, -1.514, -2.27] | -inf |
| 2.4 | [-0.9962, -1.992, -2.988] | -inf |
| 2.8 | [-0.6313, -1.263, -1.894] | -inf |
| 3.2 | [0.1165, 0.2331, 0.3496] | 0.9206 |
| 3.6 | [0.7937, 1.587, 2.381] | 0.9206 |
| 4 | [0.9894, 1.979, 2.968] | 0.9206 |
| 4.4 | [0.5849, 1.17, 1.755] | 0.9206 |
| 4.8 | [-0.1743, -0.3487, -0.523] | -inf |
| 5.2 | [-0.8278, -1.656, -2.483] | -inf |
Every 4th of 60 samples, from the vector stimulus.
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 3c100aff6f27235305db4ad4d572f32e342718ad · produced by docsSample --out <folder> --blocks Spectral_Entropy Octave_Spectrum Magnitude_Squared_Coherence --steps 60 · data docs/generated/samples/Control_Systems__Spectral_Measurements__Spectral_Entropy.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).