Self Conditioned Controller — Control Systems/Gain Scheduling
Control_Systems/Gain_Scheduling/Self_Conditioned_Controller · 2 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.
Self-Conditioned Controller
Control Systems / Gain Scheduling
A state-space controller [A, B, C, D] whose states stay consistent with what the actuator is really doing:
dx/dt = (A − H·C)·x + (B − H·D)·e +
H·umeas
udem = C·x + D·e
While the loop is healthy the actuator delivers what was demanded, umeas = udem, the two H terms cancel exactly and the block is the controller [A, B, C, D]. When the actuator saturates, or the controller is switched out of the loop, umeas stops following udem and the gain H pulls the states toward values consistent with the real input – which is what stops them winding up, and what makes a switch back into the loop smooth. H is chosen so that the eigenvalues of A − H·C are the poles you give.
Ports
- e – the controller input (typically a tracking error), an [m,1] column with m the column count of B and D.
- u_meas – the actuator's measured output, [1,1]: the value the plant actually received.
- u_dem – the demanded actuator command, [1,1].
Parameters
- A Matrix – the controller's state matrix, [n,n], with n from 1 to 8.
- B Matrix – [n,m], one column per entry of e.
- C Matrix – [1,n]. ⚠ One row only: with a single controller output the gain H that places n poles is unique, so this block and Simulink compute the same H. With several outputs there are infinitely many, and Simulink picks one by a robustness criterion this block does not reproduce.
- D Matrix – [1,m], the direct feedthrough from e.
- Initial State – the controller states at t = 0: a scalar (every state starts there) or an n-element vector.
- Poles of A-H*C – n real, distinct values, the
eigenvalues the conditioning dynamics are placed at. Faster poles pull the states
onto the measured input harder. Repeated poles are refused, exactly as Simulink's
placerefuses a multiplicity greater than the output count; complex poles cannot be written in a real matrix and are not offered. - 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 block exports the discretized two-input state space x[k+1] = Ad·x + Bd·[e; umeas], udem = Cd·x + Dd·[e; umeas], with the gain H, the subtraction and the discretization all folded into constants at export time by the model's discretization method – so no generated core places a pole or inverts a matrix. Every parameter is therefore structural: re-export after changing one.
The three HDL targets are genuine synthesizable Q16.16: multiply-accumulate on those constants, nothing else. Watch the range of the discretized entries if the poles are very fast.
Simulink bridge
Import and export, mapped to Aerospace Blockset's
aerolibschedule/Self-Conditioned [A,B,C,D] (its name is drawn on two
lines, so the library path carries a line break). The six parameters map one to
one: A Matrix → Ak, B Matrix →
Bk, C Matrix → Ck, D Matrix →
Dk, Initial State → x_initial and Poles
of A-H*C → vec_w. The Simulink block defines no
SampleTime (measured), so the rate stays on this side and a
positive Sampling Time (s) is reported rather than written. A Simulink block
carrying several output rows or complex poles imports, and is then refused by the
checks above with the reason named.
Notes
- Stateful and linear: n continuous states, carried as a real state space, so model reduction can read it and every discretization method applies.
- Verified against R2026a: the block's H matches MATLAB's
placeto 1.6e-15, and a zero-order-hold discretization of the state space above reproduces the Simulink block to 1.2e-15 at every sample. - ⚠ A loop closed through u_meas is refused when D is not zero. The feedthrough from e makes the block feed through as a whole here, where Simulink sees that umeas itself never reaches the output directly. Such a loop is the block's intended use, so either leave D Matrix at zero or put a Memory in the actuator path.
- The pair (A, C) must be observable: otherwise no H can move every pole, and the run is stopped with that reason – as Simulink's mask stops.
Code facts#
| Fact | Value |
|---|---|
| registered type | Control_Systems/Gain_Scheduling/Self_Conditioned_Controller |
| family | Control_Systems/Gain_Scheduling |
| solver environment class | ICoreBlock_0_Control_Systems_1_Gain_Scheduling_2_Self_Conditioned_Controller |
| source | src/ICoreBlocks/ICoreBlockLibrary/Blocks/Control_Systems/Gain_Scheduling/Self_Conditioned_Controller/ICoreBlock_0_Control_Systems_1_Gain_Scheduling_2_Self_Conditioned_Controller.cpp |
| header | src/ICoreBlocks/ICoreBlockLibrary/Blocks/Control_Systems/Gain_Scheduling/Self_Conditioned_Controller/ICoreBlock_0_Control_Systems_1_Gain_Scheduling_2_Self_Conditioned_Controller.h |
| default size on canvas | 150 × 80 px |
| ports at insert | 2 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 | e |
| 2 | in | ICoreDouble | u_meas |
| 3 | out | ICoreDouble | u_dem |
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 |
|---|---|---|
A Matrix | [-1 -0.2;0 -3] | Ak |
B Matrix | [1;1] | Bk |
C Matrix | [1 0] | Ck |
D Matrix | 0.02 | Dk |
Initial State | 0 | x_initial |
Poles of A-H*C | [-5 -2] | vec_w |
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::Both |
| Simulink path | aerolibschedule/Self-Conditioned\n[A,B,C,D] |
| port-count rule | PortsParam::None |
SampleTime parameter | no — the counterpart defines none; the rate stays on the ICore side |
| ICore config | Simulink parameter | Value translation |
|---|---|---|
A Matrix | Ak | passes through |
B Matrix | Bk | passes through |
C Matrix | Ck | passes through |
D Matrix | Dk | passes through |
Initial State | x_initial | passes through |
Poles of A-H*C | vec_w | passes through |
Caveat (shown to the user): the six mask parameters map one to one and e, u_meas are the first and second ports. H is recomputed on each side from the same four matrices and poles -- by place() in Simulink and by Ackermann's formula here, which agree to rounding for the single controller output this block supports. The Simulink block defines no SampleTime (measured), so the rate stays on the ICore side
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:
B0no sample under docs/generated/samples/ — nothing to cross-check (P8.1)
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).
Self-Conditioned Controller -- a state-space controller that tracks its real actuator dx/dt = (A - H*C)*x + (B - H*D)*e + H*u_meas u_dem = C*x + D*e
Aerospace Blockset's aerolibschedule/Self-Conditioned [A,B,C,D], read out of the masked subsystem rather than off its help page: an Integrator (IC = x_initial) fed by (Bk-H*Dk)*e plus (Ak - H*Ck)*x plus H*u_meas, and an output Ck*x + Dk*e. The mask computes H = place(Ak', Ck', vec_w)' once, at initialization.
MEASURED AGAINST R2026a, both halves. At A = [-1 -0.2; 0.35 -3], B = [1.2; 0.7], C = [1 -0.4], D = 0.03, poles [-4.5 -1.7]:
- place(A', C', poles)' = [0.94117647058823362; -3.1470588235294112], and Ackermann's
formula on the dual -- what this block computes -- gives the same gain to 1.6e-15. For ONE controller output the gain placing n distinct poles is unique, so the two methods cannot disagree by more than rounding; that is also why C is restricted to one row.
- Driven by 500 samples of ZOH-held noise on both inputs, the Simulink block (ode4 at
Ts/100) and the ZOH discretization of the LTI system below agree to 1.2e-15 at every sample. So the block IS that LTI system, and a ZOH export reproduces it exactly.
⚠ THE BLOCK CARRIES THE WHOLE THING AS ONE STATE SPACE on the stacked input [e; u_meas]: A_sc = A - H*C, B_sc = [B - H*D, H], C_sc = C, D_sc = [D, 0] seeded in the constructor, re-derived in loadBlockConfig(), and both compute pairs are taken FROM it (ADDING_NEW_BLOCKS §4, reference Transfer Function / DC Motor). Code export discretizes it by the MODEL's method.
⚠ Place's own refusals are reproduced rather than papered over: poles repeated (multiplicity greater than rank(C') = 1) and an unobservable (A, C) pair both stop the run, as both stop the Simulink mask. Complex poles are not offered -- a configuration here is a real matrix -- and the description says so.
Sample results#
No sample run is committed for this block. Samples come from the headless harness (DOCS_PLAN.md P8.1) into docs/generated/samples/; until one exists this block's behaviour is witnessed by the parity and export-verification suites, not by a plot here.