Control Engineering with
Python
Symbols
🐍 |
Code |
🔍 |
Worked Example |
📈 |
Graph |
🧩 |
Exercise |
🏷️ |
Definition |
💻 |
Numerical Method |
💎 |
Theorem |
🧮 |
Analytical Method |
📝 |
Remark |
🧠 |
Theory |
ℹ️ |
Information |
🗝️ |
Hint |
⚠️ |
Warning |
🔓 |
Solution |
🐍 Imports
from numpy import *
from numpy.linalg import *
from scipy.linalg import *
from matplotlib.pyplot import *
from mpl_toolkits.mplot3d import *
from scipy.integrate import solve_ivp
🐍 Streamplot Helper
def Q(f, xs, ys):
X, Y = meshgrid(xs, ys)
v = vectorize
fx = v(lambda x, y: f([x, y])[0])
fy = v(lambda x, y: f([x, y])[1])
return X, Y, fx(X, Y), fy(X, Y)
🧭
We are interested in the behavior of the solution to
First, we study some elementary systems in this class.
📈 Trajectory
a = 2.0; x0 = 1.0
figure()
t = linspace(0.0, 3.0, 1000)
plot(t, exp(a*t)*x0, "k")
xlabel("$t$"); ylabel("$x(t)$"); title(f"$a={a}$")
grid(); axis([0.0, 2.0, 0.0, 10.0])
📈
figure()
plot(real(a), imag(a), "x", color="k")
gca().set_aspect(1.0)
xlim(-3,3); ylim(-3,3);
plot([-3,3], [0,0], "k")
plot([0, 0], [-3, 3], "k")
xticks([-2,-1,0,1,2]); yticks([-2,-1,0,1,2])
title(f"$a={a}$"); grid(True)
📈
a = 1.0; x0 = 1.0
figure()
t = linspace(0.0, 3.0, 1000)
plot(t, exp(a*t)*x0, "k")
xlabel("$t$"); ylabel("$x(t)$"); title(f"$a={a}$")
grid(); axis([0.0, 2.0, 0.0, 10.0])
📈
figure()
plot(real(a), imag(a), "x", color="k")
gca().set_aspect(1.0)
xlim(-3,3); ylim(-3,3);
plot([-3,3], [0,0], "k")
plot([0, 0], [-3, 3], "k")
xticks([-2,-1,0,1,2]); yticks([-2,-1,0,1,2])
title(f"$a={a}$"); grid(True)
📈
a = 0.0; x0 = 1.0
figure()
t = linspace(0.0, 3.0, 1000)
plot(t, exp(a*t)*x0, "k")
xlabel("$t$"); ylabel("$x(t)$"); title(f"$a={a}$")
grid(); axis([0.0, 2.0, 0.0, 10.0])
📈
figure()
plot(real(a), imag(a), "x", color="k")
gca().set_aspect(1.0)
xlim(-3,3); ylim(-3,3);
plot([-3,3], [0,0], "k")
plot([0, 0], [-3, 3], "k")
xticks([-2,-1,0,1,2]); yticks([-2,-1,0,1,2])
title(f"$a={a}$"); grid(True)
📈
a = -1.0; x0 = 1.0
figure()
t = linspace(0.0, 3.0, 1000)
plot(t, exp(a*t)*x0, "k")
xlabel("$t$"); ylabel("$x(t)$"); title(f"$a={a}$")
grid(); axis([0.0, 2.0, 0.0, 10.0])
📈
figure()
plot(real(a), imag(a), "x", color="k")
gca().set_aspect(1.0)
xlim(-3,3); ylim(-3,3);
plot([-3,3], [0,0], "k")
plot([0, 0], [-3, 3], "k")
xticks([-2,-1,0,1,2]); yticks([-2,-1,0,1,2])
title(f"$a={a}$"); grid(True)
📈
a = -2.0; x0 = 1.0
figure()
t = linspace(0.0, 3.0, 1000)
plot(t, exp(a*t)*x0, "k")
xlabel("$t$"); ylabel("$x(t)$"); title(f"$a={a}$")
grid(); axis([0.0, 2.0, 0.0, 10.0])
📈
figure()
plot(real(a), imag(a), "x", color="k")
gca().set_aspect(1.0)
xlim(-3,3); ylim(-3,3);
plot([-3,3], [0,0], "k")
plot([0, 0], [-3, 3], "k")
xticks([-2,-1,0,1,2]); yticks([-2,-1,0,1,2])
title(f"$a={a}$"); grid(True)
🔬 Analysis
The origin is globally asymptotically stable when
i.e. is in the open
left-hand plane.
Let the time constant be
When the system is asymptotically stable,
Quantitative Convergence
controls the speed of
convergence to the origin:
Vector Case, Diagonal, Real-Valued
i.e.
📈
a1 = -1.0; a2 = 2.0; x10 = x20 = 1.0
figure()
t = linspace(0.0, 3.0, 1000)
x1 = exp(a1*t)*x10; x2 = exp(a2*t)*x20
xn = sqrt(x1**2 + x2**2)
plot(t, xn , "k")
plot(t, x1, "k--")
plot(t, x2 , "k--")
xlabel("$t$"); ylabel("$\|x(t)\|$"); title(f"$a_1={a1}, \; a_2={a2}$")
grid(); axis([0.0, 2.0, 0.0, 10.0])
📈
figure()
plot(real(a1), imag(a1), "x", color="k")
plot(real(a2), imag(a2), "x", color="k")
gca().set_aspect(1.0)
xlim(-3,3); ylim(-3,3);
plot([-3,3], [0,0], "k")
plot([0, 0], [-3, 3], "k")
xticks([-2,-1,0,1,2]); yticks([-2,-1,0,1,2])
title(f"$a_1={a1}, \; a_2={a2}$")
grid(True)
📈
a1 = -1.0; a2 = -2.0; x10 = x20 = 1.0
figure()
t = linspace(0.0, 3.0, 1000)
x1 = exp(a1*t)*x10; x2 = exp(a2*t)*x20
xn = sqrt(x1**2 + x2**2)
plot(t, xn , "k")
plot(t, x1, "k--")
plot(t, x2 , "k--")
xlabel("$t$"); ylabel("$\|x(t)\|$"); title(f"$a_1={a1}, \; a_2={a2}$")
grid(); axis([0.0, 2.0, 0.0, 10.0])
📈
figure()
plot(real(a1), imag(a1), "x", color="k")
plot(real(a2), imag(a2), "x", color="k")
gca().set_aspect(1.0)
xlim(-3,3); ylim(-3,3);
plot([-3,3], [0,0], "k")
plot([0, 0], [-3, 3], "k")
xticks([-2,-1,0,1,2]); yticks([-2,-1,0,1,2])
title(f"$a_1={a1}, \; a_2={a2}$")
grid(True)
🔬 Analysis
💎 The rightmost
determines the asymptotic behavior,
💎 The origin is globally asymptotically stable if and only
if
every is in the open
left-hand plane.
Scalar Case, Complex-Valued
, .
🔓 Solution: formally, the same old solution
But now, :
if and
📈
a = 1.0j; x0=1.0
figure()
t = linspace(0.0, 20.0, 1000)
plot(t, real(exp(a*t)*x0), label="$\Re(x(t))$")
plot(t, imag(exp(a*t)*x0), label="$\mathrm{Im}(x(t))$")
xlabel("$t$")
legend(); grid()
fig = figure()
ax = fig.add_subplot(111, projection="3d")
zticks = ax.set_zticks
ax.plot(t, real(exp(a*t)*x0), imag(exp(a*t)*x0))
xticks([0.0, 20.0]); yticks([]); zticks([])
ax.set_xlabel("$t$")
ax.set_ylabel("$\Re(x(t))$")
ax.set_zlabel("$\mathrm{Im}(x(t))$")
📈
figure()
plot(real(a), imag(a), "x", color="k")
gca().set_aspect(1.0)
xlim(-3,3); ylim(-3,3);
plot([-3,3], [0,0], "k")
plot([0, 0], [-3, 3], "k")
xticks([-2,-1,0,1,2]); yticks([-2,-1,0,1,2])
title(f"$a={a}$"); grid(True)
📈
a = -0.5 + 1.0j; x0=1.0
figure()
t = linspace(0.0, 20.0, 1000)
plot(t, real(exp(a*t)*x0), label="$\Re(x(t))$")
plot(t, imag(exp(a*t)*x0), label="$\mathrm{Im}(x(t))$")
xlabel("$t$")
legend(); grid()
📈
fig = figure()
ax = fig.add_subplot(111, projection="3d")
zticks = ax.set_zticks
ax.plot(t, real(exp(a*t)*x0), imag(exp(a*t)*x0))
xticks([0.0, 20.0]); yticks([]); zticks([])
ax.set_xlabel("$t$")
ax.set_ylabel("$\Re(x(t))$")
ax.set_zlabel("$\mathrm{Im}(x(t))$")
📈
figure()
plot(real(a), imag(a), "x", color="k")
gca().set_aspect(1.0)
xlim(-3,3); ylim(-3,3);
plot([-3,3], [0,0], "k")
plot([0, 0], [-3, 3], "k")
xticks([-2,-1,0,1,2]); yticks([-2,-1,0,1,2])
title(f"$a={a}$")
grid(True)
🏷️ Exponential Matrix
If , its exponential is defined as:
⚠️
The exponential of a matrix is
not the matrix with elements (the elementwise
exponential).
1. 🧮
Compute the exponential of .
🗝️ Hint:
2. 🐍 💻 🔬
Compute numerically:
exp(M)
(numpy
)
expm(M)
(scipy.linalg
)
and check the results consistency.
1. 🔓
We have
and hence for any ,
2. 🔓
>>> M = [[0.0, 1.0], [1.0, 0.0]]
>>> exp(M)
array([[1. , 2.71828183],
[2.71828183, 1. ]])
>>> expm(M)
array([[1.54308063, 1.17520119],
[1.17520119, 1.54308063]])
These results are consistent:
>>> array([[exp(0.0), exp(1.0)],
... [exp(1.0), exp(0.0)]])
array([[1. , 2.71828183],
[2.71828183, 1. ]])
>>> array([[cosh(1.0), sinh(1.0)],
... [sinh(1.0), cosh(1.0)]])
array([[1.54308063, 1.17520119],
[1.17520119, 1.54308063]])
💎 Internal Dynamics
The solution of
is
🧩 G.A.S. L.A.
📝 For any dynamical system, if the origin is a globally
asymptotically stable equilibrium, then it is a locally attractive
equilbrium.
💎 For linear systems, the converse result also
holds.
🚀 Let’s prove this!
1. 🧠
Show that for any linear system , if the origin is locally attractive, then it is also
globally attractive.
2. 🧠
Show that linear system , if the origin is globally attractive, then it is also
globally asymptotically stable.
🗝️ Hint: Consider the solutions associated to where is the canonical basis
of the state space.
1. 🔓
If the origin is locally attractive, then there is a such that for any
such that
,
Now, let any . Since the norm of is , and by linearity of , we obtain
Thus the origin is globally attractive.
2. 🔓
Let be a bounded set of
. Since
the solution of , satisfies
Since is bounded, there is a
such that for any
in
,
Since for every ,
,
Finally
Thus when
, uniformly w.r.t.
. In other words, the
origin is globally asymptotically stable.
🏷️ Eigenvalue & Eigenvector
Let . If , and
is an eigenvector of
, is an eigenvalue of
.
The spectrum of is the set of its eigenvalues.
It is characterized by:
🏷️ Modes & Poles
Consider the system .
💎 Stability Criteria
Let .
The origin of is
globally asymptotically stable
all eigenvalues of have a
negative real part.
Why does this criteria work?
Assume that:
(📝 very likely unless has
some special structure.)
Let
There is an invertible matrix such that
Thus, if , is equivalent to
The system is G.A.S. iff each component of the system is, which holds
iff for each
.
🧩 Spring-Mass System
Consider the scalar ODE
1. 🧮
Represent this system as a first-order ODE.
2. 🧠 🧮
Is this system asymptotically stable?
3. 🧠 🧮
Do the solutions have oscillatory components?
Find the set of associated rotational frequencies.
4. 🧠 🧮
Same set of questions (1., 2., 3.) for
when .
2. 🔓
We have
hence the system is not globally asymptotically stable.
3. 🔓
Since
the spectrum of is
The system poles are .
The general solution can be
decomposed as
Thus the components of
oscillate at the rotational frequency
Let . If , then and
Otherwise,
Thus, if ,
and otherwise
In each case, the system is globally asymptotically stable.
If , the poles
are real-valued; the components of the solution do not oscillate.
If , the
imaginary part of the poles is
thus the solution components oscillate at the rotational
frequency
🧩 Integrator Chain
Consider the system
1. 🧮
Compute the solution
when
2. 🦊 🧮
Compute the solution for an arbitrary
3. 🧮
Same questions for the system
for some .
🗝️ Hint: Find the ODE satisfied by .
4. 🧮
Is the system asymptotically stable ?
5. 🧠
Why does the stability analysis of this system matter ?
1. 🔓
Let .
The ODE is
equivalent to:
When ,
yields , then
yields
,
…
yields
Similarly to the previous question, we find that:
And more generally, by linearity:
4. 🔓
The structure of shows
that
If , then
the system is asymptotically stable.
If , then
the system is not.
For example when , we have
5. 🔓
Every square complex matrix ,
even if it is not diagonalizable, can be decomposed
into a block-diagonal matrix where each block has the structure .
Thus, the result of the previous question allows to prove the 💎 Stability Criteria in the general
case.