-
Notifications
You must be signed in to change notification settings - Fork 88
/
siso_controllability.py
164 lines (134 loc) · 5.37 KB
/
siso_controllability.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
"""
Created on 22 Mar 2013
@author: St Elmo Wilken
"""
from __future__ import print_function
import control as cn
import numpy as np
import matplotlib.pyplot as plt
"""
This module checks if your plant conforms to the SISO controllability rules
as explained in Skogestad chapter 5.14.
"""
"""
Rule 1 and 2: |e| < 1 for acceptable control given feedback
Rule 1: Speed of response to reject disturbances.
How fast must the plant be to be able to reject disturbances?
e = S*Gd*d
If you require |e| < 1 and given the worst case disturbance i.e. |d| = 1
then |S*Gd| < 1 (assuming feedback control is used).
Rule 2: Speed of response to track reference changes.
Similar to rule 1 but this time you want |S| < 1\R where
R is the max allowed reference change relative to the allowed error.
"""
def rule_one_two(S, Gd, R=1.1, freq=np.arange(0.001, 1, 0.001)):
"""
Parameters: S => sensitivity transfer function
Gd => transfer function of the disturbance
w => frequency range (mostly there to make the plots look nice
You want the red dashed line above the blue solid line for adequate
disturbance rejection.
"""
mag_s, phase_s, freq_s = cn.bode_plot(S, omega=freq)
plt.clf()
inv_gd = 1/Gd
mag_i, phase_i, freq_i = cn.bode_plot(inv_gd, omega=freq)
plt.clf()
unity = [1] * len(freq)
inv_R = [1.0/R] * len(freq)
plt.xlabel("Frequency [rad/s]")
plt.ylabel("Magnitude")
"""
Rule 3 and 4: |u| < 1 to ensure your input is in the allowed range
given that |e| < 1
Rule 3: Input constraints arising from disturbances.
|G| > |Gd| - 1 for acceptable control where |Gd| > 1
Rule 4: Input constraints arising from set points.
|G| > R - 1 for acceptable control
"""
def rule_three_four(G, Gd, R=1.1, perfect=True,
freq=np.arange(0.001, 1, 0.001)):
"""
Parameters: G => transfer function of system
Gd => transfer function of disturbances on system
R => max allowed reference change relative to e (R > 1)
perfect => Boolean: True then assumes perfect control
"""
mag_g, phase_g, freq_g = cn.bode_plot(G, omega=freq)
plt.clf()
mag_gd, phase_gd, freq_gd = cn.bode_plot(Gd, omega=freq)
plt.clf()
plt.subplot(211)
if perfect:
plt.loglog(freq_gd, mag_g, color="red", label="|G|", ls="--")
plt.loglog(freq_gd, mag_gd, color="blue", label="|Gd|")
else:
gd_min = mag_gd - 1
gd_min = [x for x in gd_min if x > 0]
freq_all = [
freq_gd[x] for x in range(len(freq_gd)) if (mag_gd[x]-1) > 0]
mag_g_all = [
mag_g[x] for x in range(len(freq_gd)) if (mag_gd[x]-1) > 0]
plt.loglog(freq_all, gd_min, color="blue", label="|Gd| - 1")
plt.loglog(freq_all, mag_g_all, color="red", label="|G|", ls="--")
plt.legend(loc=3)
plt.grid()
plt.ylabel("Magnitude")
plt.xlabel("Frequency [rad/s]")
plt.subplot(212)
plt.loglog(freq_g, mag_g, color="red", label="|G|", ls="--")
if perfect:
R_stretch = len(freq_g)*[np.abs(R)]
plt.loglog(freq_g, R_stretch, color="blue", label="|R|")
else:
R_stretch = np.subtract(len(freq_g)*[np.abs(R)], [1]*len(freq_g))
plt.loglog(freq_g, R_stretch, color="blue", label="|R|-1")
plt.legend(loc=1)
plt.grid()
plt.ylabel("Magnitude")
plt.xlabel("Frequency [rad/s]")
"""
Rules 5 - 8:
These are more like bounds on the bandwidth given certain characteristics of
the system.
They do depend on the bound of either S or T...
(S for zeros, T for poles via internal stability)
The important thing is really just Theorem 5.3 + the interpolation
constraints... everything else follows from that.
"""
"""
This method will just show the intersection of the dead time frequency
with the loop gain magnitude (forms part of rule 1 and rule 5).
Effectively what you want here is to know if:
|Gd(w_theta)| < 1 where w_theta = 1/(dead time)
"""
def dead_time_bound(L, Gd, deadtime, freq=np.arange(0.001, 1, 0.001)):
"""
Parameters: L => the loop transfer function
Gd => Disturbance transfer function
deadtime => the deadtime in seconds of the system
Notes: If the cross over frequencies are very large or very small
you will have to find them yourself.
"""
mag, phase, omega = cn.bode_plot(L, omega=freq)
mag_d, phase_d, omega_d = cn.bode_plot(Gd, omega=freq)
plt.clf()
gm, pm, wg, wp_L = cn.margin(mag, phase, omega)
gm, pm, wg, wp_Gd = cn.margin(mag_d, phase_d, omega_d)
freq_lim = [freq[x] for x in range(len(freq)) if 0.1 < mag[x] < 10]
mag_lim = [mag[x] for x in range(len(freq)) if 0.1 < mag[x] < 10]
plt.loglog(freq_lim, mag_lim, color="blue", label="|L|")
dead_w = 1.0/deadtime
ymin, ymax = plt.ylim()
plt.loglog([dead_w, dead_w], [ymin, ymax], color="red", ls="--",
label="dead time frequency")
plt.loglog([wp_L, wp_L], [ymin, ymax], color="green", ls=":",
label="w_c")
plt.loglog([wp_Gd, wp_Gd], [ymin, ymax], color="black", ls="--",
label=" w_d")
print("You require feedback for disturbance rejection up to (w_d) = " +
str(wp_Gd) +
"\n Remember than w_B < w_c < W_BT and w_d < w_B hence w_d < w_c.")
print("The upper bound on w_c based on the dead time\
(wc < w_dead = 1/dead_seconds) = " + str(1.0/deadtime))
plt.legend(loc=3)