Cubic Spline — Control Systems/Curve Fitting
Control_Systems/Curve_Fitting/Cubic_Spline · 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.
Cubic Spline
Control Systems / Curve Fitting
Evaluates a piecewise cubic at its input: the spline is built once,
from the parameters, and each sample costs finding the interval the input
falls in and evaluating that interval's cubic
y = a·t³ + b·t² + c·t + d in the local
coordinate t = u − xₖ. The three ways of building it are the
Curve Fitting Toolbox's: through the data
(csapi/csape), near the data
(csaps), or taken straight from a piecewise coefficient matrix you
already have (a ppform, as fnval reads one).
Ports
- u – the abscissa the spline is read at, of any size [m,n]. The spline is applied to each entry independently; this is one curve, not m×n of them.
- y – the spline's value there, of the same size [m,n]. The block never reshapes a signal.
Parameters
- Breakpoints – the abscissae x, a row or column vector of 2 to 64 strictly increasing values. A repeated breakpoint is a zero-width interval and a decreasing one describes no axis at all; both are refused with a message rather than divided by.
- Data Values – the ordinates y, a vector of the same length. Read in the interpolating and smoothing types and ignored in ppform, where the pieces are given directly.
- Spline Type – where the cubics come from:
- interpolating – the spline passes exactly through every data
point. This is
csapi/csape, and the default. - smoothing – the spline balances closeness to the data against
its own curvature, controlled by Smoothing Parameter. This is
csaps, and it always carries natural end conditions, so End Conditions is ignored. - ppform – no fitting at all: Piecewise Coefficients is
the table of cubics and the block only evaluates it, which is what
fnvaldoes with a ppform. Data Values, End Conditions, End Slopes and Smoothing Parameter are all ignored.
- interpolating – the spline passes exactly through every data
point. This is
- End Conditions – the two conditions an interpolating cubic
spline needs beyond the data, used by the interpolating type only:
- not-a-knot – the third derivative is continuous across the
second and second-to-last breakpoints, so the first two pieces are one cubic and
the last two are another. This is what MATLAB's
splineandcsapido, and the default here. It needs at least 4 breakpoints. - natural – the second derivative is zero at both ends
(
csape(…, 'variational')). This is the one Simulink's lookup tables call "Cubic spline" – see Notes. - clamped – the first derivative takes the values given by End
Slopes at the two ends (
csape(…, 'complete', …)).
- not-a-knot – the third derivative is continuous across the
second and second-to-last breakpoints, so the first two pieces are one cubic and
the last two are another. This is what MATLAB's
- End Slopes – the two derivatives [d₀ dₙ] the clamped end condition imposes, at the first and last breakpoint. Ignored by the other two end conditions.
- Smoothing Parameter – p in (0, 1], the
csapsparameter: the fit minimises p·Σ(yₖ−f(xₖ))² + (1−p)·∫f″². p = 1 interpolates (and is then exactly the natural interpolating spline); a smaller p follows the data less and bends less; p near zero approaches the straight-line least-squares fit. MATLAB picks a default p from the data when you do not give one and this block does not reproduce that choice – it asks. Used by the smoothing type only. - Piecewise Coefficients – the cubics themselves for the
ppform type: one row per interval (so one fewer than there are
breakpoints) and four columns in descending powers of the local
coordinate, [a b c d] for a·t³ + b·t² +
c·t + d with t measured from that row's own breakpoint. This is exactly
the layout of a MATLAB ppform's
coefsfield, so a spline built in MATLAB can be pasted in unchanged. Ignored by the other two types. - Extrapolation – what the block answers outside the
breakpoints:
- spline – the first and last cubics are continued beyond the
ends, which is what
fnvalandppvaldo. Cubics grow fast: a little way outside the data this is a large number, and that is the honest answer rather than a wrong one. - clip – the value at the nearest end breakpoint is held, which
is what a Simulink lookup table does under
ExtrapMethod"Clip".
- spline – the first and last cubics are continued beyond the
ends, which is what
- 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 spline is structural and is inlined into the generated arithmetic rather than exposed as a tunable parameter: the number of pieces decides how many branches the emitted core contains, which no runtime parameter can change. Re-export after changing any parameter. No target solves anything – the linear system was solved at export time, and what is emitted is a chain of comparisons and one nested multiply-add per branch.
The three HDL targets are genuine synthesizable Q16.16, which is possible only because each piece is evaluated in its own local coordinate t = u − xₖ: t stays inside one interval, so t³ stays small, where the same curve written as a polynomial in u would cube the whole abscissa and overflow the format on ordinary data.
Simulink bridge
No equivalent (Support::None). The Curve Fitting Toolbox
ships no Simulink library at all: csapi, csape,
csaps and fnval are MATLAB functions. Simulink's
nearest block is the 1-D Lookup Table in its "Cubic spline"
interpolation mode, and that is a different curve – measured, it is
the natural spline, not MATLAB's not-a-knot one – while its parameter set
has nothing to carry an end condition, a smoothing parameter or a ppform. That
block already has an ICore counterpart of its own, 1-D Lookup Table, so
mapping this one onto the same library path would also make an import
ambiguous. This block therefore has no parity testbench; code export
verification still covers it across all ten languages.
Notes
- Algebraic, with no state. The output depends only on the current input, so the same input twice gives the same output twice.
- Measured against R2026a, not asserted. On x = [0 0.7 1.9 2.4 4.1 5]
with y = [1.3 -0.4 2.1 0.5 -1.2 0.9] the coefficients built here match
csapito 7.1e−15,csape's natural and clamped forms to 1.8e−15, andcsapsat p = 0.6 and p = 0.95 to 7.2e−16 and 6.7e−16. - Simulink's "Cubic spline" and MATLAB's are different splines. A 1-D
Lookup Table set to "Cubic spline" over that same data reproduces the
natural spline here to the six digits the probe printed, and differs from
csapiby up to 0.47 at points inside the table. If you are reproducing a Simulink model, choose natural and clip; if you are reproducing a MATLAB script, keep not-a-knot and spline. - not-a-knot needs four breakpoints, because the condition it imposes is stated at the second and the second-to-last of them. With two or three, use natural or clamped, which are well posed there.
- p = 1 is the natural interpolating spline, exactly – the smoothing form is written so the two constructions meet rather than merely resemble each other, which is what makes the parameter readable as "how much smoothing" instead of "which algorithm".
- Only the piecewise-polynomial form is read. The B-form MATLAB's
spapiandspap2produce is a different representation and is not accepted here; convert it withfn2fm(sp, 'pp')first. - No state space. A piecewise cubic is not linear in the input, so the block carries none and model reduction correctly declines to merge it.
Code facts#
| Fact | Value |
|---|---|
| registered type | Control_Systems/Curve_Fitting/Cubic_Spline |
| family | Control_Systems/Curve_Fitting |
| solver environment class | ICoreBlock_0_Control_Systems_1_Curve_Fitting_2_Cubic_Spline |
| source | src/ICoreBlocks/ICoreBlockLibrary/Blocks/Control_Systems/Curve_Fitting/Cubic_Spline/ICoreBlock_0_Control_Systems_1_Curve_Fitting_2_Cubic_Spline.cpp |
| header | src/ICoreBlocks/ICoreBlockLibrary/Blocks/Control_Systems/Curve_Fitting/Cubic_Spline/ICoreBlock_0_Control_Systems_1_Curve_Fitting_2_Cubic_Spline.h |
| default size on canvas | 134 × 72 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 | u |
| 2 | out | ICoreDouble | y |
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 |
|---|---|---|
Breakpoints | [0 1 2 3 4] | — |
Data Values | [0 1 0.5 1.5 1] | — |
Spline Type | interpolating%~%smoothing%~%ppform~~interpolating | — |
End Conditions | not-a-knot%~%natural%~%clamped~~not-a-knot | — |
End Slopes | [0 0] | — |
Smoothing Parameter | 0.9 | — |
Piecewise Coefficients | [0 0 1 0; 0 0 1 1; 0 0 1 2; 0 0 1 3] | — |
Extrapolation | spline%~%clip~~spline | — |
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: csapi, csape, csaps and fnval are MATLAB functions and the Curve Fitting Toolbox ships no Simulink library at all. The nearest Simulink block is the 1-D Lookup Table in its "Cubic spline" mode, which was MEASURED against R2026a and computes a DIFFERENT curve - the natural spline rather than MATLAB's not-a-knot one, differing by up to 0.47 inside the table - and whose parameters carry no end condition, no smoothing parameter and no ppform. That library path already belongs to ICore's own 1-D Lookup Table, so a second entry on it would also make an import ambiguous. Reported rather than dropped, and it carries no parity testbench
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).
Cubic Spline -- csapi / csape / csaps / fnval as one block THE SOLVE HAPPENS ONCE, WHEN THE CONFIGURATION IS READ. A cubic spline is a table of cubics, one per interval, and which cubics they are follows from the breakpoints and the data alone. So the small linear system is solved at configuration load and every one of the ten targets inlines the resulting numbers: the emitted core compares, subtracts and multiplies, and solves nothing. That is what lets a construction as heavy as a smoothing spline reach a PLC and three hardware description languages at all.
⚠ EVERY CONSTRUCTION IS MEASURED AGAINST R2026a. On x = [0 0.7 1.9 2.4 4.1 5] with y = [1.3 -0.4 2.1 0.5 -1.2 0.9] -- irregular spacing, asymmetric data -- the coefficients derived here match, entry for entry:
csapi(x, y) (not-a-knot) 7.1e-15 csape(x, y, 'variational') (natural) 1.8e-15 csape(x, y, 'complete', d) (clamped) 1.8e-15 csaps(x, y, 0.6) 7.2e-16 csaps(x, y, 0.95) 6.7e-16
Measured by compiling the block's OWN text for the solve outside the app and diffing the coefficients it produces against those five MATLAB calls, so the figures are this file's and not a prototype's.
The smoothing form is the Reinsch one: with rho = (1-p)/p, solve (R + rho*Q'Q) m = Q'y for the interior second derivatives and take f = y - rho*Q*m as the values the spline actually passes through. p = 1 gives rho = 0, i.e. the natural INTERPOLATING spline, so the two modes meet rather than merely resemble each other.
⚠ SIMULINK'S "CUBIC SPLINE" IS THE NATURAL ONE AND MATLAB'S IS NOT, WHICH IS WHY THIS BLOCK ASKS. A 1-D Lookup Table with InterpMethod "Cubic spline" over the data above reproduces the NATURAL end conditions (agreeing to the six digits the probe printed) and differs from csapi by up to 0.47 at points INSIDE the table -- not only at the ends, where a reader might expect an end condition to matter, because the end conditions propagate inwards. Two products, one name, two different curves. That measurement is also why "End Conditions" is a first-class parameter here rather than a hardcoded not-a-knot: a user reproducing a MATLAB script wants one and a user reproducing a Simulink model wants the other, and neither can tell from the answer alone which one they got.
HDL IS GENUINE Q16.16, and the local coordinate is what makes that safe. Each piece is evaluated in t = u - x_i, which never leaves the width of one interval, so the cubed term stays small where a polynomial written in u would raise the whole abscissa to the third
Sample results#
| t | in ICoreDouble-Out-0 | out ICoreDouble-Out-0 |
|---|---|---|
| 0 | -2 | -24.5 |
| 0.4 | 0.5 | 0.9688 |
| 0.8 | -2 | -24.5 |
| 1.2 | 0.5 | 0.9688 |
| 1.6 | -2 | -24.5 |
| 2 | 0.5 | 0.9688 |
| 2.4 | -2 | -24.5 |
| 2.8 | 0.5 | 0.9688 |
| 3.2 | -2 | -24.5 |
| 3.6 | 0.5 | 0.9688 |
| 4 | -2 | -24.5 |
| 4.4 | 0.5 | 0.9688 |
| 4.8 | -2 | -24.5 |
| 5.2 | 0.5 | 0.9688 |
Every 4th of 60 samples, from the table stimulus.
The same rig also ran:
| Stimulus | What it is | Output range |
|---|---|---|
impulse | Impulse: one sample of 1 at k = 5, 0 elsewhere (Repeating Sequence Stair) | 0 … 1 |
ramp | Ramp: slope 1 from t = 0 | -20.2 … 1.732 |
sine | Sine Wave: amplitude 1, 2 rad/s, no phase, no bias | -7 … 1.066 |
step | Step: 0 -> 1 at t = 1 s | 0 … 1 |
Plotted: table — Repeating Sequence Stair: [-2 -1 -0.5 0 0.5 1 2 3], one entry per sample
Category static · sample time 0.1 · 60 steps · commit 3c100aff6f27235305db4ad4d572f32e342718ad · produced by docsSample --out <folder> --blocks Cubic_Spline --steps 60 · data docs/generated/samples/Control_Systems__Curve_Fitting__Cubic_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).