Chapter 5: From Higher-Order ODEs to State-Space Form
Lesson 3: Handling Multiple Inputs and Outputs in State-Space
This lesson extends the ODE-to-state-space conversion procedures from Lessons 1–2 to the multi-input and multi-output setting. We formalize dimensioning, construct \( \mathbf{B} \), \( \mathbf{C} \), \( \mathbf{D} \) systematically, and prove equivalence to the original higher-order input–output differential equations without invoking transfer functions (reserved for Chapter 6). Implementations are provided in Python, C++, Java, MATLAB/Simulink, and Wolfram Mathematica.
1. Conceptual Overview: What “Multiple Inputs and Outputs” Changes
In Chapter 4, we introduced the continuous-time LTI state-space model
\[ \dot{\mathbf{x}}(t) = \mathbf{A}\mathbf{x}(t) + \mathbf{B}\mathbf{u}(t),\qquad \mathbf{y}(t) = \mathbf{C}\mathbf{x}(t) + \mathbf{D}\mathbf{u}(t). \]
In the multi-input, multi-output (MIMO) case, we track dimensions explicitly: \( \mathbf{x}(t)\in\mathbb{R}^n \), \( \mathbf{u}(t)\in\mathbb{R}^m \), \( \mathbf{y}(t)\in\mathbb{R}^p \), with matrices \( \mathbf{A}\in\mathbb{R}^{n\times n} \), \( \mathbf{B}\in\mathbb{R}^{n\times m} \), \( \mathbf{C}\in\mathbb{R}^{p\times n} \), \( \mathbf{D}\in\mathbb{R}^{p\times m} \).
Compared with SISO conversion, the key structural differences are:
- Multiple inputs: the input channel becomes a vector and \( \mathbf{B} \) gains multiple columns—one per input.
- Multiple outputs: outputs become a vector; \( \mathbf{C} \) stacks output-selection/combination rows; \( \mathbf{D} \) captures any direct feedthrough from inputs.
- Coupling is allowed: outputs can depend on the same state components, and each input can drive multiple states.
flowchart TD
U["Input vector u(t) (m channels)"] --> B["State dynamics: x_dot = A x + B u"]
B --> X["State vector x(t) (n states)"]
X --> C["Output map: y = C x + D u"]
U --> C
C --> Y["Output vector y(t) (p channels)"]
In this chapter, we focus on constructing \( (\mathbf{A},\mathbf{B},\mathbf{C},\mathbf{D}) \) from given higher-order ODE descriptions. No frequency-domain tools are required.
2. Multiple Inputs: One Higher-Order ODE Driven by Several Inputs
Consider an n-th order scalar input–output differential equation (single output signal \( y \)), but driven by m inputs collected in \( \mathbf{u}(t)=[u_1(t)\;\;u_2(t)\;\;\dots\;\;u_m(t)]^\top \):
\[ y^{(n)}(t) + a_{n-1}y^{(n-1)}(t) + \cdots + a_1\dot{y}(t) + a_0 y(t) = \mathbf{b}^\top \mathbf{u}(t), \]
where \( \mathbf{b}\in\mathbb{R}^m \) is the vector of input gains. Following Lessons 1–2 (phase-variable/companion construction), define the state:
\[ \mathbf{x}(t) = \begin{bmatrix} x_1(t)\\x_2(t)\\ \vdots\\ x_n(t) \end{bmatrix} := \begin{bmatrix} y(t)\\ \dot{y}(t)\\ \vdots\\ y^{(n-1)}(t) \end{bmatrix}. \]
Then, by definition of the state components,
\[ \dot{x}_1 = x_2,\;\dot{x}_2=x_3,\;\dots,\;\dot{x}_{n-1}=x_n. \]
The last equation comes from the given ODE:
\[ \dot{x}_n = y^{(n)}(t) = -a_0 x_1 - a_1 x_2 - \cdots - a_{n-1} x_n + \mathbf{b}^\top \mathbf{u}(t). \]
Collecting these into matrix form yields the companion \( \mathbf{A} \) and a multi-column \( \mathbf{B} \):
\[ \dot{\mathbf{x}}(t) = \underbrace{\begin{bmatrix} 0 & 1 & 0 & \cdots & 0\\ 0 & 0 & 1 & \cdots & 0\\ \vdots & \vdots & \vdots & \ddots & \vdots\\ 0 & 0 & 0 & \cdots & 1\\ -a_0 & -a_1 & -a_2 & \cdots & -a_{n-1} \end{bmatrix}}_{\mathbf{A}\in\mathbb{R}^{n\times n}} \mathbf{x}(t) + \underbrace{\begin{bmatrix} 0 & 0 & \cdots & 0\\ 0 & 0 & \cdots & 0\\ \vdots & \vdots & \ddots & \vdots\\ 0 & 0 & \cdots & 0\\ b_1 & b_2 & \cdots & b_m \end{bmatrix}}_{\mathbf{B}\in\mathbb{R}^{n\times m}} \mathbf{u}(t). \]
Here the last row of \( \mathbf{B} \) is \( \mathbf{b}^\top \), and all other entries are zero, reflecting that inputs enter only the highest derivative equation in this canonical construction.
2.1 Output equation for the scalar-output case
If the measured output is exactly \( y(t) \), then \( y(t)=x_1(t) \) and we choose:
\[ y(t) = \underbrace{\begin{bmatrix}1 & 0 & \cdots & 0\end{bmatrix}}_{\mathbf{C}\in\mathbb{R}^{1\times n}} \mathbf{x}(t) + \underbrace{\begin{bmatrix}0 & 0 & \cdots & 0\end{bmatrix}}_{\mathbf{D}\in\mathbb{R}^{1\times m}} \mathbf{u}(t). \]
2.2 Equivalence proof (input–output ODE ↔ state-space)
We now prove that the state-space model above is equivalent to the original ODE, in the precise sense that any sufficiently differentiable solution \( y(t) \) of the ODE generates a state \( \mathbf{x}(t) \) that satisfies the state equations, and conversely any solution of the state equations yields a \( y(t)=x_1(t) \) satisfying the ODE.
Proposition (Equivalence in the M-input scalar-output canonical construction): Assume \( \mathbf{u}(t) \) is continuous and \( y(t) \) is \( n \)-times differentiable. Define \( \mathbf{x}(t)=[y,\dot{y},\dots,y^{(n-1)}]^\top \). Then \( y(t) \) satisfies the ODE if and only if \( (\mathbf{x}(t),\mathbf{u}(t)) \) satisfies \( \dot{\mathbf{x}}=\mathbf{A}\mathbf{x}+\mathbf{B}\mathbf{u} \) and \( y=\mathbf{C}\mathbf{x}+\mathbf{D}\mathbf{u} \).
Proof:
- (ODE ⇒ state-space) If \( y(t) \) satisfies the ODE, then by the state definition we have \( \dot{x}_i = x_{i+1} \) for \( i=1,\dots,n-1 \). Moreover, the ODE implies \( y^{(n)} = -\sum_{k=0}^{n-1} a_k y^{(k)} + \mathbf{b}^\top \mathbf{u} \), which is exactly \( \dot{x}_n = -a_0 x_1 - \cdots - a_{n-1} x_n + \mathbf{b}^\top \mathbf{u} \). Stacking these gives \( \dot{\mathbf{x}}=\mathbf{A}\mathbf{x}+\mathbf{B}\mathbf{u} \), and \( y=x_1=\mathbf{C}\mathbf{x} \).
- (state-space ⇒ ODE) If \( \dot{\mathbf{x}}=\mathbf{A}\mathbf{x}+\mathbf{B}\mathbf{u} \) holds, the first \( n-1 \) rows force \( \dot{x}_1=x_2,\dots,\dot{x}_{n-1}=x_n \). Therefore, \( x_k = y^{(k-1)} \) for all \( k=1,\dots,n \) (by repeated differentiation). The last row then becomes \( \dot{x}_n = y^{(n)} = -a_0 y - a_1 \dot{y} - \cdots - a_{n-1} y^{(n-1)} + \mathbf{b}^\top \mathbf{u} \), which is exactly the original ODE. ∎
The proof is identical in structure to the SISO case, but note that the forcing term is now the scalar \( \mathbf{b}^\top \mathbf{u}(t) \), i.e., a linear combination of the input channels.
3. Multiple Outputs: Constructing \( \mathbf{C} \) and \( \mathbf{D} \)
Suppose the internal state vector \( \mathbf{x}(t)\in\mathbb{R}^n \) has already been constructed from the underlying ODEs (Lessons 1–2), and we now specify p measured outputs \( \mathbf{y}(t)\in\mathbb{R}^p \).
In this chapter, we restrict to output equations that are algebraic linear combinations of the state and the (possibly direct) inputs:
\[ \mathbf{y}(t) = \mathbf{C}\mathbf{x}(t) + \mathbf{D}\mathbf{u}(t), \qquad \mathbf{C}\in\mathbb{R}^{p\times n},\;\mathbf{D}\in\mathbb{R}^{p\times m}. \]
3.1 Common output patterns in ODE-derived models
- Output is a subset of states: e.g., measure position and velocity, so \( y_1=x_1 \), \( y_2=x_2 \). Then \( \mathbf{C} \) is a row-selector matrix and typically \( \mathbf{D}=\mathbf{0} \).
- Output is a linear combination: e.g., \( y_1 = x_1 + 2x_3 \). Then the corresponding row of \( \mathbf{C} \) contains these coefficients.
- Direct feedthrough (algebraic output dependence on input): e.g., \( y_2 = x_2 + 0.1 u_1 \). Then \( \mathbf{D} \neq \mathbf{0} \).
3.2 Stacking outputs to build \( \mathbf{C} \) and \( \mathbf{D} \)
If each output is given as \( y_i(t) = \mathbf{c}_i^\top \mathbf{x}(t) + \mathbf{d}_i^\top \mathbf{u}(t) \) for \( i=1,\dots,p \), then stacking them yields:
\[ \mathbf{C} = \begin{bmatrix} \mathbf{c}_1^\top\\ \mathbf{c}_2^\top\\ \vdots\\ \mathbf{c}_p^\top \end{bmatrix}, \qquad \mathbf{D} = \begin{bmatrix} \mathbf{d}_1^\top\\ \mathbf{d}_2^\top\\ \vdots\\ \mathbf{d}_p^\top \end{bmatrix}. \]
3.3 Output differentiation is not required here
You may be tempted to treat outputs like \( \dot{y} \) as “new equations.” In this chapter, we do not create new states from output definitions. If you want \( \dot{y} \) as an output and \( \dot{y} \) is already a state component (as in phase-variable form), it is simply a row selection in \( \mathbf{C} \).
4. Multiple Higher-Order ODEs: Augmenting States for Multiple Outputs
A typical “multiple-output” ODE description provides multiple differential equations, e.g., two outputs \( y_1(t),y_2(t) \) each governed by its own higher-order equation, possibly driven by the same input vector \( \mathbf{u}(t) \). A principled (and chapter-consistent) construction is to form an augmented state by stacking the phase-variable states of each output equation.
Example structure (two scalar ODEs of order \( n_1 \) and \( n_2 \)):
\[ \begin{aligned} y_1^{(n_1)} + a_{1,n_1-1}y_1^{(n_1-1)} + \cdots + a_{1,0}y_1 &= \mathbf{b}_1^\top \mathbf{u},\\ y_2^{(n_2)} + a_{2,n_2-1}y_2^{(n_2-1)} + \cdots + a_{2,0}y_2 &= \mathbf{b}_2^\top \mathbf{u}. \end{aligned} \]
Define states for each output separately: \( \mathbf{x}^{(1)}=[y_1,\dot{y}_1,\dots,y_1^{(n_1-1)}]^\top \in\mathbb{R}^{n_1} \), \( \mathbf{x}^{(2)}=[y_2,\dot{y}_2,\dots,y_2^{(n_2-1)}]^\top \in\mathbb{R}^{n_2} \), and form the augmented state:
\[ \mathbf{x} = \begin{bmatrix} \mathbf{x}^{(1)}\\ \mathbf{x}^{(2)} \end{bmatrix} \in \mathbb{R}^{n_1+n_2}. \]
In the absence of explicit coupling terms between \( y_1 \) and \( y_2 \) in the ODEs, the resulting state matrix is block diagonal:
\[ \mathbf{A} = \begin{bmatrix} \mathbf{A}_1 & \mathbf{0}\\ \mathbf{0} & \mathbf{A}_2 \end{bmatrix}, \qquad \mathbf{B} = \begin{bmatrix} \mathbf{B}_1\\ \mathbf{B}_2 \end{bmatrix}, \]
where \( (\mathbf{A}_i,\mathbf{B}_i) \) are companion-form blocks like in Section 2, with the last row of \( \mathbf{B}_i \) equal to \( \mathbf{b}_i^\top \).
Outputs \( \mathbf{y}=[y_1\;\;y_2]^\top \) are extracted by selecting the first state in each block:
\[ \mathbf{y} = \underbrace{ \begin{bmatrix} 1 & 0 & \cdots & 0 & 0 & \cdots & 0\\ 0 & \cdots & 0 & 1 & 0 & \cdots & 0 \end{bmatrix}}_{\mathbf{C}\in\mathbb{R}^{2\times(n_1+n_2)}} \mathbf{x} + \underbrace{\mathbf{0}}_{\mathbf{D}\in\mathbb{R}^{2\times m}}\mathbf{u}. \]
Remark (coupled ODEs): if the ODEs contain cross-terms such as \( y_1 \) depending on \( y_2 \) or its derivatives, then block diagonality is lost; cross-terms appear as off-diagonal blocks in \( \mathbf{A} \). The construction is still systematic: every term that is linear in the chosen states becomes an entry in \( \mathbf{A} \), while input terms become entries in \( \mathbf{B} \).
flowchart TD
S0["Start: given ODEs + chosen outputs"] --> S1["Choose states per ODE: x = [y, y_dot, ..., y^(n-1)]"]
S1 --> S2["Write x_dot equations; isolate highest derivatives"]
S2 --> S3["Assemble A from state-to-state coefficients"]
S2 --> S4["Assemble B from input coefficients (m columns)"]
S3 --> S5["Define outputs: y = C x + D u (stack p rows)"]
S4 --> S5
S5 --> S6["Check dimensions: A(nxn), B(nxm), C(pxn), D(pxm)"]
S6 --> S7["Validate by differentiating: recover original ODE(s)"]
5. Worked Example: 2nd-Order Output with Two Inputs and Two Measured Outputs
Consider the scalar 2nd-order ODE driven by two inputs \( u_1,u_2 \):
\[ \ddot{y}(t) + 3\dot{y}(t) + 2y(t) = 4u_1(t) - u_2(t). \]
Define \( x_1=y \), \( x_2=\dot{y} \). Then \( \dot{x}_1=x_2 \) and
\[ \dot{x}_2 = \ddot{y} = -2x_1 - 3x_2 + 4u_1 - u_2. \]
Hence
\[ \mathbf{A}=\begin{bmatrix}0 & 1\\ -2 & -3\end{bmatrix},\quad \mathbf{B}=\begin{bmatrix}0 & 0\\ 4 & -1\end{bmatrix}. \]
Now define two outputs: \( y_1=y=x_1 \) and \( y_2=\dot{y}+0.5u_2=x_2+0.5u_2 \). Then
\[ \mathbf{C}=\begin{bmatrix}1 & 0\\ 0 & 1\end{bmatrix},\quad \mathbf{D}=\begin{bmatrix}0 & 0\\ 0 & 0.5\end{bmatrix}. \]
This example illustrates the central “MIMO bookkeeping” principle: \( \mathbf{B} \) has one column per input channel, while \( \mathbf{C} \) has one row per output channel.
6. Python Implementation (NumPy + python-control)
The control (python-control) package provides MIMO
state-space objects and simulation utilities. We implement the worked
example from Section 5 and simulate a response to piecewise inputs.
import numpy as np
# Example from Section 5
A = np.array([[0.0, 1.0],
[-2.0, -3.0]])
B = np.array([[0.0, 0.0],
[4.0, -1.0]])
C = np.array([[1.0, 0.0],
[0.0, 1.0]])
D = np.array([[0.0, 0.0],
[0.0, 0.5]])
# Build MIMO state-space model
import control as ctrl
sys = ctrl.ss(A, B, C, D)
# Time grid
t = np.linspace(0.0, 10.0, 2001)
# Inputs: u1 is a step, u2 is a decaying exponential
u1 = np.ones_like(t)
u2 = np.exp(-0.7 * t)
# Stack inputs as shape (m, len(t)) expected by python-control
U = np.vstack([u1, u2])
# Simulate with zero initial state
t_out, y_out, x_out = ctrl.forced_response(sys, T=t, U=U, X0=np.zeros(2), return_x=True)
print("y_out shape:", y_out.shape) # (p, N)
print("x_out shape:", x_out.shape) # (n, N)
# Optional: verify that y2 = x2 + 0.5 u2 numerically
y2_check = x_out[1, :] + 0.5 * u2
max_err = np.max(np.abs(y_out[1, :] - y2_check))
print("max|y2 - (x2+0.5u2)| =", max_err)
Notes for students:
- In MIMO simulation, the input signal is a matrix whose rows are input channels.
- The output signal is likewise a matrix whose rows are output channels.
- The matrices \( \mathbf{A},\mathbf{B},\mathbf{C},\mathbf{D} \) must satisfy dimension consistency: \( \mathbf{A}\mathbf{x} \) and \( \mathbf{B}\mathbf{u} \) are both \( n\times 1 \), and \( \mathbf{C}\mathbf{x} \), \( \mathbf{D}\mathbf{u} \) are \( p\times 1 \).
7. C++ Implementation (Eigen + Forward Euler)
Below is a minimal MIMO state-space simulator using the forward Euler method: \( \mathbf{x}_{k+1}=\mathbf{x}_k + \Delta t(\mathbf{A}\mathbf{x}_k+\mathbf{B}\mathbf{u}_k) \), \( \mathbf{y}_k=\mathbf{C}\mathbf{x}_k+\mathbf{D}\mathbf{u}_k \). This is purely instructional; more accurate integrators will be used later.
#include <Eigen/Dense>
#include <iostream>
#include <vector>
#include <cmath>
int main() {
using Eigen::MatrixXd;
using Eigen::VectorXd;
// Matrices from Section 5
MatrixXd A(2,2), B(2,2), C(2,2), D(2,2);
A << 0.0, 1.0,
-2.0, -3.0;
B << 0.0, 0.0,
4.0, -1.0;
C << 1.0, 0.0,
0.0, 1.0;
D << 0.0, 0.0,
0.0, 0.5;
double T = 10.0;
double dt = 0.005;
int N = static_cast<int>(T / dt) + 1;
VectorXd x = VectorXd::Zero(2);
for (int k = 0; k < N; ++k) {
double t = k * dt;
// Inputs u1(t)=1, u2(t)=exp(-0.7 t)
VectorXd u(2);
u(0) = 1.0;
u(1) = std::exp(-0.7 * t);
// Output
VectorXd y = C * x + D * u;
// Print a few samples
if (k % 400 == 0) {
std::cout << "t=" << t
<< " y1=" << y(0)
<< " y2=" << y(1)
<< " x1=" << x(0)
<< " x2=" << x(1)
<< std::endl;
}
// Euler step
VectorXd xdot = A * x + B * u;
x = x + dt * xdot;
}
return 0;
}
The important MIMO detail is that \( \mathbf{u} \) is a vector and \( \mathbf{B} \) is a matrix with one column per input channel.
8. Java Implementation (EJML + Forward Euler)
In Java, EJML provides efficient dense linear algebra. The structure mirrors the C++ example.
import org.ejml.data.DMatrixRMaj;
import org.ejml.dense.row.CommonOps_DDRM;
public class MimoStateSpaceEuler {
public static void main(String[] args) {
// Matrices from Section 5
DMatrixRMaj A = new DMatrixRMaj(new double[][]{
{0.0, 1.0},
{-2.0, -3.0}
});
DMatrixRMaj B = new DMatrixRMaj(new double[][]{
{0.0, 0.0},
{4.0, -1.0}
});
DMatrixRMaj C = new DMatrixRMaj(new double[][]{
{1.0, 0.0},
{0.0, 1.0}
});
DMatrixRMaj D = new DMatrixRMaj(new double[][]{
{0.0, 0.0},
{0.0, 0.5}
});
double T = 10.0;
double dt = 0.005;
int N = (int)(T / dt) + 1;
DMatrixRMaj x = new DMatrixRMaj(2, 1); // zero init
DMatrixRMaj u = new DMatrixRMaj(2, 1);
DMatrixRMaj Ax = new DMatrixRMaj(2, 1);
DMatrixRMaj Bu = new DMatrixRMaj(2, 1);
DMatrixRMaj xdot = new DMatrixRMaj(2, 1);
DMatrixRMaj y = new DMatrixRMaj(2, 1);
DMatrixRMaj Cx = new DMatrixRMaj(2, 1);
DMatrixRMaj Du = new DMatrixRMaj(2, 1);
for (int k = 0; k < N; k++) {
double t = k * dt;
// Inputs
u.set(0, 0, 1.0);
u.set(1, 0, Math.exp(-0.7 * t));
// y = Cx + Du
CommonOps_DDRM.mult(C, x, Cx);
CommonOps_DDRM.mult(D, u, Du);
CommonOps_DDRM.add(Cx, Du, y);
if (k % 400 == 0) {
System.out.printf("t=%.3f y1=%.6f y2=%.6f x1=%.6f x2=%.6f%n",
t, y.get(0,0), y.get(1,0), x.get(0,0), x.get(1,0));
}
// xdot = Ax + Bu
CommonOps_DDRM.mult(A, x, Ax);
CommonOps_DDRM.mult(B, u, Bu);
CommonOps_DDRM.add(Ax, Bu, xdot);
// Euler update: x = x + dt * xdot
x.add(0, 0, dt * xdot.get(0,0));
x.add(1, 0, dt * xdot.get(1,0));
}
}
}
As in C++, the MIMO structure is reflected in the matrix dimensions and channel stacking.
9. MATLAB and Simulink Implementation
MATLAB’s Control System Toolbox directly supports MIMO state-space via
ss.
% Matrices from Section 5
A = [0 1; -2 -3];
B = [0 0; 4 -1];
C = [1 0; 0 1];
D = [0 0; 0 0.5];
sys = ss(A,B,C,D);
t = linspace(0,10,2001);
u1 = ones(size(t));
u2 = exp(-0.7*t);
U = [u1(:) u2(:)]; % N-by-m
x0 = [0; 0];
[y, tOut, x] = lsim(sys, U, t, x0);
% y is N-by-p, x is N-by-n
disp(size(y));
disp(size(x));
% Verify y2 = x2 + 0.5*u2
y2check = x(:,2) + 0.5*u2(:);
fprintf("max|y2 - (x2+0.5u2)| = %.3e\n", max(abs(y(:,2) - y2check)));
9.1 Simulink model construction (programmatic)
A state-space block in Simulink can realize the same model. The script below creates a simple model with: (i) two input sources, (ii) a MIMO State-Space block, (iii) scopes for outputs.
modelName = 'mimo_state_space_demo';
new_system(modelName); open_system(modelName);
% Add input sources
add_block('simulink/Sources/Step', [modelName '/u1_step'], 'Position', [50 50 80 80]);
add_block('simulink/Sources/Clock', [modelName '/clock'], 'Position', [50 130 80 160]);
add_block('simulink/Math Operations/Gain', [modelName '/gain_minus0p7'], ...
'Gain', '-0.7', 'Position', [120 130 170 160]);
add_block('simulink/Math Operations/Math Function', [modelName '/exp'], ...
'Operator', 'exp', 'Position', [200 130 250 160]);
% Mux inputs to 2-channel vector
add_block('simulink/Signal Routing/Mux', [modelName '/Mux'], 'Inputs', '2', 'Position', [300 70 320 150]);
% Add State-Space block
add_block('simulink/Continuous/State-Space', [modelName '/StateSpace'], 'Position', [380 80 480 140]);
A = [0 1; -2 -3];
B = [0 0; 4 -1];
C = [1 0; 0 1];
D = [0 0; 0 0.5];
set_param([modelName '/StateSpace'], 'A', mat2str(A), 'B', mat2str(B), 'C', mat2str(C), 'D', mat2str(D));
% Add Scope
add_block('simulink/Sinks/Scope', [modelName '/Scope'], 'Position', [540 90 570 120]);
% Wiring
add_line(modelName, 'u1_step/1', 'Mux/1');
add_line(modelName, 'clock/1', 'gain_minus0p7/1');
add_line(modelName, 'gain_minus0p7/1', 'exp/1');
add_line(modelName, 'exp/1', 'Mux/2');
add_line(modelName, 'Mux/1', 'StateSpace/1');
add_line(modelName, 'StateSpace/1', 'Scope/1');
save_system(modelName);
This realizes the same mathematical model: \( \dot{\mathbf{x}}=\mathbf{A}\mathbf{x}+\mathbf{B}\mathbf{u} \), \( \mathbf{y}=\mathbf{C}\mathbf{x}+\mathbf{D}\mathbf{u} \), with \( m=2 \) input channels and \( p=2 \) output channels.
10. Wolfram Mathematica Implementation
Mathematica supports state-space models natively and can compute time responses for MIMO systems.
A = { {0, 1}, {-2, -3} };
B = { {0, 0}, {4, -1} };
C = { {1, 0}, {0, 1} };
D = { {0, 0}, {0, 1/2} };
sys = StateSpaceModel[{A, B, C, D}];
tmax = 10;
u1[t_] := 1;
u2[t_] := Exp[-0.7 t];
u[t_] := {u1[t], u2[t]};
x0 = {0, 0};
(* State response and output response *)
xResp = StateResponse[sys, u[t], {t, 0, tmax}, x0];
yResp = OutputResponse[sys, u[t], {t, 0, tmax}, x0];
(* Check identity: y2(t) == x2(t) + 0.5 u2(t) *)
check = Simplify[
yResp[[2]] - (xResp[[2]] + (1/2) u2[t])
];
check
The final check should simplify to 0 (or
numerically evaluate near zero), confirming that the stacked output
equation is being applied consistently.
11. Problems and Solutions
Problem 1 (Build \( \mathbf{B} \) for multiple inputs): Consider the 3rd-order ODE \[ y^{(3)}(t) + 2\ddot{y}(t) + 5\dot{y}(t) + y(t) = 3u_1(t) + 4u_2(t) - 2u_3(t). \] Using phase-variable states \( x_1=y, x_2=\dot{y}, x_3=\ddot{y} \), construct \( \mathbf{A}\in\mathbb{R}^{3\times 3} \) and \( \mathbf{B}\in\mathbb{R}^{3\times 3} \).
Solution: By definition, \( \dot{x}_1=x_2 \), \( \dot{x}_2=x_3 \). The ODE gives \[ \dot{x}_3 = y^{(3)} = -y - 5\dot{y} - 2\ddot{y} + 3u_1 + 4u_2 - 2u_3 = -x_1 - 5x_2 - 2x_3 + 3u_1 + 4u_2 - 2u_3. \] Therefore
\[ \mathbf{A}= \begin{bmatrix} 0 & 1 & 0\\ 0 & 0 & 1\\ -1 & -5 & -2 \end{bmatrix}, \qquad \mathbf{B}= \begin{bmatrix} 0 & 0 & 0\\ 0 & 0 & 0\\ 3 & 4 & -2 \end{bmatrix}. \]
Problem 2 (Multiple outputs from one state): For the system in Problem 1, define two outputs: \( y_1(t)=y(t) \) and \( y_2(t)=\dot{y}(t) + 0.2u_2(t) \). Construct \( \mathbf{C}\in\mathbb{R}^{2\times 3} \) and \( \mathbf{D}\in\mathbb{R}^{2\times 3} \).
Solution: Since \( x_1=y \) and \( x_2=\dot{y} \), \[ y_1 = x_1,\qquad y_2 = x_2 + 0.2u_2. \] Hence
\[ \mathbf{C}= \begin{bmatrix} 1 & 0 & 0\\ 0 & 1 & 0 \end{bmatrix}, \qquad \mathbf{D}= \begin{bmatrix} 0 & 0 & 0\\ 0 & 0.2 & 0 \end{bmatrix}. \]
Problem 3 (Recover the ODE from a companion-form MIMO model): Let \( \mathbf{x}=[y,\dot{y}]^\top \) and \[ \dot{\mathbf{x}}= \begin{bmatrix} 0 & 1\\ -6 & -5 \end{bmatrix}\mathbf{x} + \begin{bmatrix} 0 & 0\\ 2 & -1 \end{bmatrix}\mathbf{u},\quad y = \begin{bmatrix}1 & 0\end{bmatrix}\mathbf{x}. \] Derive the 2nd-order ODE relating \( y \) to \( u_1,u_2 \).
Solution: From \( x_1=y \) and \( \dot{x}_1=x_2 \), we have \( x_2=\dot{y} \). The second state equation gives \[ \dot{x}_2 = \ddot{y} = -6x_1 - 5x_2 + 2u_1 - u_2 = -6y - 5\dot{y} + 2u_1 - u_2. \] Therefore the equivalent ODE is:
\[ \ddot{y}(t) + 5\dot{y}(t) + 6y(t) = 2u_1(t) - u_2(t). \]
Problem 4 (Dimension consistency check): Suppose \( n=4 \), \( m=3 \), \( p=2 \). If \( \mathbf{x}\in\mathbb{R}^4 \) and \( \mathbf{u}\in\mathbb{R}^3 \), state the required dimensions of \( \mathbf{A},\mathbf{B},\mathbf{C},\mathbf{D} \) and prove that each term in \( \dot{\mathbf{x}}=\mathbf{A}\mathbf{x}+\mathbf{B}\mathbf{u} \), \( \mathbf{y}=\mathbf{C}\mathbf{x}+\mathbf{D}\mathbf{u} \) is well-defined.
Solution: The dynamics require \( \dot{\mathbf{x}}\in\mathbb{R}^4 \). Since \( \mathbf{x}\in\mathbb{R}^4 \), \( \mathbf{A}\mathbf{x} \) is defined only if \( \mathbf{A}\in\mathbb{R}^{4\times 4} \), producing a \( 4\times 1 \) vector. Since \( \mathbf{u}\in\mathbb{R}^3 \), \( \mathbf{B}\mathbf{u} \) is defined only if \( \mathbf{B}\in\mathbb{R}^{4\times 3} \), again producing a \( 4\times 1 \) vector. Their sum is therefore in \( \mathbb{R}^4 \). For outputs, \( \mathbf{y}\in\mathbb{R}^2 \). Thus \( \mathbf{C}\mathbf{x} \) requires \( \mathbf{C}\in\mathbb{R}^{2\times 4} \), producing a \( 2\times 1 \) vector, and \( \mathbf{D}\mathbf{u} \) requires \( \mathbf{D}\in\mathbb{R}^{2\times 3} \), also producing a \( 2\times 1 \) vector. Hence \( \mathbf{y}=\mathbf{C}\mathbf{x}+\mathbf{D}\mathbf{u} \) is well-defined. ∎
12. Summary
We extended the ODE-to-state-space conversion to the MIMO setting by: (i) treating inputs as vectors so \( \mathbf{B} \) acquires multiple columns, (ii) stacking outputs so \( \mathbf{C} \) acquires multiple rows, and allowing direct feedthrough via \( \mathbf{D} \), and (iii) proving equivalence to the original higher-order ODEs via direct differentiation and substitution. These constructions prepare us to work systematically with state-space realizations before introducing transfer functions and transfer matrices in Chapter 6.
13. References
- Kalman, R.E. (1960). On the general theory of control systems. Proceedings of the First International Congress on Automatic Control, 481–492.
- Kalman, R.E., Ho, Y.C., & Narendra, K.S. (1963). Controllability of linear dynamical systems. Contributions to Differential Equations, 1, 189–213.
- Luenberger, D.G. (1964). Observing the state of a linear system. IEEE Transactions on Military Electronics, 8(2), 74–80.
- Rosenbrock, H.H. (1970). State-space and multivariable theory. IEE Proceedings (foundational contributions across papers of the era).
- Wonham, W.M. (1967). On pole assignment in multi-input controllable linear systems. IEEE Transactions on Automatic Control, 12(6), 660–665.
Note: Some references above develop concepts that will be formalized later (e.g., controllability and pole assignment), but they are included as foundational theoretical sources for the broader MIMO state-space framework.