StateSpace

StateSpace runs a linear model of a plant from its four matrices.
3.30–3.34

StateSpace runs a linear model of a plant from its four matrices. Use it as a reference model, a soft sensor, or a simulation you can commission against before the hardware exists. The whole model is configuration: A, B, C, D and the starting state are all parameters, so a system identified offline drops in without a code change.

flowchart LR
    i1(["inputVector — the model's input u"]) --> B["StateSpace"]
    i2(["disturbanceVector — added to the state rate"]) --> B
    i3(["reset"]) --> B
    B --> o1(["stateVector — the model state x"])
    B --> o2(["stateDotVector — rate of change of the state"])
    B --> o3(["outputVector — the model output y"])

The model is $\dot{x} = Ax + Bu + g$ and $y = Cx + Du$. The matrices are continuous-time, and the block integrates them at the task rate — do not supply a discretised model. Stability needs task period < $2/\lvert\lambda\rvert$ for every eigenvalue $\lambda$ of A: a model with a 200 Hz pole needs a task period under 1.6 ms. Nothing checks this — see Limits and errors.

Every matrix is written as one flat list, filled column by column. For a 4×4 A matrix the first four values are its first column, not its first row. Getting this wrong gives you a transposed model that is stable, plausible and wrong.

This block has no enable and no disable. It always runs.

Signals

Inputs

Path Unit Range Description
inputVector model input units unbounded The model’s input $u$, one element per input. Length is fixed when the machine is built.
disturbanceVector model state units per second unbounded Added directly to the state rate, one element per state. It bypasses the B matrix, so its units are those of $\dot{x}$ and not of an input. Leave it at zero unless you are injecting a state disturbance deliberately.
reset - - A rising value restores stateVector to stateVectorInit and zeroes stateDotVector. It is a one-shot — the block clears it. On the reset cycle outputVector keeps its previous value, so ignore it for one cycle.

Outputs

Path Unit Description
stateVector model state units The model state $x$, one element per state. This is the model’s entire memory. It starts at zero after a controller start — not at stateVectorInit — and is not cleared by a stop.
stateDotVector model state units per second The rate of change of the state on this cycle. It describes the transition out of the state published alongside it.
outputVector model output units The model output $y$, one element per output, computed from the state published in the same cycle.

Parameters

Path Unit Default Range Effect
matrixA 1/s all zeros any System dynamics, states × states, column-major. Its eigenvalues are the model’s poles and decide both its behaviour and whether it is stable at your task rate.
matrixB state units per input unit per second all zeros any Input matrix, states × inputs, column-major. How the input drives each state.
matrixC output unit per state unit all zeros any Output matrix, outputs × states, column-major. Which states are visible, and how.
matrixD output unit per input unit all zeros any Feed-through matrix, outputs × inputs, column-major. A non-zero value routes the input to the output in the same cycle, with no delay.
stateVectorInit model state units all zeros any The state reset restores. It is not applied at startup — the state starts at zero regardless.

All five parameters are persistent and survive a controller restart, which is how a model ships with a machine. No parameters exist below this block.

Setup

  1. Write down your model’s dimensions: how many states, inputs and outputs. Read the lengths of stateVector, inputVector and outputVector from the parameter tree and confirm they match. They are fixed when the machine is built and cannot be changed at runtime.

  2. Confirm your matrices are continuous-time. If your identification tool gave you a discrete model, convert it back, or the dynamics will be wrong by a factor of the task period.

  3. Check your task period against your fastest pole: it must be below $2/\lvert\lambda\rvert$, and comfortably below for accuracy. Divide 2 by the largest pole magnitude in rad/s.

    Step 3 is the check nothing does for you. A model that is perfectly stable on paper will grow without bound here if the task period is too slow for it, and the only symptom is the state running away.

  4. Write matrixA as a flat list, column by column. For a 2×2 matrix $\begin{pmatrix} a & b \ c & d\end{pmatrix}$ the list is a, c, b, d.

  5. Write matrixB, matrixC and matrixD the same way. Leave matrixD at zeros unless your model genuinely feeds through.

  6. Set inputVector to zero and watch stateVector. With no input and no disturbance it should decay toward zero from wherever it is. If it grows, go back to step 3 or check for a transposed matrix.

  7. Apply a small step on inputVector and compare outputVector against what your offline model predicts. A mismatch in shape means a transposed matrix; a mismatch in timescale means step 2.

Tuning

There is nothing to tune. The work is entering the model correctly and confirming it behaves as designed.

  1. Verify the model offline first. This block reproduces what you give it; it cannot tell you the model is wrong.
  2. Check stability at your task rate before anything else, per Setup step 3. This is the one property that depends on the controller and not on your model.
  3. Verify element ordering with a deliberately asymmetric A matrix. A symmetric test matrix cannot tell a transposed model from a correct one.
  4. Compare a step response against your offline tool. Match the shape first, then the timescale. Shape errors are ordering errors; timescale errors are discrete-versus-continuous errors.
  5. Watch for slow drift in the amplitude of an oscillatory model. Integrating at the task rate distorts lightly damped poles, and the faster the pole relative to the task rate, the worse it gets. If it matters, raise the task rate.
  6. Use reset between test runs so each starts from the same state, and set stateVectorInit to whatever that should be.

Response to a unit input for three A matrices with a 2 Hz natural frequency:damping 0.2 overshoots to 1.54 and rings, damping 0.7 overshoots to 1.05, anddamping 1.5 rises to 1.0 with no overshoot.

Read the model’s damping off the overshoot on any curve.

Symptom Cause Action
stateVector grows without bound Task period too slow for the model’s fastest pole, or a transposed matrix Check step 3 of Setup; then verify element ordering with an asymmetric matrix
Everything reads zero Expected with unconfigured matrices — they all default to zero Write matrixA through matrixD
The response has the right timescale but the wrong shape A matrix was written row by row instead of column by column Rewrite it column-major
The response is far faster or slower than your offline model Discretised matrices were supplied, but this block wants continuous-time ones Convert back to continuous time
The output moves the instant the input does Expected: a non-zero matrixD feeds the input straight through Set matrixD to zeros if your model has no feed-through
An oscillatory model slowly gains amplitude Expected: integrating at the task rate distorts lightly damped poles Raise the task rate, or add damping to the model
stateVector did not start at stateVectorInit after a restart Expected: the state starts at zero, and stateVectorInit is only what reset restores Pulse reset after every start
outputVector disagrees with stateVector for one cycle after a reset Expected: the output is not recomputed on a reset cycle Ignore the output for one cycle after a reset
reset had to be written again for a second reset Expected: it is a one-shot and the block clears it Write it true again
disturbanceVector has a much larger effect than expected Expected: it adds to the state rate directly and bypasses matrixB Scale it in state-rate units, not input units
The simulation kept running after the controller was stopped and started Expected: the state is not cleared by a stop Pulse reset after each start
A value went non-numeric and stayed there A non-numeric value reached a matrix or an input Fix the source, then pulse reset — that clears the state
You need a different number of states Not possible — the dimensions are fixed when the machine is built It needs a configuration change

A starting point for a 2 Hz second-order model with 0.7 damping, two states, one input, one output, on a 1 ms task. States are position and velocity:

matrixA         = 0, -157.91, 1, -17.59
matrixB         = 0, 157.91
matrixC         = 1, 0
matrixD         = 0
stateVectorInit = 0, 0

Note matrixA is column-major: the four values are row 1 column 1, row 2 column 1, row 1 column 2, row 2 column 2 — in that order.

Limits and errors

Limit Set by What happens Reported
Task period against the model’s poles Nothing Not checked. The task period must stay below 2 divided by the largest pole magnitude in rad/s, and the eigenvalues are never computed. Too slow a task period makes the state grow without bound, with no warning and no flag. Only reset or a restart clears it Not reported
Matrix element order Fixed Column-major, always. A row-major list silently produces a transposed model Not reported
Model time domain Fixed Continuous-time only. There is no discrete-time mode, whatever else you may have read Not reported
Matrix dimensions Machine configuration Fixed when the machine is built. Read the array lengths from the parameter tree to discover them Not reported
stateVector, outputVector Nothing Unbounded. Bound them downstream if a consumer needs a limit Not reported
Startup state Fixed The state starts at zero, not at stateVectorInit. Pulse reset to apply the initial state Not reported
Model state reset reset restores stateVectorInit. There is no way to reset to zero from the parameter tree unless stateVectorInit is zero Not reported

The block raises no errors or warnings and logs nothing. Every failure above shows as a value on a trace, not as a message.


Verified against motorcortex-control3 3.30.0 (bc348fd).