Modal Parameter Fit — Control Systems/Vibration
Control_Systems/Vibration/Modal_Parameter_Fit · 2 input / 2 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.
Modal Parameter Fit
Control Systems / Vibration
The natural frequencies and damping ratios of a structure, read off a
frequency-response function – MATLAB's modalfit at
FitMethod 'pp', the peak-picking arm. The FRF arrives on two
ports as its real and imaginary parts, one entry per frequency bin, and the block
finds up to Modes resonances in it:
- the local maxima of 20·log10|H| whose prominence exceeds 0.5 dB, tallest first;
- around each one, nine bins and a three-parameter least squares – A = [Re H, −2ω Im H, −1], b = ω² Re H – giving fn = √X1/2π and ζ = X2/√X1;
- a sort by frequency, and then modalfit's own filter: a fit whose pole has a positive real part is discarded. The sort runs before the filter, so a discarded mode leaves its gap in frequency order rather than closing it.
⚠ That filter is poles(~(real(poles)<=0 & ~isreal(poles))) = NaN,
and isreal is a property of the whole array: one complex pole,
or one slot no peak was found for – NaN + i·NaN, whose imaginary part
is NaN and so is not zero – makes the test true for every pole at once. The
block carries that as a single flag, because with it removed a critically damped
peak is kept or dropped according to what the other slots hold.
Ports
- Re – the real part of the FRF, [B, 1], bin m at m·Δf hertz.
- Im – its imaginary part, the same size.
- fn – the natural frequencies in hertz, [Modes, 1], ascending. A mode the fit did not find reports 0.
- dr – the damping ratios, [Modes, 1], in the same order.
Parameters
- Modes – how many resonances to look for, a whole number from
1 to 8. This is
modalfit'smnum, and it sets the height of both output ports. - Bin Spacing – Δf, the frequency step between the FRF's bins, in hertz: bin m is at m·Δf. The same parameter, under the same name, that Welch PSD, Cross Power Spectral Density and the rest of this family carry.
- Sampling Time (s) – zero or less inherits the solver's rate; a positive value runs the block at that period.
Where the FRF comes from
Spectral Measurements / Transfer Function Estimate publishes exactly
this pair: H1 = Pyx/Pxx on Re and Im ports, [L/2+1, 1], with
its own Bin Spacing. Wire those two ports into these two, give both blocks the
same Bin Spacing, and the result is modalfit(modalfrf(x, y), ...) one
block at a time – the estimator that MATLAB's modalfrf also
uses by default. Any other source of an FRF on two real ports serves equally: this
block reads the numbers, not their provenance.
Code export
All ten targets: Python, MATLAB, Java, Rust, C, C++, VHDL, Verilog, SystemVerilog and PLC Structured Text. The peak search, the prominence rule, the nine-bin solve and the sort are emitted as loops over the input arrays; both settings are structural, since they fix the loop bounds and the output height.
⚠ The three HDL targets run it in simulation-only real arithmetic, quantizing only at the port boundaries: the fit divides by a determinant and takes square roots, which a Q16.16 datapath does not carry.
Simulink bridge
None (Support::None). modalfit is a Signal
Processing Toolbox function and that toolbox ships no Simulink library; a search
of 119 block libraries found no modal-fit block. 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.
Notes
- Stateless: the answer depends only on the FRF present at that step.
Discrete by nature all the same
(
setDiscreteOnlyBlock(true)), because there is nothing to integrate. - ⚠ A mode that is not found reports 0, where modalfit reports NaN: three of the ten targets carry no NaN, and 0 Hz is not a frequency this fit can otherwise produce.
- ⚠ Only the peak-picking arm. modalfit's default is LSCE, which needs the inverse transform of the FRF, a Hankel least squares and then the roots of a degree-2·Modes polynomial. Root finding has no bounded form in these ten targets, so it is not offered rather than approximated. LSRF is refused by MATLAB itself in code generation.
- ⚠ No mode shapes. A shape is a vector across sensors and needs several FRF columns measured at once; one column fixes the pole and says nothing about the shape.
- Peak picking is the crude estimator, and modalfit's documentation says so: it assumes the modes are well separated and lightly damped. Two modes inside one peak are reported as one.
Code facts#
| Fact | Value |
|---|---|
| registered type | Control_Systems/Vibration/Modal_Parameter_Fit |
| family | Control_Systems/Vibration |
| solver environment class | ICoreBlock_0_Control_Systems_1_Vibration_2_Modal_Parameter_Fit |
| source | src/ICoreBlocks/ICoreBlockLibrary/Blocks/Control_Systems/Vibration/Modal_Parameter_Fit/ICoreBlock_0_Control_Systems_1_Vibration_2_Modal_Parameter_Fit.cpp |
| header | src/ICoreBlocks/ICoreBlockLibrary/Blocks/Control_Systems/Vibration/Modal_Parameter_Fit/ICoreBlock_0_Control_Systems_1_Vibration_2_Modal_Parameter_Fit.h |
| default size on canvas | 150 × 86 px |
| ports at insert | 2 in, 2 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 | Re |
| 2 | in | ICoreDouble | Im |
| 3 | out | ICoreDouble | fn |
| 4 | out | ICoreDouble | dr |
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 |
|---|---|---|
Modes | 2 | not crossed |
Bin Spacing | 1 | not crossed |
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 |
| deliberately not crossed | Modes, Bin Spacing |
Caveat (shown to the user): modalfit is a Signal Processing Toolbox function and that toolbox ships no Simulink library; a name search of 119 block libraries (dsp, dsphdl, signal, simulink, hdlcoder, comm, audio, soc) found no modal-fit or modal-parameter block, so there is no path a diagram could name
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).
Modal Parameter Fit -- MATLAB's modalfit, peak-picking arm, on an FRF that arrives on ports In: the frequency-response function as Re and Im, one entry per bin, bin m at m*df hertz. Out: the natural frequencies fn and the damping ratios dr of up to
Modesmodes.The arm is modalfit's 'pp', written out:
- dB = 20 log10 |H|.
- The local maxima whose PROMINENCE exceeds 0.5 dB, tallest first, at most
Modesofthem -- findpeaks(dB, 'NPeaks', mnum, 'MinPeakProminence', 0.5, 'SortStr', 'descend').
- Nine bins around each peak, and a three-parameter least squares there:
A = [Re H, -2w Im H, -1], b = w^2 Re H, X = A\b, fn = sqrt(X1)/2pi, dr = X2/sqrt(X1).
- Sort, then turn each (fn, dr) into a pole and keep only the poles with real part <= 0.
⚠ EVERY ONE OF THOSE FOUR STEPS WAS MEASURED AGAINST R2026a, by a prototype written in the loops this block emits -- no findpeaks, no pinv, no complex type -- and run against modalfit on 300 random FRFs (24 to 96 bins, 1 to 5 modes, a third of them noisy): worst relative disagreement 2.8e-12 on fn and 3.8e-13 on dr, with the peak SELECTION identical in every case. Four details carry that agreement, and each was a measured correction:
- THE LEAST SQUARES IS ON THE REAL PARTS. modalfit writes pinv(real(A))*real(b) with a
complex A, so the second column is -2w Im H and the right-hand side is w^2 Re H. Here it is the 3x3 normal equations solved by the adjugate -- measured against MATLAB's own backslash on the same 300 cases, same agreement to 2.8e-12.
- A NEGATIVE FITTED wn^2 IS NOT AN ERROR TO MATLAB. sqrt(X1) is then imaginary, and every
quantity after it is complex; the block carries the same numbers as (re, im) pairs, so it follows modalfit into that branch instead of dropping the mode. On a third of the noisy cases that is the difference between agreeing and not.
- modalfit SORTS BEFORE IT FILTERS. The frequencies are sorted, then unphysical poles are
replaced by NaN, so a discarded mode leaves its gap where its frequency put it -- not at the end. A complex fitted frequency sorts by magnitude and then by phase, which is why the sort key here is the magnitude with a tie-break on a nonzero imaginary part.
- THE POLE FILTER IS
real(poles)<=0 & ~isreal(poles), and isreal() IS A PROPERTY OF THEWHOLE ARRAY IN MATLAB, not of an element: one complex pole makes the second test true for every pole in the vector. The block does the same -- one flag for the whole answer. ⚠ AND A SLOT NO PEAK WAS FOUND FOR COUNTS AS COMPLEX. MATLAB leaves it NaN, and NaN + 1i*NaN has an imaginary part of NaN, which is not zero, so an unfilled slot makes isreal(poles) false exactly as a genuine complex pole does. Measured: a lone critically damped peak (dr = 1, a real pole) is KEPT when another slot is empty and DROPPED when every slot is filled and every pole is real. Missing that one clause cost 4 ticks in 800 on the stream check and 1 case in 300 on the stress; with it, both are exact.
Sample results#
No stimulus produced a sampled output in this rig — Invalid input size at: ICore Blocks/Home/Modal Parameter Fit. 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 351dc4dde · produced by docsSample --out <folder> --blocks Control_Systems/Vibration/Modal_Parameter_Fit,Control_Systems/Optimization/Nelder_Mead_Search --steps 60
Sample data: docs/generated/samples/Control_Systems__Vibration__Modal_Parameter_Fit.json