Third Order Intercept — Control Systems/Spectral Measurements
Control_Systems/Spectral_Measurements/Third_Order_Intercept · 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.
Third Order Intercept
Control Systems / Spectral Measurements
The third-order output intercept of a two-tone spectrum, in decibels: the
output power at which a third-order intermodulation product would be as strong as
the carriers themselves, extrapolated from how far below them it actually sits.
OIP3 = mean(Pfund) + (mean(Pfund) −
mean(Pimod)) / 2, every term in dB. This is MATLAB's
toi(Pxx, F, 'psd'), transcribed from its source.
The two strongest tones are found in order – the second with the first's band temporarily removed, and then put back, because the first carrier's skirt is part of the spectrum the products are measured against. The pair is then sorted so f1 < f2, and the two products are sought at 2f1 − f2 and 2f2 − f1.
Ports
- p – the one-sided power spectral density, an [N,1] column or a [1,N] row of 5 to 48 bins. Bin k sits at frequency k·Δ, so the first bin is DC. Five is the smallest window that can hold two carriers and a product between them; the upper bound is what the six unrolled searches cost in emitted code. Values are expected non-negative, as MATLAB requires.
- oip3 – the intercept in dB, a scalar.
- f1 – the lower carrier's centroid frequency.
- f2 – the upper carrier's. The pair is always reported in frequency order, whichever of the two was the stronger.
Parameters
- Bin Spacing – Δ, the frequency step between neighbouring bins, a single positive number. The whole frequency axis follows from it, and the two product frequencies are arithmetic on it.
- 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. The bin count and the spacing are settled before the run, so all six searches are unrolled at export time and no emitted core contains a loop or an array index that moves.
The three HDL targets are simulation-only: four base-10
logarithms and two divisions of run-time quantities do not belong in a Q16.16
datapath. They carry the arithmetic in real and quantize only at the
port boundary.
Simulink bridge
No equivalent, so nothing crosses in either direction. Measured rather
than assumed: toi is a Signal Processing Toolbox function, that
toolbox ships no Simulink library at all, and a find_system sweep of
2102 blocks across twenty DSP System Toolbox and Simulink library roots
– at depth 6, under masks – matches nothing for intercept,
distortion, spurious, snr, sinad, thd or
sfdr. Mixed-Signal Blockset, which does carry measurement blocks of this
kind, is not installed and is not one of the eight toolboxes this work covers.
Notes
- Algebraic and stateless: the reading depends only on the spectrum presented this step.
- The first carrier's band is restored before either product is sought,
which is what
toi.mdoes. On a close-spaced pair the lower product sits inside that skirt, so a block that left it removed would measure it against a hole. - One divergence from MATLAB, stated rather than hidden. When f2 is the second harmonic of f1 the lower product falls at DC, inside the band this measurement has already removed, and MATLAB reports the whole reading as NaN. This block averages the products it could measure – the upper one alone in that case – because a NaN baked into ten generated cores is a worse answer than a number, and nothing downstream can compare one. If neither product lands inside the axis there is nothing to extrapolate from, and the reading comes back from the floor rather than as an infinity.
- It is not a reading of the other four. SNR, SINAD and THD share one decomposition of a single carrier; SFDR compares bin values. This block is the only two-carrier measurement in the family.
Code facts#
| Fact | Value |
|---|---|
| registered type | Control_Systems/Spectral_Measurements/Third_Order_Intercept |
| family | Control_Systems/Spectral_Measurements |
| solver environment class | ICoreBlock_0_Control_Systems_1_Spectral_Measurements_2_Third_Order_Intercept |
| source | src/ICoreBlocks/ICoreBlockLibrary/Blocks/Control_Systems/Spectral_Measurements/Third_Order_Intercept/ICoreBlock_0_Control_Systems_1_Spectral_Measurements_2_Third_Order_Intercept.cpp |
| header | src/ICoreBlocks/ICoreBlockLibrary/Blocks/Control_Systems/Spectral_Measurements/Third_Order_Intercept/ICoreBlock_0_Control_Systems_1_Spectral_Measurements_2_Third_Order_Intercept.h |
| default size on canvas | 150 × 90 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 | oip3 |
| 3 | out | ICoreDouble | f1 |
| 4 | out | ICoreDouble | f2 |
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 | — |
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. toi is a Signal Processing Toolbox MATLAB function, not a block; that toolbox ships no Simulink library, and a sweep of 2102 blocks across twenty DSP System Toolbox and Simulink library roots carries no intercept, distortion or spurious measurement block
Catalog contract: src/ICoreBlocks/ICoreCoder/ICoreCommandSystem/SimulinkBridge/ICoreSimulinkBlockCatalog.h
Description vs code#
The checker has a blind spot here — it could not resolve something (a grouped port bullet, a computed config name), which is reported and never counted as a pass. A reader has to settle it:
B0every stimulus in the sample errored — cross-checks skipped
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).
Third Order Intercept -- the two-tone OIP3 of a spectrum (MATLAB toi(Pxx, F, 'psd')) oip3 = mean(Pfund) + (mean(Pfund) - mean(Pimod)) / 2 every term in dB
The output power at which a third-order intermodulation product would be as strong as the carriers themselves, extrapolated from how far below them it actually sits. It is the one block in this family that looks for TWO carriers, and the sequence transcribed from computeTOI() is what the whole measurement turns on:
q = the density, with the DC BIN DOUBLED, and DC's whole band removed f1, P1 = the strongest tone left f2, P2 = the strongest tone with f1's BAND TEMPORARILY REMOVED -- and then PUT BACK sort so f1 < f2 I1 = the tone nearest 2*f1 - f2 the lower intermodulation product I2 = the tone nearest 2*f2 - f1 the upper one
⚠ "TEMPORARILY" IS LOAD BEARING. The first carrier's skirt is part of the spectrum the products are measured against, so toi.m saves that band, zeroes it for the second search, and restores it before looking for either product. A block that left it zeroed measures the lower product against a hole -- and on a close-spaced pair that hole is exactly where the product is.
⚠ THE SORT IS NOT COSMETIC. 2*f1 - f2 is the LOWER product only once f1 < f2; skip the sort and that expression goes negative, which lands off the axis and reports nothing.
⚠ AND WHEN f2 IS THE SECOND HARMONIC OF f1, the lower product falls at DC -- inside the band this block has already removed -- where it cannot be measured. MATLAB returns the whole reading as NaN. THIS BLOCK DOES NOT, and the divergence is deliberate: a NaN baked into ten generated cores is a worse answer than a number, and the suite that compares them has no notion of a NaN that is "correct". It averages the products it could measure -- the upper one alone in that case -- and the description says so in as many words. The rig covers that path with a spectrum of its own.
⚠ MEASURED AGAINST R2026a BEFORE ANY OF THIS WAS WRITTEN, on four spectra including the degenerate one, and reproduced by a Python stand-in first. Every number below is toi()'s:
T1 = [0.25 0.03 0.12 0.03 0.90 0.035 0.80 0.028 0.09 0.022 0.05 0.018], delta = 0.25 oip3 -2.5569657741493761 f1 1.0012953367875645 f2 1.5084541062801933 Pfund [-6.1753 -6.8403] dB Pimod [-14.2597 -14.5593] dB at 0.55 and 1.98928... T2 = twelve bins at delta = 0.2 oip3 -3.8460821912132714 T3 = [0.30 0.05 0.90 0.04 0.45 0.03 0.20 0.02], delta = 0.5 -- THE DEGENERATE PAIR:
Sample results#
No stimulus produced a sampled output in this rig — Invalid input size at: ICore Blocks/Home/Third Order Intercept. That is a fact about the single-block rig, not a verdict on the block: an offline batch fit, a block whose output only appears at onSolverFinish, or one that needs a driven environment cannot be exercised alone.
Category unsampled · sample time 0.1 · 60 steps · commit b0394938c · produced by docsSample --out <folder> --blocks Third_Order_Intercept --steps 60
Sample data: docs/generated/samples/Control_Systems__Spectral_Measurements__Third_Order_Intercept.json