10000 Merge pull request #196 from murrayrm/fix_ss_fresp_dtime · python-control/python-control@26661f6 · GitHub
[go: up one dir, main page]

Skip to content

Commit 26661f6

Browse files
authored
Merge pull request #196 from murrayrm/fix_ss_fresp_dtime
Fix namespace and logic errrors in statesp.freqresp + unit tests
2 parents 5158a39 + f2af185 commit 26661f6

File tree

2 files changed

+64
-9
lines changed

2 files changed

+64
-9
lines changed

control/statesp.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -459,10 +459,8 @@ def freqresp(self, omega):
459459
if isdtime(self, strict=True):
460460
dt = timebase(self)
461461
cmplx_freqs = exp(1.j * omega * dt)
462-
if ((omega * dt).any() > pi):
463-
warn_message = ("evalfr: frequency evaluation"
464-
" above Nyquist frequency")
465-
warnings.warn(warn_message)
462+
if max(abs(omega)) * dt > math.pi:
463+
warn("freqresp: frequency evaluation above Nyquist frequency")
466464
else:
467465
cmplx_freqs = omega * 1.j
468466

control/tests/freqresp_test.py

Lines changed: 62 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@
1010
import numpy as np
1111
import control as ctrl
1212
from control.statesp import StateSpace
13-
from control.matlab import ss, tf, bode
13+
from control.xferfcn import TransferFunction
14+
from control.matlab import ss, tf, bode, rss
1415
from control.exception import slycot_check
1516
import matplotlib.pyplot as plt
1617

@@ -45,7 +46,7 @@ def test_superimpose(self):
4546
ctrl.bode_plot(ctrl.tf([5], [1, 1]))
4647

4748
# Check to make sure there are two axes and that each axes has two lines
48-
assert len(plt.gcf().axes) == 2
49+
self.assertEqual(len(plt.gcf().axes), 2)
4950
for ax in plt.gcf().axes:
5051
# Make sure there are 2 lines in each subplot
5152
assert len(ax.get_lines()) == 2
@@ -55,7 +56,7 @@ def test_superimpose(self):
5556
ctrl.bode_plot([ctrl.tf([1], [1,2,1]), ctrl.tf([5], [1, 1])])
5657

5758
# Check to make sure there are two axes and that each axes has two lines
58-
assert len(plt.gcf().axes) == 2
59+
self.assertEqual(len(plt.gcf().axes), 2)
5960
for ax in plt.gcf().axes:
6061
# Make sure there are 2 lines in each subplot
6162
assert len(ax.get_lines()) == 2
@@ -67,7 +68,7 @@ def test_superimpose(self):
6768
ctrl.bode_plot(ctrl.tf([5], [1, 1]))
6869

6970
# Check to make sure there are two axes and that each axes has one line
70-
assert len(plt.gcf().axes) == 2
71+
self.assertEqual(len(plt.gcf().axes), 2)
7172
for ax in plt.gcf().axes:
7273
# Make sure there is only 1 line in the subplot
7374
assert len(ax.get_lines()) == 1
@@ -77,7 +78,7 @@ def test_superimpose(self):
7778
if ax.get_label() == 'control-bode-magnitude':
7879
break
7980
ax.semilogx([1e-2, 1e1], 20 * np.log10([1, 1]), 'k-')
80-
assert len(ax.get_lines()) == 2
81+
self.assertEqual(len(ax.get_lines()), 2)
8182

8283
def test_doubleint(self):
8384
# 30 May 2016, RMM: added to replicate typecast bug in freqresp.py
@@ -107,6 +108,62 @@ def test_mimo(self):
107108
#plt.figure(4)
108109
#bode(sysMIMO,self.omega)
109110

111+
def test_discrete(self):
112+
# Test discrete time frequency response
113+
114+
# SISO state space systems with either fixed or unspecified sampling times
115+
sys = rss(3, 1, 1)
116+
siso_ss1d = StateSpace(sys.A, sys.B, sys.C, sys.D, 0.1)
117+
siso_ss2d = StateSpace(sys.A, sys.B, sys.C, sys.D, True)
118+
119+
# MIMO state space systems with either fixed or unspecified sampling times
120+
A = [[-3., 4., 2.], [-1., -3., 0.], [2., 5., 3.]]
121+
B = [[1., 4.], [-3., -3.], [-2., 1.]]
122+
C = [[4., 2., -3.], [1., 4., 3.]]
123+
D = [[-2., 4.], [0., 1.]]
124+
mimo_ss1d = StateSpace(A, B, C, D, 0.1)
125+
mimo_ss2d = StateSpace(A, B, C, D, True)
126+
127+
# SISO transfer functions
128+
siso_tf1d = TransferFunction([1, 1], [1, 2, 1], 0.1)
129+
siso_tf2d = TransferFunction([1, 1], [1, 2, 1], True)
130+
131+
# Go through each system and call the code, checking return types
132+
for sys in (siso_ss1d, siso_ss2d, mimo_ss1d, mimo_ss2d,
133+
siso_tf1d, siso_tf2d):
134+
# Set frequency range to just below Nyquist freq (for Bode)
135+
omega_ok = np.linspace(10e-4,0.99,100) * np.pi/sys.dt
136+
137+
# Test frequency response
138+
ret = sys.freqresp(omega_ok)
139+
140+
# Check for warning if frequency is out of range
141+
import warnings
142+
warnings.simplefilter('always', UserWarning) # don't supress
143+
with warnings.catch_warnings(record=True) as w:
144+
omega_bad = np.linspace(10e-4,1.1,10) * np.pi/sys.dt
145+
ret = sys.freqresp(omega_bad)
146+
print("len(w) =", len(w))
147+
self.assertEqual(len(w), 1)
148+
self.assertIn("above", str(w[-1].message))
149+
self.assertIn("Nyquist", str(w[-1].message))
150+
151+
# Test bode plots (currently only implemented for SISO)
152+
if (sys.inputs == 1 and sys.outputs == 1):
153+
# Generic call (frequency range calculated automatically)
154+
ret_ss = bode(sys)
155+
156+
# Convert to transfer function and test bode again
157+
systf = tf(sys);
158+
ret_tf = bode(systf)
159+
160+
# Make sure we can pass a frequency range
161+
bode(sys, omega_ok)
162+
163+
else:
164+
# Calling bode should generate a not implemented error
165+
self.assertRaises(NotImplementedError, bode, (sys,))
166+
110167
def suite():
111168
return unittest.TestLoader().loadTestsFromTestCase(TestTimeresp)
112169

0 commit comments

Comments
 (0)
0