-
Notifications
You must be signed in to change notification settings - Fork 458
Expand file tree
/
Copy pathwrappers.py
More file actions
425 lines (336 loc) · 13 KB
/
Copy pathwrappers.py
File metadata and controls
425 lines (336 loc) · 13 KB
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
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
# wrappers.py - Wrappers for the MATLAB compatibility module.
"""Wrappers for the MATLAB compatibility module.
"""
import warnings
from warnings import warn
import numpy as np
from scipy.signal import zpk2tf
from ..exception import ControlArgument
from ..lti import LTI
from ..statesp import ss
from ..xferfcn import tf
__all__ = ['bode', 'nyquist', 'ngrid', 'rlocus', 'pzmap', 'dcgain', 'connect']
def bode(*args, **kwargs):
"""bode(sys[, omega, dB, Hz, deg, ...])
Bode plot of the frequency response.
Plots a bode gain and phase diagram.
Parameters
----------
sys : LTI, or list of LTI
System for which the Bode response is plotted and give. Optionally
a list of systems can be entered, or several systems can be
specified (i.e. several parameters). The `sys` arguments may also be
interspersed with format strings. A frequency argument (array_like)
may also be added (see Examples).
omega : array
Range of frequencies in rad/s.
dB : boolean
If True, plot result in dB.
Hz : boolean
If True, plot frequency in Hz (omega must be provided in rad/sec).
deg : boolean
If True, return phase in degrees (else radians).
plot : boolean
If True, plot magnitude and phase.
Returns
-------
mag, phase, omega : array
Magnitude, phase, and frequencies represented in the Bode plot.
Examples
--------
>>> from control.matlab import ss, bode
>>> sys = ss([[1, -2], [3, -4]], [[5], [7]], [[6, 8]], 9)
>>> mag, phase, omega = bode(sys)
>>> bode(sys, w) # one system, freq vector # doctest: +SKIP
>>> bode(sys1, sys2, ..., sysN) # several systems # doctest: +SKIP
>>> bode(sys1, sys2, ..., sysN, w) # doctest: +SKIP
>>> bode(sys1, 'plotstyle1', ..., sysN, 'plotstyleN') # doctest: +SKIP
"""
from ..freqplot import bode_plot
# Use the plot keyword to get legacy behavior
# TODO: update to call frequency_response and then bode_plot
kwargs = dict(kwargs) # make a copy since we modify this
if 'plot' not in kwargs:
kwargs['plot'] = True
# Turn off deprecation warning
with warnings.catch_warnings():
warnings.filterwarnings(
'ignore', message='.* return values of .* is deprecated',
category=DeprecationWarning)
# If first argument is a list, assume python-control calling format
if hasattr(args[0], '__iter__'):
retval = bode_plot(*args, **kwargs)
else:
# Parse input arguments
syslist, omega, args, other = _parse_freqplot_args(*args)
kwargs.update(other)
# Call the bode command
retval = bode_plot(syslist, omega, *args, **kwargs)
return retval
def nyquist(*args, plot=True, **kwargs):
"""nyquist(syslist[, omega])
Nyquist plot of the frequency response.
Plots a Nyquist plot for the system over a (optional) frequency range.
Parameters
----------
syslist : list of LTI
List of linear input/output systems (single system is OK).
omega : array_like
Set of frequencies to be evaluated, in rad/sec.
omega_limits : array_like of two values
Set limits for plotted frequency range. If Hz=True the limits are
in Hz otherwise in rad/s. Specifying `omega` as a list of two
elements is equivalent to providing `omega_limits`.
plot : bool
If False, do not generate a plot.
Returns
-------
real : ndarray (or list of ndarray if len(syslist) > 1))
Real part of the frequency response array.
imag : ndarray (or list of ndarray if len(syslist) > 1))
Imaginary part of the frequency response array.
omega : ndarray (or list of ndarray if len(syslist) > 1))
Frequencies in rad/s.
"""
from ..freqplot import nyquist_plot, nyquist_response
# If first argument is a list, assume python-control calling format
if hasattr(args[0], '__iter__'):
return nyquist_plot(*args, **kwargs)
# Parse arguments
syslist, omega, args, other = _parse_freqplot_args(*args)
kwargs.update(other)
# Get the Nyquist response (and pop keywords used there)
response = nyquist_response(
syslist, omega, *args, omega_limits=kwargs.pop('omega_limits', None))
contour = response.contour
if plot:
# Plot the result
nyquist_plot(response, *args, **kwargs)
# Create the MATLAB output arguments
freqresp = syslist(contour)
real, imag = freqresp.real, freqresp.imag
return real, imag, contour.imag
def _parse_freqplot_args(*args):
"""Parse arguments to frequency plot routines (bode, nyquist)"""
syslist, plotstyle, omega, other = [], [], None, {}
i = 0
while i < len(args):
# Check to see if this is a system of some sort
if isinstance(args[i], LTI):
# Append the system to our list of systems
syslist.append(args[i])
i += 1
# See if the next object is a plotsytle (string)
if (i < len(args) and isinstance(args[i], str)):
plotstyle.append(args[i])
i += 1
# Go on to the next argument
continue
# See if this is a frequency list
elif isinstance(args[i], (list, np.ndarray)):
omega = args[i]
i += 1
break
# See if this is a frequency range
elif isinstance(args[i], tuple) and len(args[i]) == 2:
other['omega_limits'] = args[i]
i += 1
else:
raise ControlArgument("unrecognized argument type")
# Check to make sure that we processed all arguments
if (i < len(args)):
raise ControlArgument("not all arguments processed")
# Check to make sure we got the same number of plotstyles as systems
if (len(plotstyle) != 0 and len(syslist) != len(plotstyle)):
raise ControlArgument(
"number of systems and plotstyles should be equal")
# Warn about unimplemented plotstyles
#! TODO: remove this when plot styles are implemented in bode()
#! TODO: uncomment unit test code that tests this out
if (len(plotstyle) != 0):
warn("Warning (matlab.bode): plot styles not implemented");
if len(syslist) == 0:
raise ControlArgument("no systems specified")
elif len(syslist) == 1:
# If only one system given, return just that system (not a list)
syslist = syslist[0]
return syslist, omega, plotstyle, other
# TODO: rewrite to call root_locus_map, without using legacy plot keyword
def rlocus(*args, **kwargs):
"""rlocus(sys[, gains, xlim, ylim, ...])
Root locus diagram.
Calculate the root locus by finding the roots of 1 + k * G(s) where G
is a linear system with transfer function num(s)/den(s) and each k is
an element of gains.
Parameters
----------
sys : LTI object
Linear input/output systems (SISO only, for now).
gains : array_like, optional
Gains to use in computing plot of closed-loop poles.
xlim : tuple or list, optional
Set limits of x axis (see `matplotlib.axes.Axes.set_xlim`).
ylim : tuple or list, optional
Set limits of y axis (see `matplotlib.axes.Axes.set_ylim`).
plot : bool
If False, do not generate a plot.
Returns
-------
roots : ndarray
Closed-loop root locations, arranged in which each row corresponds
to a gain in gains.
gains : ndarray
Gains used. Same as gains keyword argument if provided.
Notes
-----
This function is a wrapper for `root_locus_plot`,
with legacy return arguments.
"""
from ..rlocus import root_locus_plot
# Use the plot keyword to get legacy behavior
kwargs = dict(kwargs) # make a copy since we modify this
if 'plot' not in kwargs:
kwargs['plot'] = True
# Turn off deprecation warning
with warnings.catch_warnings():
warnings.filterwarnings(
'ignore', message='.* return values of .* is deprecated',
category=DeprecationWarning)
retval = root_locus_plot(*args, **kwargs)
return retval
# TODO: rewrite to call pole_zero_map, without using legacy plot keyword
def pzmap(*args, **kwargs):
"""pzmap(sys[, grid, plot])
Plot a pole/zero map for a linear system.
Parameters
----------
sys : `StateSpace` or `TransferFunction`
Linear system for which poles and zeros are computed.
plot : bool, optional
If True a graph is generated with matplotlib,
otherwise the poles and zeros are only computed and returned.
grid : boolean (default = False)
If True, plot omega-damping grid.
Returns
-------
poles : array
The system's poles.
zeros : array
The system's zeros.
Notes
-----
This function is a wrapper for `pole_zero_plot`,
with legacy return arguments.
"""
from ..pzmap import pole_zero_plot
# Use the plot keyword to get legacy behavior
kwargs = dict(kwargs) # make a copy since we modify this
if 'plot' not in kwargs:
kwargs['plot'] = True
# Turn off deprecation warning
with warnings.catch_warnings():
warnings.filterwarnings(
'ignore', message='.* return values of .* is deprecated',
category=DeprecationWarning)
retval = pole_zero_plot(*args, **kwargs)
return retval
from ..nichols import nichols_grid
def ngrid():
return nichols_grid()
ngrid.__doc__ = nichols_grid.__doc__
def dcgain(*args):
"""dcgain(sys) \
dcgain(num, den) \
dcgain(Z, P, k) \
dcgain(A, B, C, D)
Compute the gain of the system in steady state.
The function takes either 1, 2, 3, or 4 parameters:
* dcgain(sys)
* dcgain(num, den)
* dcgain(Z, P, k)
* dcgain(A, B, C, D)
Parameters
----------
A, B, C, D : array_like
A linear system in state space form.
Z, P, k : array_like, array_like, number
A linear system in zero, pole, gain form.
num, den : array_like
A linear system in transfer function form.
sys : `StateSpace` or `TransferFunction`
A linear system object.
Returns
-------
gain : ndarray
The gain of each output versus each input:
:math:`y = gain \\cdot u`.
Notes
-----
This function is only useful for systems with invertible system
matrix `A`.
All systems are first converted to state space form. The function then
computes:
.. math:: gain = - C \\cdot A^{-1} \\cdot B + D
"""
#Convert the parameters to state space form
if len(args) == 4:
A, B, C, D = args
return ss(A, B, C, D).dcgain()
elif len(args) == 3:
Z, P, k = args
num, den = zpk2tf(Z, P, k)
return tf(num, den).dcgain()
elif len(args) == 2:
num, den = args
return tf(num, den).dcgain()
elif len(args) == 1:
sys, = args
return sys.dcgain()
else:
raise ValueError("Function `dcgain` needs either 1, 2, 3 or 4 "
"arguments.")
from ..bdalg import connect as ct_connect
def connect(*args):
"""connect(sys, Q, inputv, outputv)
Index-based interconnection of an LTI system.
The system `sys` is a system typically constructed with `append`, with
multiple inputs and outputs. The inputs and outputs are connected
according to the interconnection matrix `Q`, and then the final inputs
and outputs are trimmed according to the inputs and outputs listed in
`inputv` and `outputv`.
NOTE: Inputs and outputs are indexed starting at 1 and negative values
correspond to a negative feedback interconnection.
Parameters
----------
sys : `InputOutputSystem`
System to be connected.
Q : 2D array
Interconnection matrix. First column gives the input to be connected.
The second column gives the index of an output that is to be fed into
that input. Each additional column gives the index of an additional
input that may be optionally added to that input. Negative values
mean the feedback is negative. A zero value is ignored. Inputs
and outputs are indexed starting at 1 to communicate sign information.
inputv : 1D array
List of final external inputs, indexed starting at 1.
outputv : 1D array
List of final external outputs, indexed starting at 1.
Returns
-------
out : `InputOutputSystem`
Connected and trimmed I/O system.
See Also
--------
append, feedback, connect, negate, parallel, series
Examples
--------
>>> G = ct.rss(7, inputs=2, outputs=2)
>>> K = [[1, 2], [2, -1]] # negative feedback interconnection
>>> T = ct.connect(G, K, [2], [1, 2])
>>> T.ninputs, T.noutputs, T.nstates
(1, 2, 7)
"""
# Turn off the deprecation warning
with warnings.catch_warnings():
warnings.filterwarnings('ignore', message="`connect` is deprecated")
return ct_connect(*args)