In this Python and control engineering tutorial, we will learn one very important scientific computing technique that can be used to implement classical and advanced control and estimation algorithms. We will learn how to automatize the linearization of nonlinear systems in Python by using Python’s symbolic computation library called SymPy. The method presented in this video tutorial is very important for designing controllers and estimators for high-order nonlinear dynamical systems where it is practically impossible to perform linearization by hand. Before reading this tutorial it is a very good idea to get yourself familiar with the basics of linearization of nonlinear systems. Consequently, we strongly suggest you thoroughly study our tutorial on the linearization of nonlinear systems given here. The YouTube video accompanying this post is given here.
Test Case 1: Linearization of Nonlinear Pendulum Equations
This is our first test case for testing our approach for automatic differentiation and linearization. In our previous tutorial given here, we derived a nonlinear state-space model of a pendulum system. This is the classical system for testing linear and nonlinear control engineering algorithms. The pendulum is shown in the figure below.
In the figure above, is the length of the pendulum, is the mass of the ball, is the gravitational acceleration constant, is the angle of the pendulum, and is the control force. By using the state-space variables:
(1)
where is the angular velocity, we obtain the nonlinear pendulum state-space model of the pendulum
(2)
where is the control input. To derive this state-space model, we assumed that the control force is . Our state space model can be written in the standard form
(3)
where the state vector is
(4)
and the vector function is defined by
(5)
where
(6)
To linearize the state-space model, we need to compute the Jacobian matrices of the nonlinear vector function with respect to the state vector and the control input .
The state Jacobian matrix is defined by
(7)
The input Jacobian matrix is defined by
(8)
To linearize the model, we need to evaluate the Jacobian matrices at the selected linearization vectors (points) that are usually denoted with the “star” superscript. We select the following state and input linearization points
(9)
Consequently, we obtain the following A and B matrices
(10)
The final linearized model is given by
(11)
where the new state vector represents the linearized state in the relative coordinates
(12)
and the scalar is the relative input
(13)
The state-space model (11) can be represented in the compact form given below
(14)
where
(15)
Next, we present the Python script for computing the linearization. The Python script is given below.
# -*- coding: utf-8 -*-
"""
Symbolic linarization of nonlinear state-space models
"""
import numpy as np
from sympy import *
# symbolic state vector
x = MatrixSymbol('x',2,1)
# symbolic input
u=symbols('u')
# symbolic constants
g,l,m = symbols('g,l,m')
# define the nonlinear state equation
stateFunction=Matrix([[x[1]],[-(g/l)*sin(x[0])+(1/(m*l))*(u**2)]])
# compute the Jacobians
JacobianState= stateFunction.jacobian(x)
JacobianInput= stateFunction.jacobian([u])
# linearization points
statePoint=np.array([[0],[0]])
inputPoint=1
# first approach, using subs
Amatrix=JacobianState.subs(x, Matrix(statePoint))
Bmatrix=JacobianInput.subs(u, inputPoint)
# we can also substitute the constants
Amatrix=Amatrix.subs({g:9.81,l:1})
Bmatrix=Bmatrix.subs({l:1,m:1})
First, we define the symbolic vector “x” consisting of state variables. Then, we define the symbolic input variable “u”, and the symbolic variables “g”, “l”, and “m”. Then, we define the symbolic state equation “stateFunction”. We use the SymPy function jacobian() to compute the Jacobians with respect to state and control inputs. The output of the “jacobian()” function is the symbolic expression. Then, we define the linearization points. There are two approaches for computing the matrices and . The above-presented script shows the approach that is based on the function “subs()”. We use the function “subs()” to substitute symbolic variables in symbolic expressions with the numerical values. The second approach for computing the matrices A and B is shown below.
# second approach, using lambdify
JacobianState2=JacobianState.subs({g:9.81,l:1})
JacobianInput2=JacobianInput.subs({l:1,m:1})
AmatrixFunction=lambdify(x,JacobianState2)
BmatrixFunction=lambdify(u,JacobianInput2)
Amatrix2=AmatrixFunction(statePoint)
Bmatrix2=BmatrixFunction(inputPoint)
We use the SymPy “lambdify()” function. This function creates the classical Python function out of symbolic expressions. The “lambdify()” function returns the Python function whose argument is a specified symbolic variable. For example, the code line “AmatrixFunction=lambdify(x,JacobianState2)”, returns the function “AmatrixFunction”. The input argument of the “lambdify()” is the symbolic variable “x” which will be the input argument of the output function “AmatrixFunction”, and the second argument of “lambdify()” is the symbolic expression.
Test Case 2: Third Order Nonlinear System
As the second test case, we consider the third-order nonlinear system given below
(16)
This state space model can be represented in the vector form
(17)
where
(18)
and where
(19)
Let us first analytically compute the Jacobian matrices, such that we can test the Python implementation
(20)
The input Jacobian matrix is defined by
(21)
We select the following state and input linearization points as follows
(22)
Consequently, we obtain the following A and B matrices
(23)
The code given below is used to linearize the system in Python.
"""
Symbolic linarization of nonlinear state-space models
@author: aleks
"""
import numpy as np
from sympy import *
# symbolic state vector
x = MatrixSymbol('x',3,1)
# symbolic input
u=symbols('u')
# define the nonlinear state equation
stateFunction=Matrix([[x[1]],[x[2]],[-x[0]**2+sin(x[1])+x[1]*x[2]+u]])
# compute the Jacobians
JacobianState= stateFunction.jacobian(x)
JacobianInput= stateFunction.jacobian([u])
# linearization points
statePoint=np.array([[1],[0],[1]])
inputPoint=0
AmatrixFunction=lambdify(x,JacobianState)
BmatrixFunction=lambdify(u,JacobianInput)
Amatrix=AmatrixFunction(statePoint)
Bmatrix=BmatrixFunction(inputPoint)