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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy