Skip to content

Commit e5360dc

Browse files
committed
refactoring of nyquist into response/plot + updated unit tests, examples
1 parent d07422d commit e5360dc

9 files changed

Lines changed: 639 additions & 365 deletions

File tree

control/ctrlutil.py

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -86,18 +86,9 @@ def unwrap(angle, period=2*math.pi):
8686
return angle
8787

8888
def issys(obj):
89-
"""Return True if an object is a Linear Time Invariant (LTI) system,
90-
otherwise False.
89+
"""Deprecated function to check if an object is an LTI system.
9190
92-
Examples
93-
--------
94-
>>> G = ct.tf([1], [1, 1])
95-
>>> ct.issys(G)
96-
True
97-
98-
>>> K = np.array([[1, 1]])
99-
>>> ct.issys(K)
100-
False
91+
Use isinstance(obj, ct.LTI)
10192
10293
"""
10394
warnings.warn("issys() is deprecated; use isinstance(obj, ct.LTI)",

control/descfcn.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
import scipy
1919
from warnings import warn
2020

21-
from .freqplot import nyquist_plot
21+
from .freqplot import nyquist_response
2222

2323
__all__ = ['describing_function', 'describing_function_plot',
2424
'DescribingFunctionNonlinearity', 'friction_backlash_nonlinearity',
@@ -259,10 +259,11 @@ def describing_function_plot(
259259
warn = omega is None
260260

261261
# Start by drawing a Nyquist curve
262-
count, contour = nyquist_plot(
263-
H, omega, plot=True, return_contour=True,
264-
warn_encirclements=warn, warn_nyquist=warn, **kwargs)
265-
H_omega, H_vals = contour.imag, H(contour)
262+
response = nyquist_response(
263+
H, omega, warn_encirclements=warn, warn_nyquist=warn,
264+
check_kwargs=False, **kwargs)
265+
response.plot(**kwargs)
266+
H_omega, H_vals = response.contour.imag, H(response.contour)
266267

267268
# Compute the describing function
268269
df = describing_function(F, A)

control/freqplot.py

Lines changed: 497 additions & 251 deletions
Large diffs are not rendered by default.

control/lti.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
from .iosys import InputOutputSystem
1414

1515
__all__ = ['poles', 'zeros', 'damp', 'evalfr', 'frequency_response',
16-
'freqresp', 'dcgain', 'bandwidth']
16+
'freqresp', 'dcgain', 'bandwidth', 'LTI']
1717

1818

1919
class LTI(InputOutputSystem):
@@ -466,10 +466,8 @@ def frequency_response(
466466
if sys_.isdtime(strict=True):
467467
nyquistfrq = math.pi / sys_.dt
468468
if not omega_range_given:
469-
# limit up to and including nyquist frequency
470-
# TODO: make this optional?
471-
omega_sys = np.hstack((
472-
omega_sys[omega_sys < nyquistfrq], nyquistfrq))
469+
# Limit up to the Nyquist frequency
470+
omega_sys = omega_sys[omega_sys < nyquistfrq]
473471

474472
# Compute the frequency response
475473
responses.append(sys_.frequency_response(omega_sys, squeeze=squeeze))

control/matlab/wrappers.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ def bode(*args, **kwargs):
9090
return retval
9191

9292

93-
def nyquist(*args, **kwargs):
93+
def nyquist(*args, plot=True, **kwargs):
9494
"""nyquist(syslist[, omega])
9595
9696
Nyquist plot of the frequency response.
@@ -114,7 +114,7 @@ def nyquist(*args, **kwargs):
114114
frequencies in rad/s
115115
116116
"""
117-
from ..freqplot import nyquist_plot
117+
from ..freqplot import nyquist_response, nyquist_plot
118118

119119
# If first argument is a list, assume python-control calling format
120120
if hasattr(args[0], '__iter__'):
@@ -125,8 +125,10 @@ def nyquist(*args, **kwargs):
125125
kwargs.update(other)
126126

127127
# Call the nyquist command
128-
kwargs['return_contour'] = True
129-
_, contour = nyquist_plot(syslist, omega, *args, **kwargs)
128+
response = nyquist_response(syslist, omega, *args, **kwargs)
129+
contour = response.contour
130+
if plot:
131+
nyquist_plot(response, *args, **kwargs)
130132

131133
# Create the MATLAB output arguments
132134
freqresp = syslist(contour)

control/tests/kwargs_test.py

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,8 @@ def test_kwarg_search(module, prefix):
110110
(lambda x, u, params: None, lambda zflag, params: None), {}),
111111
(control.InputOutputSystem, 0, 0, (),
112112
{'inputs': 1, 'outputs': 1, 'states': 1}),
113+
(control.LTI, 0, 0, (),
114+
{'inputs': 1, 'outputs': 1, 'states': 1}),
113115
(control.flatsys.LinearFlatSystem, 1, 0, (), {}),
114116
(control.NonlinearIOSystem.linearize, 1, 0, (0, 0), {}),
115117
(control.StateSpace.sample, 1, 0, (0.1,), {}),
@@ -156,26 +158,32 @@ def test_matplotlib_kwargs(function, nsysargs, moreargs, kwargs, mplcleanup):
156158
function(*args, **kwargs)
157159

158160
# Now add an unrecognized keyword and make sure there is an error
159-
with pytest.raises(AttributeError,
160-
match="(has no property|unexpected keyword)"):
161+
with pytest.raises(
162+
(AttributeError, TypeError),
163+
match="(has no property|unexpected keyword|unrecognized keyword)"):
161164
function(*args, **kwargs, unknown=None)
162165

163166

164167
@pytest.mark.parametrize(
165-
"data_fcn, plot_fcn", [
166-
(control.step_response, control.time_response_plot),
167-
(control.step_response, control.TimeResponseData.plot),
168-
(control.frequency_response, control.FrequencyResponseData.plot),
169-
(control.frequency_response, control.bode),
170-
(control.frequency_response, control.bode_plot),
168+
"data_fcn, plot_fcn, mimo", [
169+
(control.step_response, control.time_response_plot, True),
170+
(control.step_response, control.TimeResponseData.plot, True),
171+
(control.frequency_response, control.FrequencyResponseData.plot, True),
172+
(control.frequency_response, control.bode, True),
173+
(control.frequency_response, control.bode_plot, True),
174+
(control.nyquist_response, control.nyquist_plot, False),
171175
])
172-
def test_response_plot_kwargs(data_fcn, plot_fcn):
176+
def test_response_plot_kwargs(data_fcn, plot_fcn, mimo):
173177
# Create a system for testing
174-
response = data_fcn(control.rss(4, 2, 2))
178+
if mimo:
179+
response = data_fcn(control.rss(4, 2, 2))
180+
else:
181+
response = data_fcn(control.rss(4, 1, 1))
175182

176183
# Make sure that calling the data function with unknown keyword errs
177-
with pytest.raises((AttributeError, TypeError),
178-
match="(has no property|unexpected keyword)"):
184+
with pytest.raises(
185+
(AttributeError, TypeError),
186+
match="(has no property|unexpected keyword|unrecognized keyword)"):
179187
data_fcn(control.rss(2, 1, 1), unknown=None)
180188

181189
# Call the plotting function normally and make sure it works
@@ -216,6 +224,7 @@ def test_response_plot_kwargs(data_fcn, plot_fcn):
216224
'lqr': test_unrecognized_kwargs,
217225
'nlsys': test_unrecognized_kwargs,
218226
'nyquist': test_matplotlib_kwargs,
227+
'nyquist_response': test_response_plot_kwargs,
219228
'nyquist_plot': test_matplotlib_kwargs,
220229
'pzmap': test_unrecognized_kwargs,
221230
'rlocus': test_unrecognized_kwargs,
@@ -245,6 +254,7 @@ def test_response_plot_kwargs(data_fcn, plot_fcn):
245254
frd_test.TestFRD.test_unrecognized_keyword,
246255
'FrequencyResponseData.plot': test_response_plot_kwargs,
247256
'InputOutputSystem.__init__': test_unrecognized_kwargs,
257+
'LTI.__init__': test_unrecognized_kwargs,
248258
'flatsys.LinearFlatSystem.__init__': test_unrecognized_kwargs,
249259
'NonlinearIOSystem.linearize': test_unrecognized_kwargs,
250260
'InterconnectedSystem.__init__':

0 commit comments

Comments
 (0)