Generated reference › Rational Fit — Control Systems/Curve Fitting
kind: generated#block#control-systems-curve-fitting

Rational Fit — Control Systems/Curve Fitting

Control_Systems/Curve_Fitting/Rational_Fit · 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.

Rational Fit

Control Systems / Curve Fitting

Fits a ratio of two polynomials to a frame of ordinates by nonlinear least squares and emits its coefficients:

ŷ(x) = (p₁xn + p₂xn−1 + … + pn+1) / (xm + q₁xm−1 + … + qm)

These are MATLAB's ratnm library models, and the output carries p₁…pn+1 then q₁…qm – coeffnames(fittype('rat21'))'s own order. The denominator is monic: its leading coefficient is fixed at 1, as in MATLAB, because scaling numerator and denominator together leaves the curve unchanged and the fit would otherwise be undetermined. So a fit of degrees (n, m) has n + m + 1 coefficients, not n + m + 2.

Ports

  • y – the frame of ordinates to fit, a column of [W, 1]. W is read off the wire, not configured: whatever feeds this port sets how many points the fit sees, and it must be at least n + m + 2 – one more point than there are coefficients – and at most 64. Entry k is the ordinate at x = Abscissa Start + k·Abscissa Step, counting from zero.
  • pq – the coefficient column, [n + m + 1, 1]: the n + 1 numerator coefficients in descending powers, then the m denominator ones, also in descending powers, with the leading 1 left implicit. Its height follows Numerator Degree and Denominator Degree.

Parameters

  • Options – empty (default), or the path of a Fit Options block (Home/Fit Options), MATLAB’s fitoptions: for the run, its Start Point and Maximum Iterations replace this block’s Start Point and Iterations where it has them; an option it leaves at default changes nothing.
  • Numerator Degree – n, a whole number from 0 to 5, MATLAB's own range. At n = 0 the numerator is the single constant p₁.
  • Denominator Degree – m, a whole number from 1 to 5. It starts at one because a rational model with a constant denominator is a polynomial, and ratn0 does not exist in MATLAB either – use Polynomial Fit for that.
  • Abscissa Start – x₀, where the first entry of the frame sits on the fitted axis. Any single number; the window must stay inside ±1000, which is what keeps the arithmetic of the iteration inside the range a real can carry on the hardware targets.
  • Abscissa Step – h, the spacing between consecutive entries, so entry k sits at x₀ + k·h. A single positive number.
  • Iterations – how many Levenberg-Marquardt sweeps the fit runs, a whole number from 1 to 400. There is no convergence test: every frame runs exactly this many sweeps, which is what keeps ten generated cores on the same path. On the verified configurations 30 sweeps and 400 agree to 2.4e−16.
  • 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.

What every target carries is the iteration itself, not a bank of coefficients: the linearized start, the normal equations, the elimination, the damping and the accept/reject test are all in the generated core, and the only things inlined from the configuration are the two degrees, the abscissa, the bounds and the sweep count. A core costs about (n+m+1)2·W·Iterations multiply-accumulates per step; Iterations and the frame length are what a user trades against that.

The three HDL targets are simulation-only real arithmetic, quantizing only at the port boundary: a division per point per sweep and a dense elimination have no Q16.16 form. They additionally clamp every coefficient to the ±32767 that format can carry, which the seven software targets and this block's own simulation do not.

Simulink bridge

None (Support::None). Fitting a rational model is a Curve Fitting Toolbox function (fit with a ratnm model), and that toolbox ships no Simulink library at all, 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.

Notes

  • Algebraic and stateless: the answer is a function of the frame on the port and nothing else. Unlike the rest of this family it keeps no window of its own, so it has no startup transient.
  • ⚠ It takes a FRAME where the rest of this family takes a stream, and the model is why. A rational curve is identified by its curvature: as the pole runs off to infinity the model degenerates into a polynomial of degree n−m with the coefficients growing without bound, so data with no curvature has no bounded best fit. A sliding window over a noisy signal meets that corner constantly – measured on this block's own export rig, a streamed window drove the coefficients onto the internal ±106 bound and left the hardware targets 1.4 % and 142 % away from the reference – while a frame is what a rational fit is for: a curve sampled on a fixed abscissa, arriving whole. Feed it from a Tapped Delay if a running window really is what you want, and expect the degeneracy.
  • ⚠ MATLAB's own start point for this model is RANDOM, and this block's is not. ratnm is one of the two library models with no start-point heuristic, so fit(x, y, 'rat21') draws rand(4,1) and warns "Start point not provided, choosing random start point"; measured, two such calls on one dataset answered [−254.4 2230.7 4908.0] and [0.30 −0.31 −0.92]. This block starts from the linearized fit instead – the least-squares solution of P(x) − y·(Q(x) − xm) = y·xm, which is linear in every coefficient – so the start is a function of the data and reproduces on any machine. It is a different estimator from the one the iteration then improves (it weights each point by its own denominator), which is exactly why it is used as a start and not as the answer.
  • ⚠ The fit is a fixed number of sweeps, not a converged solve, and the block reports no residual. There is no tolerance test, because a tolerance makes the number of sweeps depend on arithmetic that differs by one unit in the last place between backends, and the ten cores would then take different paths.
  • ⚠ A pole inside the frame is refused, not fitted. A trial whose denominator comes within 10−9 of zero at any sample (in the abscissa's own scale) is rejected and the iteration damps instead, and if the linearized start itself has a pole in the window the fit restarts from a denominator whose roots all sit two window-widths to the left. Every coefficient is also held inside ±106, a step that would leave that box stopping halfway to it. Both bounds exist so that no intermediate can overflow: a VHDL real that overflows aborts the simulation rather than returning an infinity.
  • ⚠ Its output is NOT a single polynomial, and Evaluate Fit reads only half of it. Evaluate Fit, Fit Derivative and Fit Integral speak one coefficient vector in descending powers; this is two of them, and the denominator's leading 1 is implicit. Split the column with a Demux and evaluate the halves separately if you need the curve – remembering to prepend the 1.
  • Several channels need several blocks. One frame, one fit.
  • No state space. The model is nonlinear in the denominator coefficients, so there is no A/B/C/D pair to seed and model reduction correctly declines to merge it.

Code facts#

FactValue
registered typeControl_Systems/Curve_Fitting/Rational_Fit
familyControl_Systems/Curve_Fitting
solver environment classICoreBlock_0_Control_Systems_1_Curve_Fitting_2_Rational_Fit
sourcesrc/ICoreBlocks/ICoreBlockLibrary/Blocks/Control_Systems/Curve_Fitting/Rational_Fit/ICoreBlock_0_Control_Systems_1_Curve_Fitting_2_Rational_Fit.cpp
headersrc/ICoreBlocks/ICoreBlockLibrary/Blocks/Control_Systems/Curve_Fitting/Rational_Fit/ICoreBlock_0_Control_Systems_1_Curve_Fitting_2_Rational_Fit.h
default size on canvas130 × 72 px
ports at insert1 in, 1 out
code generators implementedPython, MATLAB, Java, Rust, C, C++, VHDL, Verilog, SystemVerilog, PLC Structured Text

Ports#

#DirectionSignal typeDescription label
1inICoreDoubley
2outICoreDoublepq

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
Numerator Degree1—
Denominator Degree1—
Abscissa Start1—
Abscissa Step1—
Iterations50—
Options——

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): fitting a ratio of two polynomials is a Curve Fitting Toolbox function (fit with a ratNM model), 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 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:

  • B0 every 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).

Rational Fit -- MATLAB's ratNM library models over a FRAME, as (p1..p(n+1), q1..qm) y(x) = (p1*x^n + ... + p(n+1)) / (x^m + q1*x^(m-1) + ... + qm)

Read out of R2026a rather than remembered: ratNM exists for n = 0..5 and m = 1..5 (rat00 and rat10 are "Library function not found"), coeffnames(fittype('rat21')) reports {p1 p2 p3 q1}, and the denominator is MONIC because ratn forces it: "we force the first coefficient of the denominator to be 1, otherwise it will be an under-determined system". So m coefficients come back from the denominator, not m+1.

THE START POINT IS THE LINEARIZED FIT, BECAUSE MATLAB'S IS A RANDOM DRAW. startpt(fittype('rat21')) is empty, so fit warns "Start point not provided, choosing random start point" and uses rand(n+m+1, 1). Measured: two fit(x, y, 'rat11') calls on the same thirteen points, different rng seeds, answered [-254.4 2230.7 4908.0] and [0.30 -0.31 -0.92] -- the same model, two different answers, neither reproducible in a generated core. This block starts from the EQUATION-ERROR solve instead:

P(x_k) - y_k*(Q(x_k) - x_k^m) = y_k * x_k^m

is linear in every coefficient, so one least-squares solve of the frame itself gives a start point that depends on the data rather than on a random stream. The fixed-count Levenberg-Marquardt in ICoreNonlinearFitSupport then minimises the TRUE y-residual from there, which is the objective fit minimises.

MEASURED AGAINST R2026a rather than asserted, at each of the three shipped rig configurations and from the same start point (fit(..., StartPoint = the linearized fit, TolFun = TolX = 1e-15)), 41 windows each: rat21 1.1e-7, rat11 1.8e-8, rat22 1.1e-5 relative -- the last on a coefficient passing through zero, 1.5e-11 in absolute terms. Thirty sweeps and four hundred agree to 2.4e-16 there.

⚠ AND A RATIONAL FIT IS ONLY WELL POSED ON DATA WITH CURVATURE. As the pole runs to infinity a rational model degenerates to a polynomial of degree n-m, with p and q growing without bound together, so data that is nearly straight has no bounded best fit at all. That is why this block reads a FRAME and not a sliding window: measured on the export rig, a stream of the verifier's own noise put the coefficients on the +/-1e6 box and the three HDL columns at 1.4 % (rat11) and 142 % (rat12), where the same measurement over frames is 0.0026 %, 0.0029 % and 0.0066 %.

Sample results#

No stimulus produced a sampled output in this rig — Invalid input size at Rational Fit block: ICore Blocks/Home/Rational 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 941d69b00853e5485cdd3bd9c25ffe09be772999 · produced by docsSample --out <folder> --blocks Weibull_Fit Rational_Fit --steps 60

Sample data: docs/generated/samples/Control_Systems__Curve_Fitting__Rational_Fit.json