Chapter 5: From Higher-Order ODEs to State-Space Form

Lesson 1: Converting n-th Order Scalar ODEs to First-Order Vector Form

This lesson develops the foundational construction that converts a single scalar n-th order differential equation into an equivalent first-order vector differential equation by introducing a structured set of state variables. We formalize the construction for both nonlinear and linear time-invariant (LTI) cases, prove equivalence (solution correspondence and initial-condition mapping), and provide implementation templates in Python, C++, Java, MATLAB/Simulink, and Wolfram Mathematica.

1. Learning Objectives and Prerequisites

By the end of this lesson, you will be able to:

  • Convert an n-th order scalar ODE into a first-order vector ODE by defining a state vector \( \mathbf{x}(t) \in \mathbb{R}^n \).
  • Write the resulting model compactly as \( \dot{\mathbf{x}} = \mathbf{f}(t,\mathbf{x},u) \) and, for LTI cases, as \( \dot{\mathbf{x}} = \mathbf{A}\mathbf{x} + \mathbf{B}u \).
  • Prove equivalence: every solution of the scalar ODE induces a unique solution of the vector model and vice versa.
  • Implement the construction and validate it numerically across multiple programming environments.

Prerequisites from earlier chapters: vector/matrix notation (Chapter 2), vector-matrix differential equations and existence/uniqueness ideas (Chapter 3), and the basic meaning of state, input, and output (Chapter 4).

2. Problem Setup — General n-th Order Scalar ODE

Consider a scalar function \( y(t) \in \mathbb{R} \) governed by an n-th order ordinary differential equation (ODE) with an input \( u(t) \in \mathbb{R} \):

\[ y^{(n)}(t) = F\!\Big(t,\;y(t),\;\dot{y}(t),\;\dots,\;y^{(n-1)}(t),\;u(t)\Big), \quad t \in [t_0,t_f]. \]

The ODE becomes an initial value problem (IVP) once we specify the initial derivatives: \( y(t_0),\dot{y}(t_0),\dots,y^{(n-1)}(t_0) \). Our objective is to rewrite this single n-th order equation as a system of n first-order equations in a vector state.

Key idea: package the “hidden” derivative information into a vector so that the dynamics become first-order in time. This is precisely what state variables encode in modern (state-space) control.

3. State Definition and First-Order Vector Construction

Define the state components as successive derivatives of \( y(t) \):

\[ x_1(t) \triangleq y(t),\quad x_2(t) \triangleq \dot{y}(t),\quad \dots,\quad x_n(t) \triangleq y^{(n-1)}(t). \]

Stack them into a vector \( \mathbf{x}(t) = [x_1(t)\;x_2(t)\;\dots\;x_n(t)]^\top \in \mathbb{R}^n \). Then, for \( i=1,2,\dots,n-1 \) we have the “shift” relations:

\[ \dot{x}_i(t) = x_{i+1}(t), \quad i=1,2,\dots,n-1. \]

The last equation uses the original ODE, because \( x_n(t)=y^{(n-1)}(t) \) implies \( \dot{x}_n(t)=y^{(n)}(t) \). Hence:

\[ \dot{x}_n(t) = F\!\Big(t,\;x_1(t),\;x_2(t),\;\dots,\;x_n(t),\;u(t)\Big). \]

Therefore, the n-th order scalar ODE is equivalent to the first-order vector system:

\[ \dot{\mathbf{x}}(t) = \mathbf{f}\!\big(t,\mathbf{x}(t),u(t)\big) \triangleq \begin{bmatrix} x_2(t)\\ x_3(t)\\ \vdots\\ x_n(t)\\ F\!\big(t,x_1(t),x_2(t),\dots,x_n(t),u(t)\big) \end{bmatrix}. \]

If the measured output is the original variable \( y(t) \), then: \( y(t)=x_1(t) \). (Output mappings are formalized in Chapter 4 and extended in later lessons.)

flowchart TD
  A["Start with scalar ODE: y^(n) = F(t, y, y', ..., y^(n-1), u)"] --> B["Define states: x1=y, x2=y', ..., xn=y^(n-1)"]
  B --> C["Differentiate: x1dot=x2, x2dot=x3, ..., x(n-1)dot=xn"]
  C --> D["Use ODE: xndot = F(t, x1, x2, ..., xn, u)"]
  D --> E["Assemble vector model: xdot = f(t, x, u)"]
  E --> F["Output (if needed): y = x1"]
        

4. Linear Time-Invariant Specialization

A central and highly common case is an LTI scalar ODE where the highest derivative is expressed linearly in terms of lower derivatives and input:

\[ y^{(n)}(t) + a_{n-1}y^{(n-1)}(t) + a_{n-2}y^{(n-2)}(t) + \dots + a_1\dot{y}(t) + a_0 y(t) = b\,u(t), \]

where the coefficients \( a_0,\dots,a_{n-1},b \in \mathbb{R} \) are constants. Using the state definition in Section 3, the last state equation becomes:

\[ \dot{x}_n(t) = -a_0 x_1(t) - a_1 x_2(t) - \dots - a_{n-2}x_{n-1}(t) - a_{n-1}x_n(t) + b\,u(t). \]

Combining with \( \dot{x}_i=x_{i+1} \) for \( i=1,\dots,n-1 \), we obtain:

\[ \dot{\mathbf{x}}(t) = \mathbf{A}\mathbf{x}(t) + \mathbf{B}u(t), \quad y(t) = \mathbf{C}\mathbf{x}(t) + \mathbf{D}u(t), \]

with:

\[ \mathbf{A} = \begin{bmatrix} 0 & 1 & 0 & \dots & 0\\ 0 & 0 & 1 & \dots & 0\\ \vdots & \vdots & \vdots & \ddots & \vdots\\ 0 & 0 & 0 & \dots & 1\\ -a_0 & -a_1 & -a_2 & \dots & -a_{n-1} \end{bmatrix}, \quad \mathbf{B} = \begin{bmatrix} 0\\0\\ \vdots\\0\\ b \end{bmatrix}, \]

\[ \mathbf{C} = \begin{bmatrix} 1 & 0 & 0 & \dots & 0 \end{bmatrix}, \quad \mathbf{D} = [0]. \]

The specific structure of \( \mathbf{A} \) that you see here will be examined systematically in the next lesson. For now, interpret it as a direct encoding of the derivative chain plus the ODE closure in the last row.

flowchart TD
  U["u(t)"] --> S["sum: b*u - a0*x1 - a1*x2 - ... - a(n-1)*xn"]
  S --> I_n["Integrator"] --> Xn["xn = y^(n-1)"]
  Xn --> I_n1["Integrator"] --> Xn1["x(n-1) = y^(n-2)"]
  Xn1 --> Dots["..."] --> I2["Integrator"] --> X2["x2 = y'"]
  X2 --> I1["Integrator"] --> X1["x1 = y"]
  X1 --> Y["y(t)=x1"]
        

5. Equivalence Theorem and Proof

We now prove that the conversion is mathematically exact: it does not approximate or “simplify” the dynamics. It only changes coordinates from derivatives of \( y \) to a state vector.

Theorem 1 (Solution Correspondence).

Assume \( F(t,\cdot) \) is such that the IVP for the scalar ODE admits a unique solution on some interval \( [t_0,T] \). Define the state mapping: \( \mathbf{x}(t) = [y(t)\;\dot{y}(t)\;\dots\;y^{(n-1)}(t)]^\top \). Then:

  • If \( y(t) \) solves the scalar ODE, then \( \mathbf{x}(t) \) solves \( \dot{\mathbf{x}} = \mathbf{f}(t,\mathbf{x},u) \).
  • Conversely, if \( \mathbf{x}(t) \) solves \( \dot{\mathbf{x}} = \mathbf{f}(t,\mathbf{x},u) \), then \( y(t)=x_1(t) \) solves the scalar ODE.

Proof.

(Forward direction) Suppose \( y(t) \) satisfies: \( y^{(n)}(t)=F(t,y(t),\dot{y}(t),\dots,y^{(n-1)}(t),u(t)) \). Define \( x_i(t)=y^{(i-1)}(t) \) for \( i=1,\dots,n \). Then for \( i=1,\dots,n-1 \), differentiating gives:

\[ \dot{x}_i(t) = \frac{d}{dt}y^{(i-1)}(t)=y^{(i)}(t)=x_{i+1}(t). \]

For \( i=n \), we have \( \dot{x}_n(t)=y^{(n)}(t) \), hence:

\[ \dot{x}_n(t) = F\!\big(t,x_1(t),x_2(t),\dots,x_n(t),u(t)\big). \]

Collecting these n equations yields \( \dot{\mathbf{x}}(t)=\mathbf{f}(t,\mathbf{x}(t),u(t)) \).

(Reverse direction) Suppose \( \mathbf{x}(t) \) satisfies the vector system with the same \( \mathbf{f} \). Define \( y(t)=x_1(t) \). From the system, \( \dot{x}_1(t)=x_2(t) \), hence \( \dot{y}(t)=x_2(t) \). Differentiating repeatedly and using the shift relations gives:

\[ y^{(k)}(t) = x_{k+1}(t),\quad k=1,2,\dots,n-1. \]

Finally, \( y^{(n)}(t)=\dot{x}_n(t) \), and the last state equation enforces:

\[ y^{(n)}(t)=F\!\big(t,y(t),\dot{y}(t),\dots,y^{(n-1)}(t),u(t)\big). \]

Hence \( y(t) \) satisfies the original scalar ODE. This completes the proof.

Corollary 1 (Initial-Condition Mapping).

The scalar initial conditions map to the vector initial condition by: \( \mathbf{x}(t_0) = [y(t_0)\;\dot{y}(t_0)\;\dots\;y^{(n-1)}(t_0)]^\top. \) Conversely, specifying \( \mathbf{x}(t_0) \) uniquely specifies the scalar initial derivatives up to order \( n-1 \).

6. Computational Construction Checklist

For a scalar ODE given explicitly as \( y^{(n)} = F(t,y,\dot{y},\dots,y^{(n-1)},u) \), the conversion is mechanical:

  1. Choose states \( x_1=y,\;x_2=\dot{y},\;\dots,\;x_n=y^{(n-1)} \).
  2. Write \( \dot{x}_1=x_2,\;\dot{x}_2=x_3,\;\dots,\;\dot{x}_{n-1}=x_n \).
  3. Write \( \dot{x}_n = F(t,x_1,\dots,x_n,u) \).
  4. If the output is the original variable, set \( y=x_1 \).
  5. (Validation) Differentiate \( y=x_1 \) repeatedly and eliminate intermediate states to recover the scalar ODE.

For the LTI case, steps 1–3 automatically produce matrices \( \mathbf{A},\mathbf{B} \) (and optionally \( \mathbf{C},\mathbf{D} \)). Numerical simulations can then be performed with any ODE solver.

7. Python Implementation (NumPy/SciPy + python-control)

The function below constructs \( \mathbf{A},\mathbf{B},\mathbf{C},\mathbf{D} \) for the LTI scalar ODE: \( y^{(n)} + a_{n-1}y^{(n-1)} + \dots + a_0 y = b u \). We then simulate using scipy.integrate.solve_ivp and optionally wrap as a state-space object using control.ss.

import numpy as np
from dataclasses import dataclass

@dataclass
class StateSpace:
    A: np.ndarray
    B: np.ndarray
    C: np.ndarray
    D: np.ndarray

def nth_order_ode_to_ss(a, b=1.0):
    """
    Build state-space matrices for:
        y^(n) + a[n-1] y^(n-1) + ... + a[1] y' + a[0] y = b u

    Parameters
    ----------
    a : array_like, shape (n,)
        Coefficients [a0, a1, ..., a(n-1)].
    b : float
        Input gain.

    Returns
    -------
    StateSpace(A,B,C,D)
    """
    a = np.asarray(a, dtype=float).ravel()
    n = a.size
    A = np.zeros((n, n), dtype=float)
    # shift rows
    for i in range(n - 1):
        A[i, i + 1] = 1.0
    # last row
    A[n - 1, :] = -a  # [-a0, -a1, ..., -a(n-1)]
    B = np.zeros((n, 1), dtype=float)
    B[n - 1, 0] = float(b)
    C = np.zeros((1, n), dtype=float)
    C[0, 0] = 1.0
    D = np.zeros((1, 1), dtype=float)
    return StateSpace(A, B, C, D)

# Example: third-order system: y''' + 3 y'' + 3 y' + 1 y = 2 u
ss = nth_order_ode_to_ss(a=[1.0, 3.0, 3.0], b=2.0)

# Simulate: xdot = A x + B u(t)
from scipy.integrate import solve_ivp

def u_of_t(t):
    # step input
    return 1.0 if t >= 0.0 else 0.0

def f(t, x):
    x = x.reshape(-1, 1)
    dx = ss.A @ x + ss.B * u_of_t(t)
    return dx.ravel()

t0, tf = 0.0, 10.0
x0 = np.array([0.0, 0.0, 0.0])  # y(0), y'(0), y''(0)
sol = solve_ivp(f, (t0, tf), x0, max_step=0.01, rtol=1e-8, atol=1e-10)

# Output y(t) = C x(t)
y = (ss.C @ sol.y).ravel()

print("A=\n", ss.A)
print("B=\n", ss.B)
print("Final y(tf)=", y[-1])

# Optional: python-control wrapper (state-space object)
try:
    import control
    sys = control.ss(ss.A, ss.B, ss.C, ss.D)
    # sys can be used with control.forced_response, control.initial_response, etc.
    print(sys)
except ImportError:
    print("python-control not installed; install with: pip install control")

Recommended Python libraries (as they appear across modern control workflows): numpy, scipy (ODE solvers), control (state-space objects and responses), and sympy (symbolic checks).

8. C++ Implementation (Eigen + RK4 from Scratch)

The following C++ template constructs the matrices and simulates \( \dot{\mathbf{x}}=\mathbf{A}\mathbf{x}+\mathbf{B}u(t) \) using a fixed-step 4th-order Runge–Kutta method (RK4). Matrix operations use Eigen.

#include <Eigen/Dense>
#include <iostream>
#include <vector>
#include <functional>

struct StateSpace {
  Eigen::MatrixXd A;
  Eigen::MatrixXd B;
  Eigen::MatrixXd C;
  Eigen::MatrixXd D;
};

StateSpace nthOrderOdeToSS(const std::vector<double>& a, double b) {
  const int n = static_cast<int>(a.size());
  StateSpace ss;
  ss.A = Eigen::MatrixXd::Zero(n, n);
  for (int i = 0; i < n - 1; ++i) ss.A(i, i + 1) = 1.0;

  for (int j = 0; j < n; ++j) ss.A(n - 1, j) = -a[j];

  ss.B = Eigen::MatrixXd::Zero(n, 1);
  ss.B(n - 1, 0) = b;

  ss.C = Eigen::MatrixXd::Zero(1, n);
  ss.C(0, 0) = 1.0;

  ss.D = Eigen::MatrixXd::Zero(1, 1);
  return ss;
}

Eigen::VectorXd rk4Step(
  const std::function<Eigen::VectorXd(double, const Eigen::VectorXd&)>& f,
  double t, const Eigen::VectorXd& x, double h
) {
  Eigen::VectorXd k1 = f(t, x);
  Eigen::VectorXd k2 = f(t + 0.5*h, x + 0.5*h*k1);
  Eigen::VectorXd k3 = f(t + 0.5*h, x + 0.5*h*k2);
  Eigen::VectorXd k4 = f(t + h, x + h*k3);
  return x + (h/6.0) * (k1 + 2.0*k2 + 2.0*k3 + k4);
}

int main() {
  // Example: y''' + 3 y'' + 3 y' + 1 y = 2 u
  std::vector<double> a = {1.0, 3.0, 3.0}; // [a0, a1, a2]
  double b = 2.0;
  StateSpace ss = nthOrderOdeToSS(a, b);

  auto u = [](double t) { return (t >= 0.0) ? 1.0 : 0.0; };

  auto f = [&ss, &u](double t, const Eigen::VectorXd& x) {
    return ss.A * x + ss.B * u(t);
  };

  double t0 = 0.0, tf = 10.0, h = 1e-3;
  int N = static_cast<int>((tf - t0) / h);

  Eigen::VectorXd x = Eigen::VectorXd::Zero(3); // [y, y', y'']
  double t = t0;

  for (int k = 0; k < N; ++k) {
    x = rk4Step(f, t, x, h);
    t += h;
  }

  double y = (ss.C * x)(0, 0);
  std::cout << "Final y(tf) = " << y << std::endl;
  return 0;
}

Typical C++ libraries in control-oriented workflows include Eigen (linear algebra) and custom or library ODE integrators. The state construction itself is pure linear algebra; simulation requires numerical integration.

9. Java Implementation (EJML + Simple RK4)

In Java, EJML provides matrix operations. Below is a compact RK4 simulator for the same LTI construction.

import org.ejml.data.DMatrixRMaj;
import org.ejml.dense.row.CommonOps_DDRM;

import java.util.function.DoubleFunction;

public class NthOrderOdeToStateSpace {

    static class StateSpace {
        DMatrixRMaj A, B, C, D;
        StateSpace(DMatrixRMaj A, DMatrixRMaj B, DMatrixRMaj C, DMatrixRMaj D){
            this.A = A; this.B = B; this.C = C; this.D = D;
        }
    }

    static StateSpace nthOrderOdeToSS(double[] a, double b) {
        int n = a.length;
        DMatrixRMaj A = new DMatrixRMaj(n, n);
        for (int i = 0; i < n - 1; i++) A.set(i, i + 1, 1.0);
        for (int j = 0; j < n; j++) A.set(n - 1, j, -a[j]);

        DMatrixRMaj B = new DMatrixRMaj(n, 1);
        B.set(n - 1, 0, b);

        DMatrixRMaj C = new DMatrixRMaj(1, n);
        C.set(0, 0, 1.0);

        DMatrixRMaj D = new DMatrixRMaj(1, 1); // zero
        return new StateSpace(A, B, C, D);
    }

    static DMatrixRMaj f(StateSpace ss, DoubleFunction<Double> u, double t, DMatrixRMaj x) {
        // dx = A x + B u(t)
        DMatrixRMaj dx = new DMatrixRMaj(x.numRows, 1);
        CommonOps_DDRM.mult(ss.A, x, dx);
        double ut = u.apply(t);
        for (int i = 0; i < x.numRows; i++) {
            dx.add(i, 0, ss.B.get(i, 0) * ut);
        }
        return dx;
    }

    static DMatrixRMaj rk4Step(StateSpace ss, DoubleFunction<Double> u, double t, DMatrixRMaj x, double h) {
        DMatrixRMaj k1 = f(ss, u, t, x);

        DMatrixRMaj x2 = x.copy(); CommonOps_DDRM.addEquals(x2, 0.5*h, k1);
        DMatrixRMaj k2 = f(ss, u, t + 0.5*h, x2);

        DMatrixRMaj x3 = x.copy(); CommonOps_DDRM.addEquals(x3, 0.5*h, k2);
        DMatrixRMaj k3 = f(ss, u, t + 0.5*h, x3);

        DMatrixRMaj x4 = x.copy(); CommonOps_DDRM.addEquals(x4, h, k3);
        DMatrixRMaj k4 = f(ss, u, t + h, x4);

        DMatrixRMaj out = x.copy();
        for (int i = 0; i < out.numRows; i++) {
            double incr = (h/6.0) * (k1.get(i,0) + 2.0*k2.get(i,0) + 2.0*k3.get(i,0) + k4.get(i,0));
            out.add(i, 0, incr);
        }
        return out;
    }

    public static void main(String[] args) {
        // Example: y''' + 3 y'' + 3 y' + 1 y = 2 u
        double[] a = new double[]{1.0, 3.0, 3.0};
        double b = 2.0;
        StateSpace ss = nthOrderOdeToSS(a, b);

        DoubleFunction<Double> u = (t) -> (t >= 0.0) ? 1.0 : 0.0;

        double t0 = 0.0, tf = 10.0, h = 1e-3;
        int N = (int)((tf - t0)/h);

        DMatrixRMaj x = new DMatrixRMaj(3, 1); // [y, y', y''] initially zeros
        double t = t0;

        for (int k = 0; k < N; k++) {
            x = rk4Step(ss, u, t, x, h);
            t += h;
        }

        DMatrixRMaj y = new DMatrixRMaj(1,1);
        CommonOps_DDRM.mult(ss.C, x, y);
        System.out.println("Final y(tf) = " + y.get(0,0));
    }
}

Common Java libraries used in control/estimation pipelines include EJML (linear algebra) and Apache Commons Math (ODE integrators). Here we used EJML plus a self-contained RK4 to keep the construction transparent.

10. MATLAB + Simulink Implementation

MATLAB’s Control System Toolbox supports continuous-time state-space models directly. The code below builds \( \mathbf{A},\mathbf{B},\mathbf{C},\mathbf{D} \), creates an ss object, and simulates.

% Example: y''' + 3 y'' + 3 y' + 1 y = 2 u
a = [1; 3; 3];   % [a0; a1; a2]
b = 2;

n = length(a);
A = zeros(n);
A(1:n-1, 2:n) = eye(n-1);
A(n, :) = -a(:)';

B = zeros(n,1);
B(n) = b;

C = zeros(1,n);
C(1) = 1;

D = 0;

sys = ss(A,B,C,D);

t = linspace(0,10,5001);
u = ones(size(t));           % step input
x0 = [0;0;0];                % [y(0); y'(0); y''(0)]
[y,t_out,x] = lsim(sys,u,t,x0);

disp('A='), disp(A)
disp('Final y(tf)='), disp(y(end))

For Simulink, one natural realization is a chain of integrators that produces \( x_n \to x_{n-1} \to \dots \to x_1=y \), with feedback gains implementing the last equation. The following script programmatically creates such a model using basic blocks (Integrator, Sum, Gain, Inport, Outport). This avoids manual drawing and remains fully text-based.

% Programmatic Simulink construction for the same third-order example
model = 'nth_order_chain';
new_system(model); open_system(model);

% Blocks
add_block('simulink/Sources/In1', [model '/u']);
add_block('simulink/Math Operations/Sum', [model '/Sum'], 'Inputs', '++++');
add_block('simulink/Math Operations/Gain', [model '/bGain'], 'Gain', '2');

add_block('simulink/Continuous/Integrator', [model '/Int3']);
add_block('simulink/Continuous/Integrator', [model '/Int2']);
add_block('simulink/Continuous/Integrator', [model '/Int1']);
add_block('simulink/Sinks/Out1', [model '/y']);

% Feedback gains for -a0*x1 - a1*x2 - a2*x3 (a0=1,a1=3,a2=3)
add_block('simulink/Math Operations/Gain', [model '/a0'], 'Gain', '-1');
add_block('simulink/Math Operations/Gain', [model '/a1'], 'Gain', '-3');
add_block('simulink/Math Operations/Gain', [model '/a2'], 'Gain', '-3');

% Layout positions (minimal; adjust as desired)
set_param([model '/u'], 'Position', [30 80 60 100]);
set_param([model '/bGain'], 'Position', [90 75 130 105]);
set_param([model '/Sum'], 'Position', [170 70 200 110]);
set_param([model '/Int3'], 'Position', [240 70 270 110]);
set_param([model '/Int2'], 'Position', [320 70 350 110]);
set_param([model '/Int1'], 'Position', [400 70 430 110]);
set_param([model '/y'], 'Position', [470 80 500 100]);

set_param([model '/a2'], 'Position', [240 150 270 180]);
set_param([model '/a1'], 'Position', [320 150 350 180]);
set_param([model '/a0'], 'Position', [400 150 430 180]);

% Connections: u -> bGain -> Sum -> Int3 -> Int2 -> Int1 -> y
add_line(model, 'u/1', 'bGain/1');
add_line(model, 'bGain/1', 'Sum/1');
add_line(model, 'Sum/1', 'Int3/1');
add_line(model, 'Int3/1', 'Int2/1');
add_line(model, 'Int2/1', 'Int1/1');
add_line(model, 'Int1/1', 'y/1');

% Feedback taps: x3 from Int3 output, x2 from Int2 output, x1 from Int1 output
add_line(model, 'Int3/1', 'a2/1');
add_line(model, 'Int2/1', 'a1/1');
add_line(model, 'Int1/1', 'a0/1');

% Gains feed into Sum remaining ports
add_line(model, 'a2/1', 'Sum/2');
add_line(model, 'a1/1', 'Sum/3');
add_line(model, 'a0/1', 'Sum/4');

save_system(model);

This Simulink construction directly matches the integrator-chain interpretation in Section 4 and is a faithful implementation of the first-order vector system.

11. Wolfram Mathematica Implementation

Mathematica supports state-space representations via StateSpaceModel. The code below constructs the LTI matrices and computes an output response.

(* Example: y''' + 3 y'' + 3 y' + 1 y = 2 u *)
a = {1, 3, 3};  (* {a0, a1, a2} *)
b = 2;
n = Length[a];

A = ConstantArray[0, {n, n}];
Do[A[[i, i + 1]] = 1, {i, 1, n - 1}];
A[[n, All]] = -a;

B = ConstantArray[0, {n, 1}];
B[[n, 1]] = b;

C = ConstantArray[0, {1, n}];
C[[1, 1]] = 1;

D = {{0}};

sys = StateSpaceModel[{A, B, C, D}];

u[t_] := 1;  (* step input *)
x0 = {0, 0, 0};  (* {y(0), y'(0), y''(0)} *)

(* Output response y(t) over [0,10] *)
resp = OutputResponse[sys, u, {t, 0, 10}, x0];

(* Evaluate at final time *)
N[resp /. t -> 10]

Mathematica can also solve the vector system directly with NDSolve if you prefer explicit ODE form; the state construction from Section 3 gives the system immediately.

12. Problems and Solutions

The problems below reinforce the construction, the initial-condition mapping, and the equivalence proof. All problems are self-contained and use only concepts introduced up to this lesson.

Problem 1 (Second-Order ODE to First-Order Vector Form). Convert the scalar ODE \( \ddot{y}(t) + 4\dot{y}(t) + 5y(t) = 3u(t) \) into a first-order vector system \( \dot{\mathbf{x}}=\mathbf{A}\mathbf{x}+\mathbf{B}u \) with output \( y=x_1 \).

Solution. Let \( x_1=y \), \( x_2=\dot{y} \). Then:

\[ \dot{x}_1 = x_2,\qquad \dot{x}_2 = -5x_1 - 4x_2 + 3u. \]

Thus:

\[ \mathbf{A}=\begin{bmatrix}0 & 1\\ -5 & -4\end{bmatrix},\quad \mathbf{B}=\begin{bmatrix}0\\3\end{bmatrix},\quad \mathbf{C}=\begin{bmatrix}1 & 0\end{bmatrix},\quad \mathbf{D}=[0]. \]


Problem 2 (Nonlinear Example). Consider the nonlinear ODE: \( \ddot{y}(t)= -y(t)^3 + u(t) \). Write the equivalent first-order vector system \( \dot{\mathbf{x}}=\mathbf{f}(t,\mathbf{x},u) \) with output \( y=x_1 \).

Solution. Let \( x_1=y \), \( x_2=\dot{y} \). Then:

\[ \dot{x}_1 = x_2,\qquad \dot{x}_2 = -x_1^3 + u(t). \]

Therefore \( \dot{\mathbf{x}} = [x_2\;\; -x_1^3+u]^\top \) and \( y=x_1 \).


Problem 3 (Recover the Scalar ODE by Elimination). Suppose a state model is given by: \( \dot{x}_1=x_2 \), \( \dot{x}_2=x_3 \), and \( \dot{x}_3=-2x_1-5x_2-4x_3+u(t) \), with \( y=x_1 \). Eliminate the states to obtain a single scalar ODE in \( y \).

Solution. Since \( y=x_1 \), we have:

\[ \dot{y}=\dot{x}_1=x_2,\quad \ddot{y}=\dot{x}_2=x_3,\quad y^{(3)}=\dot{x}_3. \]

Substitute \( x_2=\dot{y} \), \( x_3=\ddot{y} \) into the third equation:

\[ y^{(3)}(t) = -2y(t) - 5\dot{y}(t) - 4\ddot{y}(t) + u(t). \]

Rearranging yields the scalar ODE:

\[ y^{(3)}(t) + 4\ddot{y}(t) + 5\dot{y}(t) + 2y(t) = u(t). \]


Problem 4 (Initial-Condition Mapping). For the third-order scalar ODE \( y^{(3)} + a_2 y^{(2)} + a_1 \dot{y} + a_0 y = b u(t) \), specify precisely how the scalar initial conditions map to the vector initial condition.

Solution. With \( x_1=y \), \( x_2=\dot{y} \), \( x_3=y^{(2)} \), we have:

\[ \mathbf{x}(t_0)=\begin{bmatrix}x_1(t_0)\\x_2(t_0)\\x_3(t_0)\end{bmatrix} = \begin{bmatrix} y(t_0)\\ \dot{y}(t_0)\\ y^{(2)}(t_0) \end{bmatrix}. \]


Problem 5 (Equivalence Proof Detail). In the reverse direction of Theorem 1, justify carefully why \( y^{(k)}(t)=x_{k+1}(t) \) for \( k=1,2,\dots,n-1 \) holds for all \( t \) in the existence interval.

Solution.

Define \( y(t)=x_1(t) \). The state equations give \( \dot{x}_1=x_2 \), hence: \( \dot{y}=\dot{x}_1=x_2 \), so the claim holds for \( k=1 \). Now assume for some \( k \) with \( 1 \le k \le n-2 \) that \( y^{(k)}=x_{k+1} \). Differentiate both sides:

\[ y^{(k+1)}(t)=\frac{d}{dt}x_{k+1}(t)=\dot{x}_{k+1}(t). \]

By the shift relation in the state model, \( \dot{x}_{k+1}=x_{k+2} \), hence \( y^{(k+1)}=x_{k+2} \). By induction, the identity holds for all \( k=1,2,\dots,n-1 \) on the interval where the solution exists.

13. Summary

We converted an n-th order scalar ODE into an equivalent first-order vector system by defining the state as successive derivatives of the scalar variable. We proved exact equivalence (two-way solution correspondence) and established the precise mapping of initial conditions. For the LTI case, we derived explicit matrices \( \mathbf{A},\mathbf{B},\mathbf{C},\mathbf{D} \) and provided consistent implementations in Python, C++, Java, MATLAB/Simulink, and Wolfram Mathematica. This construction is the entry point to systematic state-space modeling and will be refined structurally in the next lesson.

14. References

  1. Kalman, R.E. (1960). On the general theory of control systems. Proceedings of the First International Conference on Automatic Control, 481–492.
  2. Kalman, R.E., & Bucy, R.S. (1961). New results in linear filtering and prediction theory. Journal of Basic Engineering (ASME), 83(1), 95–108.
  3. Gilbert, E.G. (1963). Controllability and observability in multivariable control systems. Journal of the Society for Industrial and Applied Mathematics, Series A: Control, 1(2), 128–151.
  4. Wonham, W.M. (1967). On pole assignment in multi-input controllable linear systems. IEEE Transactions on Automatic Control, 12(6), 660–665.
  5. Rosenbrock, H.H. (1970). State-space and multivariable theory (foundational developments in realization viewpoints). Proceedings/Journal contributions in control theory.
  6. Brockett, R.W. (1972). System theory on group manifolds and coset spaces (foundational system-theoretic viewpoints). SIAM Journal on Control, 10(2), 265–284.
  7. Hautus, M.L.J. (1970). Controllability and observability conditions of linear autonomous systems. Nederl. Akad. Wetensch. Proc. Ser. A, 73, 443–448.
  8. Silverman, L.M. (1969). Inversion of multivariable linear systems. IEEE Transactions on Automatic Control, 14(3), 270–276.