Skip to content

Commit b41fe06

Browse files
committed
SISO and MIMO mixed-sensitivity synthesis examples
Both examples taken from Skogestad and Postlethwaite's Multivariable Feedback Control.
1 parent cae1abe commit b41fe06

File tree

2 files changed

+282
-0
lines changed

2 files changed

+282
-0
lines changed

examples/robust_mimo.py

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
"""Demonstrate mixed-sensitivity H-infinity design for a MIMO plant.
2+
3+
Based on Example 3.8 from Multivariable Feedback Control, Skogestad
4+
and Postlethwaite, 1st Edition.
5+
"""
6+
7+
import numpy as np
8+
import matplotlib.pyplot as plt
9+
10+
from control import tf, ss, mixsyn, feedback, step_response
11+
12+
def weighting(wb,m,a):
13+
"""weighting(wb,m,a) -> wf
14+
wb - design frequency (where |wf| is approximately 1)
15+
m - high frequency gain of 1/wf; should be > 1
16+
a - low frequency gain of 1/wf; should be < 1
17+
wf - SISO LTI object
18+
"""
19+
s = tf([1,0],[1])
20+
return (s/m+wb)/(s+wb*a)
21+
22+
23+
def plant():
24+
"""plant() -> g
25+
g - LTI object; 2x2 plant with a RHP zero, at s=0.5.
26+
"""
27+
den = [0.2,1.2,1]
28+
gtf=tf([[[1],[1]],
29+
[[2,1],[2]]],
30+
[[den,den],
31+
[den,den]])
32+
return ss(gtf)
33+
34+
35+
# as of this writing (2017-07-01), python-control doesn't have an
36+
# equivalent to Matlab's sigma function, so use a trivial stand-in.
37+
def triv_sigma(g,w):
38+
"""triv_sigma(g,w) -> s
39+
g - LTI object, order n
40+
w - frequencies, length m
41+
s - (m,n) array of singular values of g(1j*w)"""
42+
m,p,_ = g.freqresp(w)
43+
sjw = (m * np.exp(1j*p*np.pi/180)).transpose(2,0,1)
44+
sv = np.linalg.svd(sjw,compute_uv=False)
45+
return sv
46+
47+
48+
def analysis():
49+
"""Plot open-loop responses for various inputs"""
50+
g=plant()
51+
52+
t = np.linspace(0,10,101)
53+
_, yu1 = step_response(g,t,input=0)
54+
_, yu2 = step_response(g,t,input=1)
55+
56+
yu1 = yu1
57+
yu2 = yu2
58+
59+
# linear system, so scale and sum previous results to get the
60+
# [1,-1] response
61+
yuz = yu1 - yu2
62+
63+
plt.figure(1)
64+
plt.subplot(1,3,1)
65+
plt.plot(t,yu1[0],label='$y_1$')
66+
plt.plot(t,yu1[1],label='$y_2$')
67+
plt.xlabel('time')
68+
plt.ylabel('output')
69+
plt.ylim([-1.1,2.1])
70+
plt.legend()
71+
plt.title('o/l response to input [1,0]')
72+
73+
plt.subplot(1,3,2)
74+
plt.plot(t,yu2[0],label='$y_1$')
75+
plt.plot(t,yu2[1],label='$y_2$')
76+
plt.xlabel('time')
77+
plt.ylabel('output')
78+
plt.ylim([-1.1,2.1])
79+
plt.legend()
80+
plt.title('o/l response to input [0,1]')
81+
82+
plt.subplot(1,3,3)
83+
plt.plot(t,yuz[0],label='$y_1$')
84+
plt.plot(t,yuz[1],label='$y_2$')
85+
plt.xlabel('time')
86+
plt.ylabel('output')
87+
plt.ylim([-1.1,2.1])
88+
plt.legend()
89+
plt.title('o/l response to input [1,-1]')
90+
91+
92+
def synth(wb1,wb2):
93+
"""synth(wb1,wb2) -> k,gamma
94+
wb1: S weighting frequency
95+
wb2: KS weighting frequency
96+
k: controller
97+
gamma: H-infinity norm of 'design', that is, of evaluation system
98+
with loop closed through design
99+
"""
100+
g = plant()
101+
wu = ss([],[],[],np.eye(2))
102+
wp1 = ss(weighting(wb=wb1, m=1.5, a=1e-4))
103+
wp2 = ss(weighting(wb=wb2, m=1.5, a=1e-4))
104+
wp = wp1.append(wp2)
105+
k,_,info = mixsyn(g,wp,wu)
106+
return k, info.gamma
107+
108+
109+
def step_opposite(g,t):
110+
"""reponse to step of [-1,1]"""
111+
_, yu1 = step_response(g,t,input=0)
112+
_, yu2 = step_response(g,t,input=1)
113+
return yu1 - yu2
114+
115+
116+
def design():
117+
"""Show results of designs"""
118+
# equal weighting on each output
119+
k1, gam1 = synth(0.25,0.25)
120+
# increase "bandwidth" of output 2 by moving crossover weighting frequency 100 times higher
121+
k2, gam2 = synth(0.25,25)
122+
# now weight output 1 more heavily
123+
# won't plot this one, just want gamma
124+
_, gam3 = synth(25,0.25)
125+
126+
print('design 1 gamma {:.3g} (Skogestad: 2.80)'.format(gam1))
127+
print('design 2 gamma {:.3g} (Skogestad: 2.92)'.format(gam2))
128+
print('design 3 gamma {:.3g} (Skogestad: 6.73)'.format(gam3))
129+
130+
# do the designs
131+
g = plant()
132+
w = np.logspace(-2,2,101)
133+
I = ss([],[],[],np.eye(2))
134+
s1 = I.feedback(g*k1)
135+
s2 = I.feedback(g*k2)
136+
137+
# frequency response
138+
sv1 = triv_sigma(s1,w)
139+
sv2 = triv_sigma(s2,w)
140+
141+
plt.figure(2)
142+
143+
plt.subplot(1,2,1)
144+
plt.semilogx(w, 20*np.log10(sv1[:,0]), label=r'$\sigma_1(S_1)$')
145+
plt.semilogx(w, 20*np.log10(sv1[:,1]), label=r'$\sigma_2(S_1)$')
146+
plt.semilogx(w, 20*np.log10(sv2[:,0]), label=r'$\sigma_1(S_2)$')
147+
plt.semilogx(w, 20*np.log10(sv2[:,1]), label=r'$\sigma_2(S_2)$')
148+
plt.ylim([-60,10])
149+
plt.ylabel('magnitude [dB]')
150+
plt.xlim([1e-2,1e2])
151+
plt.xlabel('freq [rad/s]')
152+
plt.legend()
153+
plt.title('Singular values of S')
154+
155+
# time response
156+
157+
# in design 1, both outputs have an inverse initial response; in
158+
# design 2, output 2 does not, and is very fast, while output 1
159+
# has a larger initial inverse response than in design 1
160+
time = np.linspace(0,10,301)
161+
t1 = (g*k1).feedback(I)
162+
t2 = (g*k2).feedback(I)
163+
164+
y1 = step_opposite(t1,time)
165+
y2 = step_opposite(t2,time)
166+
167+
plt.subplot(1,2,2)
168+
plt.plot(time, y1[0], label='des. 1 $y_1(t))$')
169+
plt.plot(time, y1[1], label='des. 1 $y_2(t))$')
170+
plt.plot(time, y2[0], label='des. 2 $y_1(t))$')
171+
plt.plot(time, y2[1], label='des. 2 $y_2(t))$')
172+
plt.xlabel('time [s]')
173+
plt.ylabel('response [1]')
174+
plt.legend()
175+
plt.title('c/l response to reference [1,-1]')
176+
177+
178+
analysis()
179+
design()
180+
plt.show()

examples/robust_siso.py

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
"""Demonstrate mixed-sensitivity H-infinity design for a SISO plant.
2+
3+
Based on Example 2.11 from Multivariable Feedback Control, Skogestad
4+
and Postlethwaite, 1st Edition.
5+
"""
6+
7+
import numpy as np
8+
import matplotlib.pyplot as plt
9+
10+
from control import tf, ss, mixsyn, feedback, step_response
11+
12+
s = tf([1, 0], 1)
13+
# the plant
14+
g = 200/(10*s+1)/(0.05*s+1)**2
15+
# disturbance plant
16+
gd = 100/(10*s+1)
17+
18+
# first design
19+
# sensitivity weighting
20+
M = 1.5
21+
wb = 10
22+
A = 1e-4
23+
ws1 = (s/M+wb)/(s+wb*A)
24+
# KS weighting
25+
wu = tf(1, 1)
26+
27+
k1, cl1, info1 = mixsyn(g, ws1, wu)
28+
29+
# sensitivity (S) and complementary sensitivity (T) functions for
30+
# design 1
31+
s1 = feedback(1,g*k1)
32+
t1 = feedback(g*k1,1)
33+
34+
# second design
35+
# this weighting differs from the text, where A**0.5 is used; if you use that,
36+
# the frequency response doesn't match the figure. The time responses
37+
# are similar, though.
38+
ws2 = (s/M**0.5+wb)**2/(s+wb*A)**2
39+
# the KS weighting is the same as for the first design
40+
41+
k2, cl2, info2 = mixsyn(g, ws2, wu)
42+
43+
# S and T for design 2
44+
s2 = feedback(1,g*k2)
45+
t2 = feedback(g*k2,1)
46+
47+
# frequency response
48+
omega = np.logspace(-2,2,101)
49+
ws1mag,_,_ = ws1.freqresp(omega)
50+
s1mag,_,_ = s1.freqresp(omega)
51+
ws2mag,_,_ = ws2.freqresp(omega)
52+
s2mag,_,_ = s2.freqresp(omega)
53+
54+
plt.figure(1)
55+
# text uses log-scaled absolute, but dB are probably more familiar to most control engineers
56+
plt.semilogx(omega,20*np.log10(s1mag.flat),label='$S_1$')
57+
plt.semilogx(omega,20*np.log10(s2mag.flat),label='$S_2$')
58+
# -1 in logspace is inverse
59+
plt.semilogx(omega,-20*np.log10(ws1mag.flat),label='$1/w_{P1}$')
60+
plt.semilogx(omega,-20*np.log10(ws2mag.flat),label='$1/w_{P2}$')
61+
62+
plt.ylim([-80,10])
63+
plt.xlim([1e-2,1e2])
64+
plt.xlabel('freq [rad/s]')
65+
plt.ylabel('mag [dB]')
66+
plt.legend()
67+
plt.title('Sensitivity and sensitivity weighting frequency responses')
68+
69+
# time response
70+
time = np.linspace(0,3,201)
71+
_,y1 = step_response(t1, time)
72+
_,y2 = step_response(t2, time)
73+
74+
# gd injects into the output (that is, g and gd are summed), and the
75+
# closed loop mapping from output disturbance->output is S.
76+
_,y1d = step_response(s1*gd, time)
77+
_,y2d = step_response(s2*gd, time)
78+
79+
plt.figure(2)
80+
plt.subplot(1,2,1)
81+
plt.plot(time,y1,label='$y_1(t)$')
82+
plt.plot(time,y2,label='$y_2(t)$')
83+
84+
plt.ylim([-0.1,1.5])
85+
plt.xlim([0,3])
86+
plt.xlabel('time [s]')
87+
plt.ylabel('signal [1]')
88+
plt.legend()
89+
plt.title('Tracking response')
90+
91+
plt.subplot(1,2,2)
92+
plt.plot(time,y1d,label='$y_1(t)$')
93+
plt.plot(time,y2d,label='$y_2(t)$')
94+
95+
plt.ylim([-0.1,1.5])
96+
plt.xlim([0,3])
97+
plt.xlabel('time [s]')
98+
plt.ylabel('signal [1]')
99+
plt.legend()
100+
plt.title('Disturbance response')
101+
102+
plt.show()

0 commit comments

Comments
 (0)