Recursive Output Error — System Identification/Recursive
System_Identification/Recursive/Recursive_Output_Error · 2 input / 4 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.
Recursive Output Error
System Identification / Recursive
Streaming identification of an output-error model – the parallel-model recursion, in which the regressor carries the model's own past outputs rather than the measured ones:
φ[k] = [ u[k−d] … u[k−d−m], −ŷ[k−1]
… −ŷ[k−n] ]
ŷ[k] = φ[k]′θ[k−1],
e[k] = y[k] − ŷ[k]
Why it exists beside the ARX block. With measured y in the regressor, output noise is correlated with the regressor and biases θ no matter how long the run is. With ŷ, it is not, and the estimate stays unbiased. That single substitution is the whole difference, and it is also the source of this block's one difficulty – see Predictor Bound.
Stateful and discrete by nature: it declares itself discrete-only and always steps at its own rate.
Ports
- u – the plant input, a scalar.
- y – the measured plant output, a scalar.
- B – the numerator estimate, [m+1, 1], descending [b0 … bm].
- F – the denominator estimate, [n+1, 1], descending and monic: the leading 1 is a constant, and the estimated coefficients follow it.
- e – the a-priori prediction error y − ŷ, the residual a Residual Whiteness Monitor downstream is meant to consume.
- stable – 1 on a normal sample, 0 on any sample where the predictor clamp engaged and the update was frozen. Per sample, not latched; put a Debounced Decision after it if a sticky verdict is wanted.
Parameters
- Numerator Order (m) and Denominator Order (n) – m must be ≤ n, or the model is noncausal and the block refuses to run. The convention matches the rest of the library: an intrinsic input delay of d = n − m samples.
- Forgetting Factor (λ) – in (0, 1]. Not a free tuning knob: the recursion divides the covariance by λ every step, so an unexcited direction grows as λ−k and the per-step Lyapunov exponent is ln(1/λ). At 0.97 that is e30 over a 1000-sample run; at 0.997 it is e3. Prefer 0.995–0.999.
- Initial Covariance – the diagonal P is seeded with. Large means "trust the data, not the seed" and adapts fast.
- Predictor Bound – the magnitude ŷ is clamped to. This is the stability guard, and what it promises is narrow: it keeps the RECURSION bounded, NOT the estimated denominator stable. Because ŷ feeds back into its own regressor, a momentarily unstable F̂ would otherwise make ŷ diverge, corrupt the regressor and diverge further. When the clamp engages the parameter update is frozen for that sample – adapting on saturated data is worse than not adapting – and stable drops to 0 so the intervention is visible rather than silent. Set it a few times larger than the plant output's working range.
- Sampling Time (s) – zero or less inherits the solver's rate.
Code export
All ten targets. The orders, and therefore every array bound, are fixed at export time, so no backend allocates or sizes dynamically. Each emits the same six phases as the live solver: shift the u history, build φ from the u and ŷ histories, predict and clamp, form e, run the gated update, then shift the ŷ history and write the ports.
The warm-up gate is real and is not inherited. The update is withheld until both histories are full; without it the first n samples would adapt against zero padding. Recursive Least Squares needs no such gate because its regressor arrives on a port – this block builds its own, exactly as Recursive IIR Identification does.
The three HDL targets are SIMULATION-ONLY, by this family's standing
rule: the recursion divides once per sample, and the covariance spans roughly
twelve decades between its seed and its settled value. Neither is carryable in the
Q16.16 signal format. They run the recursion in real arithmetic and
quantize only on the way out.
Simulink bridge
None. The System Identification Toolbox's recursive estimators are
MATLAB objects driven from a script, not a Simulink block with this port shape,
and every block in this family registers Support::None for the same
reason. It therefore has no parity testbench, which is the documented
consequence; code export verification still covers it across all ten
languages.
Notes
- No state space: the recursion is nonlinear in its own state, so no A/B/C/D describes it and model reduction correctly refuses the block.
- The seed is read from the config in every backend, never from the running estimate – export happens after the model build has already solved a step.
- F is reported monic, with the constant 1 leading, so the port width is a function of the configured order alone and never of the values.
Code facts#
| Fact | Value |
|---|---|
| registered type | System_Identification/Recursive/Recursive_Output_Error |
| family | System_Identification/Recursive |
| solver environment class | ICoreBlock_0_System_Identification_1_Recursive_2_Recursive_Output_Error |
| source | src/ICoreSDK/ICoreBlockLibrary/Blocks/System_Identification/Recursive/Output_Error/ICoreBlock_0_System_Identification_1_Recursive_2_Recursive_Output_Error.cpp |
| header | src/ICoreSDK/ICoreBlockLibrary/Blocks/System_Identification/Recursive/Output_Error/ICoreBlock_0_System_Identification_1_Recursive_2_Recursive_Output_Error.h |
| default size on canvas | 150 × 104 px |
| ports at insert | 2 in, 4 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 | in | ICoreDouble | y |
| 3 | out | ICoreDouble | B |
| 4 | out | ICoreDouble | F |
| 5 | out | ICoreDouble | e |
| 6 | out | ICoreDouble | stable |
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 |
|---|---|---|
Numerator Order | 1 | — |
Denominator Order | 2 | — |
Forgetting Factor | 0.997 | — |
Initial Covariance | 1e6 | — |
Predictor Bound | 1e4 | — |
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: the System Identification Toolbox's recursive estimators are MATLAB objects driven from a script rather than a block with this port shape, so there is no add_block target and no parameter set to map onto
Catalog contract: src/ICoreSDK/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).
Recursive Output Error — the parallel-model recursion phi[k] = [ u[k-d] .. u[k-d-m] , -yhat[k-1] .. -yhat[k-n] ]' yhat[k] = phi[k]' * theta[k-1] e[k] = y[k] - yhat[k] K = P*phi / (lambda + phi'P*phi); theta += K*e; P = (P - K(P*phi)')/lambda
Read the header before this file: it records why the regressor holds the model's OWN past outputs (and so why this block cannot reuse ICoreRecursiveLeastSquares), the stability rule and the three alternatives rejected before any generator was written, why the warm-up gate is NOT inherited from Recursive_Least_Squares, and why the HDL targets are simulation-only.
Ten generators, one recursion. Each emits the same six phases in the same order as advance() below:
- shift the u history and push u_k (depth d+m+1)
- build phi from the u history and the YHAT history
- yhat = phi'theta, then CLAMP it to +/- bound (sat flag)
- e = y_k - yhat
- if the histories are full AND the clamp did not engage, run the RLS update
- shift the yhat history and push YHAT (never y), then write the four ports
Every bound is a compile-time constant, so the generated code needs no allocation and no dynamic sizing -- which is what makes the recursion expressible in the fixed-size HDL and ST cores at all.
Sample results#
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 … 0 |
ramp | Ramp: slope 1 from t = 0 | 0 … 2 |
sine | Sine Wave: amplitude 1, 2 rad/s, no phase, no bias | 0 … 1.96 |
table | Repeating Sequence Stair: [-2 -1 -0.5 0 0.5 1 2 3], one entry per sample | -0.6001 … 2 |
Plotted: step — Step: 0 -> 1 at t = 1 s
Category dynamic · sample time 0.1 · 60 steps · commit ccf005c8 · produced by docsSample --out <folder> --steps 60 · data docs/generated/samples/System_Identification__Recursive__Recursive_Output_Error.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).