Skip to content

Commit c2c7381

Browse files
committed
bode_plot: worked on frequency range
1 parent a2c4269 commit c2c7381

2 files changed

Lines changed: 106 additions & 25 deletions

File tree

control/freqplot.py

Lines changed: 47 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -119,48 +119,58 @@ def bode_plot(syslist, omega=None, omega_num=None, dB=None, Hz=None, deg=None,
119119
# If argument was a singleton, turn it into a list
120120
if (not getattr(syslist, '__iter__', False)):
121121
syslist = (syslist,)
122-
123-
mags, phases, omegas = [], [], []
122+
123+
if omega is None:
124+
# Select a default range if none is provided
125+
omega = default_frequency_range(syslist)
126+
elif (isinstance(omega, tuple) or isinstance(omega, list)) and len(omega) == 2:
127+
if omega_num:
128+
omega = sp.logspace(np.log10(omega[0]), np.log10(omega[1]), num=omega_num, endpoint=False)
129+
else:
130+
omega = sp.logspace(np.log10(omega[0]), np.log10(omega[1]), endpoint=False)
131+
132+
mags, phases, omegas, nyquistfrqs = [], [], [], []
124133
for sys in syslist:
125134
if (sys.inputs > 1 or sys.outputs > 1):
126135
#TODO: Add MIMO bode plots.
127136
raise NotImplementedError("Bode is currently only implemented for SISO systems.")
128137
else:
129-
if omega is None:
130-
# Select a default range if none is provided
131-
omega = default_frequency_range(syslist)
132-
elif (isinstance(omega, tuple) or isinstance(omega, list)) and len(omega) == 2:
133-
if omega_num:
134-
omega = sp.logspace(np.log10(omega[0]), np.log10(omega[1]), num=omega_num, endpoint=False)
135-
else:
136-
omega = sp.logspace(np.log10(omega[0]), np.log10(omega[1]), endpoint=False)
137-
138+
omega_sys = np.array(omega)
139+
if sys.isdtime(True):
140+
nyquistfrq = 2. * np.pi * 1./sys.dt / 2.
141+
omega_sys = omega_sys[omega_sys < nyquistfrq]
142+
else:
143+
nyquistfrq = None
138144
# Get the magnitude and phase of the system
139-
omega = np.array(omega)
140-
mag_tmp, phase_tmp, omega_sys = sys.freqresp(omega)
145+
mag_tmp, phase_tmp, omega_sys = sys.freqresp(omega_sys)
141146
mag = np.atleast_1d(np.squeeze(mag_tmp))
142147
phase = np.atleast_1d(np.squeeze(phase_tmp))
143148
phase = unwrap(phase)
149+
nyquistfrq_plot = None
144150
if Hz:
145151
omega_plot = omega_sys / (2. * np.pi)
152+
if nyquistfrq:
153+
nyquistfrq_plot = nyquistfrq / (2. * np.pi)
146154
else:
147155
omega_plot = omega_sys
148156

149157
mags.append(mag)
150158
phases.append(phase)
151159
omegas.append(omega_sys)
160+
nyquistfrqs.append(nyquistfrq)
152161
# Get the dimensions of the current axis, which we will divide up
153162
#! TODO: Not current implemented; just use subplot for now
154163

155164
if (Plot):
156165
# Magnitude plot
157166
ax_mag = plt.subplot(211);
158167
if dB:
159-
plt.semilogx(omega_plot, 20 * np.log10(mag), *args, **kwargs)
168+
pltline = plt.semilogx(omega_plot, 20 * np.log10(mag), *args, **kwargs)
160169
else:
161-
plt.loglog(omega_plot, mag, *args, **kwargs)
170+
pltline = plt.loglog(omega_plot, mag, *args, **kwargs)
162171
plt.hold(True);
163-
172+
if nyquistfrq_plot:
173+
plt.axvline(nyquistfrq_plot, color=pltline[0].get_color())
164174
# Add a grid to the plot + labeling
165175
plt.grid(True)
166176
plt.grid(True, which='minor')
@@ -174,7 +184,8 @@ def bode_plot(syslist, omega=None, omega_num=None, dB=None, Hz=None, deg=None,
174184
phase_plot = phase
175185
plt.semilogx(omega_plot, phase_plot, *args, **kwargs)
176186
plt.hold(True);
177-
187+
if nyquistfrq_plot:
188+
plt.axvline(nyquistfrq_plot, color=pltline[0].get_color())
178189
# Add a grid to the plot + labeling
179190
plt.grid(True)
180191
plt.grid(True, which='minor')
@@ -270,7 +281,7 @@ def nyquist_plot(syslist, omega=None, Plot=True, color='b',
270281
# result to the range [-8, 8].
271282
pow1000 = max(min(get_pow1000(f),8),-8)
272283

273-
# Get the SI prefix.
284+
# Get the SI prefix.
274285
prefix = gen_prefix(pow1000)
275286

276287
# Apply the text. (Use a space before the text to
@@ -381,6 +392,7 @@ def default_frequency_range(syslist, number_of_samples=None):
381392

382393
# Find the list of all poles and zeros in the systems
383394
features = np.array(())
395+
freq_interesting = []
384396

385397
# detect if single sys passed by checking if it is sequence-like
386398
if (not getattr(syslist, '__iter__', False)):
@@ -389,8 +401,15 @@ def default_frequency_range(syslist, number_of_samples=None):
389401
for sys in syslist:
390402
try:
391403
# Add new features to the list
392-
features = np.concatenate((features, np.abs(sys.pole())))
393-
features = np.concatenate((features, np.abs(sys.zero())))
404+
if sys.isctime():
405+
features = np.concatenate((features, np.abs(sys.pole())))
406+
features = np.concatenate((features, np.abs(sys.zero())))
407+
elif sys.isdtime(strict=True):
408+
freq_interesting.append(np.pi*1./sys.dt)
409+
# TODO: how to determine lowest interesting frequency?
410+
else:
411+
pass
412+
# TODO:
394413
except:
395414
pass
396415

@@ -401,17 +420,21 @@ def default_frequency_range(syslist, number_of_samples=None):
401420
if (features.shape[0] == 0): features = [1];
402421

403422
# Take the log of the features
423+
# TODO: in case of Hz==True: wouldn't it make more sense to to set the limits to decades in Hz instead of rad/s?
404424
features = np.log10(features)
425+
lsp_min = np.floor(np.min(features))-1
426+
lsp_max = np.ceil(np.max(features))+1
427+
if freq_interesting:
428+
lsp_min = min(lsp_min, np.log10(min(freq_interesting)))
429+
lsp_max = max(lsp_max, np.log10(max(freq_interesting)))
405430

406431
#! TODO: Add a check in discrete case to make sure we don't get aliasing
407432

408433
# Set the range to be an order of magnitude beyond any features
409434
if number_of_samples:
410-
omega = sp.logspace(np.floor(np.min(features))-1,
411-
np.ceil(np.max(features))+1, num=number_of_samples)
435+
omega = sp.logspace(lsp_min, lsp_max, num=number_of_samples, endpoint=False)
412436
else:
413-
omega = sp.logspace(np.floor(np.min(features))-1,
414-
np.ceil(np.max(features))+1)
437+
omega = sp.logspace(lsp_min, lsp_max, endpoint=False)
415438

416439

417440

control/tests/bode_plot__manualtest.py

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,42 @@ def test__bode_plot_PT1():
1919
w = (2*sp.pi*10.) # 10 Hz
2020
pt1 = control.tf([1.], [1/w, 1.]) # first order system
2121
print(pt1)
22-
pt1_bodeplot = control.bode_plot(pt1, omega=[2*sp.pi*1., 2*sp.pi*1000.], Plot=True, Hz=True, dB=True, color='b')
22+
control.bode_plot(pt1, Plot=True, Hz=True, dB=True, color='b')
23+
24+
plt.figure()
25+
plt.gcf().suptitle(testname)
26+
control.nyquist_plot(pt1)
27+
2328

29+
plt.figure()
30+
plt.gcf().suptitle(testname)
31+
control.gangof4_plot(pt1, pt1)
32+
33+
34+
35+
def test__bode_plot_PT1_3():
36+
testname = 'test__bode_plot_PT1_3'
37+
print ('\n\n{}{}\n'.format(testname, '-'*20))
38+
plt.figure()
39+
plt.gcf().suptitle(testname)
40+
41+
w = (2*sp.pi*10.) # 10 Hz
42+
pt1a = control.tf([1.], [1/w, 1.]) # first order system
43+
control.bode_plot(pt1a, Plot=True, Hz=True, dB=True, color='b')
44+
45+
w = (2*sp.pi*100.) # 100 Hz
46+
pt1b = control.tf([1.], [1/w, 1.]) # first order system
47+
control.bode_plot(pt1b, Plot=True, Hz=True, dB=True, color='b')
48+
49+
w = (2*sp.pi*1000.) # 1000 Hz
50+
pt1c = control.tf([1.], [1/w, 1.]) # first order system
51+
control.bode_plot(pt1c, Plot=True, Hz=True, dB=True, color='b')
52+
53+
print ('\n\n{}{}\n'.format(testname, '-'*20))
54+
plt.figure()
55+
plt.gcf().suptitle(testname)
56+
control.bode_plot([pt1a, pt1b, pt1c], Plot=True, Hz=True, dB=True, color='b')
57+
2458

2559
def test__bode_plot_PT1_sampled():
2660
testname = 'test__bode_plot_PT1_sampled'
@@ -40,9 +74,33 @@ def test__bode_plot_PT1_sampled():
4074
pt1s_bodeplot = control.bode_plot(pt1s, omega=[2*sp.pi*1., 2*sp.pi*500.], omega_num=1000, Plot=True, Hz=True, dB=True, color='r')
4175

4276

77+
78+
def test__bode_plot_PT1_sampled_b():
79+
testname = 'test__bode_plot_PT1_sampled_b'
80+
print ('\n\n{}{}\n'.format(testname, '-'*20))
81+
plt.figure()
82+
plt.gcf().suptitle(testname)
83+
84+
sampleTime = 0.001 # 1ms
85+
w = (2*sp.pi*10.) # 10 Hz
86+
87+
pt1 = control.tf([1.], [1/w, 1.]) # first order system
88+
print(pt1)
89+
pt1_bodeplot = control.bode_plot(pt1, Plot=True, Hz=True, dB=True, color='b')
90+
91+
pt1s = control.sample_system(pt1, sampleTime, 'tustin')
92+
print(pt1s)
93+
pt1s_bodeplot = control.bode_plot(pt1s, omega_num=1000, Plot=True, Hz=True, dB=True, color='r')
94+
95+
96+
97+
98+
4399
if __name__ == '__main__':
44100
test__bode_plot_PT1()
101+
test__bode_plot_PT1_3()
45102
test__bode_plot_PT1_sampled()
103+
test__bode_plot_PT1_sampled_b()
46104
plt.show()
47105

48106

0 commit comments

Comments
 (0)