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

Smoothing Spline — Control Systems/Curve Fitting

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

Smoothing Spline

Control Systems / Curve Fitting

Smooths the last W samples with MATLAB's spaps: among all curves whose m-th derivative is square integrable, it takes the one that minimizes

ρ·E(f) + F(Dmf), with E(f) = Σi wi·(yi − f(xi))2 the weighted error at the samples and F(g) = ∫g2dx the roughness.

The weight ρ is not given – it is searched for, on every sample, until the error measure E meets the configured Tolerance. That search is what this block adds over Cubic Spline's smoothing type, which is csaps with the smoothing parameter given. The newest sample sits at x = 0 and the sample i steps back at x = −i·h, the abscissa Polynomial Fit, Fourier Fit and Spline Fit use.

Ports

  • u – the sampled signal being smoothed. Scalar: one channel and its own window – see Notes.
  • f – the smoothed values at the W sites of the window, [W, 1], oldest first: row W−1 is the curve at the newest sample. These are spaps's second output, and they determine the curve – it is the natural spline of order 2m through them.
  • rho – the smoothing weight the search settled on, [1, 1]; spaps's third output. Zero means the error measure was already inside the tolerance and the answer is the plain weighted least-squares polynomial of degree m−1.

Parameters

  • Window Length – W, how many samples the curve is fitted to. A whole number from 3 to 41, and at least m + 1. Bounded above because the two banks are unrolled at export.
  • Smoothing Order – m, which derivative the roughness measures, and therefore which spline this is:
    • 1 (piecewise linear) – roughness is ∫(f′)2; the curve is a broken line through the smoothed values.
    • 2 (cubic) – ∫(f″)2, the usual cubic smoothing spline, and MATLAB's default.
    • 3 (quintic) – ∫(f″′)2, the quintic smoothing spline.
  • Tolerance – spaps's tol, and it carries MATLAB's sign convention: a positive value is the error budget the search must meet, so bigger means smoother; a negative value skips the search and uses ρ = |tol| directly, so bigger means less smoothing. Zero is refused: in spaps it asks for the interpolating variational spline, whose answer is the data itself and whose ρ is infinite. The units are those of E – weights times the square of the signal.
  • Abscissa Step – h, the spacing between consecutive samples on the fitted axis. A single positive number, and it is not cosmetic: roughness carries h1−2m and the error measure carries h, so ρ moves roughly as h−2m.
  • Error Weights – which wi the error measure uses:
    • Trapezoidal – MATLAB's own default when no weights are passed to spaps: the composite trapezoidal rule over the sites, so the two end samples weigh half as much as the interior ones.
    • Custom – the Custom Weights vector below.
  • Custom Weights – one positive weight per sample, oldest first, or a single positive number applied to every sample (so 1 gives the unweighted error measure). Read only in the Custom mode.
  • 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.

No linear system is solved in any generated core. The matrices of spaps's normal equations depend only on the sites, so the pair is diagonalized once at configuration load and each target carries two inlined banks of numbers – one that takes the window into the eigenbasis and one that brings the residual back – plus the eigenvalues. A trial value of ρ then costs one division per mode. Both banks are structural: they follow from all six settings, so re-export after changing any of them.

The search loop is bounded at 30 trial values in every target, so all ten run the same number of steps. spaps itself has no bound and was measured to need at most nine.

The three HDL targets are simulation-only real arithmetic, not synthesizable Q16.16: the search divides by a running quantity and takes a square root of one, and its trip count depends on the data. They quantize at the port boundary and in the shift register only. Note that rho leaves through a Q16.16 port there, so a configuration whose ρ runs past ±32768 – a small Abscissa Step with m = 3 reaches that easily – cannot carry it in hardware, while the seven software targets are unaffected.

Simulink bridge

None (Support::None). spaps is a Curve Fitting Toolbox function, 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 covers it across all ten languages.

Notes

  • Stateful, and discrete by nature (setDiscreteOnlyBlock(true)): the window advances once per sample. Nothing is carried between samples except the window – each window is smoothed from scratch, exactly as a call to spaps would be.
  • ⚠ It is not Cubic Spline's smoothing type. That block is csaps with the smoothing parameter p given, over a table fixed in configuration, and it evaluates the curve at its input. This one searches for the smoothing weight so the error measure meets a tolerance, over a running window, at three smoothing orders, with weights – and it emits the smoothed values rather than an evaluation. Reach for Cubic Spline when the curve is known and you want its value at a point; reach for this one when the amount of smoothing should follow the data.
  • ⚠ The stopping rule is MATLAB's 1 % band, so the answer is a step function of the data. spaps accepts the first trial whose error measure is within one percent of the tolerance, so two nearly identical windows can stop one iteration apart and differ by about half a percent of the residual. That is a property of the algorithm, not of any one target – but it is why a hardware target, which quantizes the input first, can disagree with the software ones on an occasional sample by more than rounding.
  • The window is zero-prefilled, and the zeros count. The first W−1 outputs of a run are a startup transient, matching Polynomial Fit, Spline Fit, Detrend and Savitzky-Golay Filter. A window of zeros meets any positive tolerance, so those samples come out at ρ = 0 and f = 0.
  • Scalar only. One channel and its own history; wire one block per channel.
  • The fit is a compromise, and both of its ends are reachable. A tolerance larger than the error of the best degree-m−1 polynomial gives that polynomial and ρ = 0; a tolerance approaching zero drives ρ up and the curve towards interpolation.
  • ⚠ Long windows at order 3 lose digits, and MATLAB loses them too. The error form's condition number grows as W2m: at W = 13 the smoothed values reproduce spaps to 1e−13, while at the allowed maximum W = 41 with m = 3 both sides are down to about 1e−9 of each other. That is the price of the normal equations this whole family pays in exchange for putting no factorization in a generated core.
  • No state space. Nonlinear in the window – the smoothing weight is found from the data – so model reduction correctly declines it.

Code facts#

FactValue
registered typeControl_Systems/Curve_Fitting/Smoothing_Spline
familyControl_Systems/Curve_Fitting
solver environment classICoreBlock_0_Control_Systems_1_Curve_Fitting_2_Smoothing_Spline
sourcesrc/ICoreBlocks/ICoreBlockLibrary/Blocks/Control_Systems/Curve_Fitting/Smoothing_Spline/ICoreBlock_0_Control_Systems_1_Curve_Fitting_2_Smoothing_Spline.cpp
headersrc/ICoreBlocks/ICoreBlockLibrary/Blocks/Control_Systems/Curve_Fitting/Smoothing_Spline/ICoreBlock_0_Control_Systems_1_Curve_Fitting_2_Smoothing_Spline.h
default size on canvas140 × 78 px
ports at insert1 in, 2 out
code generators implementedPython, MATLAB, Java, Rust, C, C++, VHDL, Verilog, SystemVerilog, PLC Structured Text

Ports#

#DirectionSignal typeDescription label
1inICoreDoubleu
2outICoreDoublef
3outICoreDoublerho

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 Length12—
Smoothing Order1 (piecewise linear)%~%2 (cubic)%~%3 (quintic)~~2 (cubic)—
Tolerance1—
Abscissa Step1—
Error WeightsTrapezoidal%~%Custom~~Trapezoidal—
Custom Weights1—

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): the tolerance-driven smoothing spline is a Curve Fitting Toolbox function (spaps), 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).

Smoothing Spline -- MATLAB's spaps, the TOLERANCE-DRIVEN smoothing spline, over a window [sp, values, rho] = spaps(x, y, tol, w, m) picks, among all functions whose m-th derivative is square integrable, the minimizer of

rho * E(f) + F(D^m f), E(f) = SUM(i) w_i (y_i - f(x_i))^2, F(g) = INTEGRAL g^2 dx

with rho chosen so that the error measure E(f) MEETS the tolerance tol. A negative tol names the smoothing weight directly, rho = |tol|. m = 1, 2, 3 are the piecewise-linear, cubic and quintic smoothing splines. This block runs that over the last W samples and emits spaps's own second and third outputs: the smoothed values at the W sites, and the rho it used.

⚠ THE SEARCH IS WHAT THE ROW IS ABOUT, and it is what separates this block from Curve_Fitting/Cubic_Spline's smoothing type. That one is csaps with the smoothing parameter p GIVEN, over a table fixed in configuration. Here the weight is SOUGHT on every sample until the error measure matches a tolerance -- Reinsch's iteration, which spaps.m runs as Newton's method at rho = 0 and the secant method after that, on

g(rho) = 1/sqrt(E(rho)) - 1/sqrt(tol)

stopping when E is within 1 % of tol, or when the step stops being positive.

⚠ NO LINEAR SOLVE SURVIVES INTO A GENERATED CORE, WHICH IS WHAT MAKES THE BLOCK POSSIBLE. spaps solves (Ct W^-1 C + rho A) u = Ct y afresh for every trial rho -- a banded system per trial, per sample, in ten languages. Both matrices depend only on the SITES, which are fixed by the window length and the abscissa step, so the pair is diagonalized once at configuration load: V' A V = I and V' Ct W^-1 C V = diag(lambda). Every trial rho then costs one division per mode and nothing else:

z = V' Ct y c_j = z_j / (lambda_j + rho) E = SUM lambda_j c_j^2 u'Au = SUM c_j^2 values = y - W^-1 C V c

so what each target carries is two inlined banks of numbers, K divisions per trial and a bounded loop. The loop is capped at 30 trial values so every backend runs the same number of steps; spaps.m has no cap and was measured to need at most 9 over 24000 random windows.

⚠ MEASURED AGAINST R2026a's spaps ITSELF, by compiling the math region below outside the app and diffing it case by case (12 configurations x 400 windows each, including the zero window, an exact straight line and both K = 1 corners):

spaps.m's A and Ct bit-identical for m = 1 and m = 2; worst 5.6e-17 for m = 3,

Sample results#

Smoothing Spline — Step: 0 -> 1 at t = 1 sSmoothing Spline — Step: 0 -> 1 at t = 1 s00.51012345t (s)in ICoreDouble-Out-0out ICoreDouble-Out-0 [12x1] entry 0out ICoreDouble-Out-1

The same rig also ran:

StimulusWhat it isOutput range
impulseImpulse: one sample of 1 at k = 5, 0 elsewhere (Repeating Sequence Stair)-0.1286 … 0.3104
rampRamp: slope 1 from t = 0-0.1614 … 4.8
sineSine Wave: amplitude 1, 2 rad/s, no phase, no bias-1.256 … 1.26
tableRepeating Sequence Stair: [-2 -1 -0.5 0 0.5 1 2 3], one entry per sample-1.967 … 2.669

Plotted: step — Step: 0 -> 1 at t = 1 s

Category dynamic · sample time 0.1 · 60 steps · commit ae1a5a4f23bf9080195613e8ad1f6128ae5da42d · produced by docsSample --out <folder> --blocks Turbofan_Engine_System EOM_6DOF_Wind_Angles EOM_6DOF_Custom_Variable_Mass_Wind_Angles EOM_6DOF_Simple_Variable_Mass_Wind_Angles Surface_Fit Smoothing_Spline Thin_Plate_Spline LPC_To_LSF_LSP --steps 60 · data docs/generated/samples/Control_Systems__Curve_Fitting__Smoothing_Spline.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).