Skip to content

Commit a3fea74

Browse files
committed
Merge branch 'master' into jgoppert
2 parents ba7025f + ea5f7de commit a3fea74

11 files changed

Lines changed: 226 additions & 59 deletions

File tree

ChangeLog

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,12 @@
1+
2014-08-09 Richard Murray <murray@sidamo.local>
2+
3+
* Cloned python-control/code from SourceForge, mapping SF user names
4+
to git format (name, e-mail)
5+
* Pushed to python-control/python-control on GitHub
6+
* Converted subversion branches/tags to git branches/tags
7+
8+
==== Repository moved from SourceForge to GitHub ====
9+
110
---- control-0.6d released -----
211

312
2014-03-22 Richard Murray <murray@sidamo.local>

control/dtime.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,11 +94,25 @@ def sample_system(sysc, Ts, method='matched'):
9494
if not isctime(sysc):
9595
raise ValueError("First argument must be continuous time system")
9696

97-
# TODO: impelement MIMO version
97+
# If we are passed a state space system, convert to transfer function first
98+
if isinstance(sysc, StateSpace) and method == 'zoh':
99+
100+
try:
101+
# try with slycot routine
102+
from slycot import mb05nd
103+
F, H = mb05nd(sysc.A, Ts)
104+
return StateSpace(F, H*sysc.B, sysc.C, sysc.D, Ts)
105+
except ImportError:
106+
if sysc.inputs != 1 or sysc.outputs != 1:
107+
raise TypeError(
108+
"mb05nd not found in slycot, or slycot not installed")
109+
110+
# TODO: implement MIMO version for other than ZOH state-space
98111
if (sysc.inputs != 1 or sysc.outputs != 1):
99112
raise NotImplementedError("MIMO implementation not available")
100113

101-
# If we are passed a state space system, convert to transfer function first
114+
# SISO state-space, with other than ZOH, or failing slycot import,
115+
# is handled by conversion to TF
102116
if isinstance(sysc, StateSpace):
103117
warn("sample_system: converting to transfer function")
104118
sysc = _convertToTransferFunction(sysc)

control/margins.py

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -80,13 +80,17 @@ def _polysqr(pol):
8080
# idea for the frequency data solution copied/adapted from
8181
# https://github.com/alchemyst/Skogestad-Python/blob/master/BODE.py
8282
# Rene van Paassen <rene.vanpaassen@gmail.com>
83-
def stability_margins(sysdata, deg=True, returnall=False, epsw=1e-12):
83+
#
84+
# RvP, July 8, 2014, corrected to exclude phase=0 crossing for the gain
85+
# margin polynomial
86+
def stability_margins(sysdata, deg=True, returnall=False, epsw=1e-10):
8487
"""Calculate gain, phase and stability margins and associated
8588
crossover frequencies.
8689
8790
Usage
8891
-----
89-
gm, pm, sm, wg, wp, ws = stability_margins(sysdata, deg=True)
92+
gm, pm, sm, wg, wp, ws = stability_margins(sysdata, deg=True,
93+
returnall=False, epsw=1e-10)
9094
9195
Parameters
9296
----------
@@ -101,7 +105,7 @@ def stability_margins(sysdata, deg=True, returnall=False, epsw=1e-12):
101105
returnall=False: boolean
102106
If true, return all margins found. Note that for frequency data or
103107
FRD systems, only one margin is found and returned.
104-
epsw=1e-12: float
108+
epsw=1e-10: float
105109
frequencies below this value are considered static gain, and not
106110
returned as margin.
107111
@@ -114,7 +118,7 @@ def stability_margins(sysdata, deg=True, returnall=False, epsw=1e-12):
114118
one crossover frequency is detected, returns the lowest corresponding
115119
margin.
116120
When requesting all margins, the return values are array_like,
117-
and all margins are returns for linear systems not equal to FRD
121+
and all margins are returned for linear systems not equal to FRD
118122
"""
119123

120124
try:
@@ -146,7 +150,19 @@ def stability_margins(sysdata, deg=True, returnall=False, epsw=1e-12):
146150
# test imaginary part of tf == 0, for phase crossover/gain margins
147151
test_w_180 = np.polyadd(np.polymul(inum, rden), np.polymul(rnum, -iden))
148152
w_180 = np.roots(test_w_180)
149-
w_180 = np.real(w_180[(np.imag(w_180) == 0) * (w_180 > epsw)])
153+
154+
# first remove imaginary and negative frequencies, epsw removes the
155+
# "0" frequency for type-2 systems
156+
w_180 = np.real(w_180[(np.imag(w_180) == 0) * (w_180 >= epsw)])
157+
158+
# evaluate response at remaining frequencies, to test for phase 180 vs 0
159+
resp_w_180 = np.real(np.polyval(sys.num[0][0], 1.j*w_180) /
160+
np.polyval(sys.den[0][0], 1.j*w_180))
161+
162+
# only keep frequencies where the negative real axis is crossed
163+
w_180 = w_180[(resp_w_180 < 0.0)]
164+
165+
# and sort
150166
w_180.sort()
151167

152168
# test magnitude is 1 for gain crossover/phase margins
@@ -202,14 +218,14 @@ def dstab(w):
202218
SM = np.abs(sys.evalfr(wstab)[0][0]+1)
203219

204220
if returnall:
205-
return GM, PM, SM, wc, w_180, wstab
221+
return GM, PM, SM, w_180, wc, wstab
206222
else:
207223
return (
208224
(GM.shape[0] or None) and GM[0],
209225
(PM.shape[0] or None) and PM[0],
210226
(SM.shape[0] or None) and SM[0],
211-
(wc.shape[0] or None) and wc[0],
212227
(w_180.shape[0] or None) and w_180[0],
228+
(wc.shape[0] or None) and wc[0],
213229
(wstab.shape[0] or None) and wstab[0])
214230

215231

control/matlab.py

Lines changed: 68 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -134,14 +134,16 @@
134134
\- lti/set set/modify properties of LTI models
135135
\- setdelaymodel specify internal delay model (state space
136136
only)
137+
\* :func:`rss` create a random continuous state space model
138+
\* :func:`drss` create a random discrete state space model
137139
== ========================== ============================================
138140
139141
140142
Data extraction
141143
----------------------------------------------------------------------------
142144
143145
== ========================== ============================================
144-
\ lti/tfdata extract numerators and denominators
146+
\* :func:`tfdata` extract numerators and denominators
145147
\ lti/zpkdata extract zero/pole/gain data
146148
\ lti/ssdata extract state-space matrices
147149
\ lti/dssdata descriptor version of SSDATA
@@ -159,7 +161,7 @@
159161
\ zpk conversion to zero/pole/gain
160162
\* :func:`ss` conversion to state space
161163
\* :func:`frd` conversion to frequency data
162-
\ c2d continuous to discrete conversion
164+
\* :func:`c2d` continuous to discrete conversion
163165
\ d2c discrete to continuous conversion
164166
\ d2d resample discrete-time model
165167
\ upsample upsample discrete-time LTI systems
@@ -183,7 +185,7 @@
183185
(see also overloaded ``*``)
184186
\* :func:`~bdalg.feedback` connect lti models with a feedback loop
185187
\ lti/lft generalized feedback interconnection
186-
\ lti/connect arbitrary interconnection of lti models
188+
\* :func:'~bdalg.connect' arbitrary interconnection of lti models
187189
\ sumblk summing junction (for use with connect)
188190
\ strseq builds sequence of indexed strings
189191
(for I/O naming)
@@ -1101,8 +1103,8 @@ def rlocus(sys, klist = None, **keywords):
11011103
"""Root locus plot
11021104
11031105
The root-locus plot has a callback function that prints pole location,
1104-
gain and damping to the Python consol on mouseclicks on the root-locus
1105-
graph.
1106+
gain and damping to the Python console on mouseclicks on the root-locus
1107+
graph.
11061108
11071109
Parameters
11081110
----------
@@ -1111,6 +1113,17 @@ def rlocus(sys, klist = None, **keywords):
11111113
klist:
11121114
optional list of gains
11131115
1116+
Keyword parameters
1117+
------------------
1118+
xlim : control of x-axis range, normally with tuple, for
1119+
other options, see matplotlib.axes
1120+
ylim : control of y-axis range
1121+
Plot : boolean (default = True)
1122+
If True, plot magnitude and phase
1123+
PrintGain: boolean (default = True)
1124+
If True, report mouse clicks when close to the root-locus branches,
1125+
calculate gain, damping and print
1126+
11141127
Returns
11151128
-------
11161129
rlist:
@@ -1155,7 +1168,7 @@ def margin(*args):
11551168
margin: no magnitude crossings found
11561169
11571170
.. todo::
1158-
better ecample system!
1171+
better example system!
11591172
11601173
#>>> gm, pm, wg, wp = margin(mag, phase, w)
11611174
"""
@@ -1168,7 +1181,7 @@ def margin(*args):
11681181
raise ValueError("Margin needs 1 or 3 arguments; received %i."
11691182
% len(args))
11701183

1171-
return margin[0], margin[1], margin[4], margin[3]
1184+
return margin[0], margin[1], margin[3], margin[4]
11721185

11731186
def dcgain(*args):
11741187
'''
@@ -1269,10 +1282,11 @@ def step(sys, T=None, X0=0., input=0, output=None, **keywords):
12691282
'''
12701283
Step response of a linear system
12711284
1272-
If the system has multiple inputs or outputs (MIMO), one input and one
1273-
output have to be selected for the simulation. The parameters `input`
1274-
and `output` do this. All other inputs are set to 0, all other outputs
1275-
are ignored.
1285+
If the system has multiple inputs or outputs (MIMO), one input has
1286+
to be selected for the simulation. Optionally, one output may be
1287+
selected. If no selection is made for the output, all outputs are
1288+
given. The parameters `input` and `output` do this. All other
1289+
inputs are set to 0, all other outputs are ignored.
12761290
12771291
Parameters
12781292
----------
@@ -1291,7 +1305,7 @@ def step(sys, T=None, X0=0., input=0, output=None, **keywords):
12911305
Index of the input that will be used in this simulation.
12921306
12931307
output: int
1294-
Index of the output that will be used in this simulation.
1308+
If given, index of the output that is returned by this simulation.
12951309
12961310
**keywords:
12971311
Additional keyword arguments control the solution algorithm for the
@@ -1316,19 +1330,21 @@ def step(sys, T=None, X0=0., input=0, output=None, **keywords):
13161330
Examples
13171331
--------
13181332
>>> yout, T = step(sys, T, X0)
1333+
13191334
'''
13201335
T, yout = timeresp.step_response(sys, T, X0, input, output,
1321-
transpose = True, **keywords)
1336+
transpose=True, **keywords)
13221337
return yout, T
13231338

1324-
def impulse(sys, T=None, input=0, output=0, **keywords):
1339+
def impulse(sys, T=None, input=0, output=None, **keywords):
13251340
'''
13261341
Impulse response of a linear system
13271342
1328-
If the system has multiple inputs or outputs (MIMO), one input and
1329-
one output must be selected for the simulation. The parameters
1330-
`input` and `output` do this. All other inputs are set to 0, all
1331-
other outputs are ignored.
1343+
If the system has multiple inputs or outputs (MIMO), one input has
1344+
to be selected for the simulation. Optionally, one output may be
1345+
selected. If no selection is made for the output, all outputs are
1346+
given. The parameters `input` and `output` do this. All other
1347+
inputs are set to 0, all other outputs are ignored.
13321348
13331349
Parameters
13341350
----------
@@ -1371,14 +1387,13 @@ def impulse(sys, T=None, input=0, output=0, **keywords):
13711387
transpose = True, **keywords)
13721388
return yout, T
13731389

1374-
def initial(sys, T=None, X0=0., input=0, output=0, **keywords):
1390+
def initial(sys, T=None, X0=0., input=None, output=None, **keywords):
13751391
'''
13761392
Initial condition response of a linear system
13771393
1378-
If the system has multiple inputs or outputs (MIMO), one input and one
1379-
output have to be selected for the simulation. The parameters `input`
1380-
and `output` do this. All other inputs are set to 0, all other outputs
1381-
are ignored.
1394+
If the system has multiple outputs (?IMO), optionally, one output
1395+
may be selected. If no selection is made for the output, all
1396+
outputs are given.
13821397
13831398
Parameters
13841399
----------
@@ -1394,10 +1409,11 @@ def initial(sys, T=None, X0=0., input=0, output=0, **keywords):
13941409
Numbers are converted to constant arrays with the correct shape.
13951410
13961411
input: int
1397-
Index of the input that will be used in this simulation.
1412+
This input is ignored, but present for compatibility with step
1413+
and impulse.
13981414
13991415
output: int
1400-
Index of the output that will be used in this simulation.
1416+
If given, index of the output that is returned by this simulation.
14011417
14021418
**keywords:
14031419
Additional keyword arguments control the solution algorithm for the
@@ -1422,9 +1438,10 @@ def initial(sys, T=None, X0=0., input=0, output=0, **keywords):
14221438
Examples
14231439
--------
14241440
>>> T, yout = initial(sys, T, X0)
1441+
14251442
'''
1426-
T, yout = timeresp.initial_response(sys, T, X0, input, output,
1427-
transpose = True, **keywords)
1443+
T, yout = timeresp.initial_response(sys, T, X0, output=output,
1444+
transpose=True, **keywords)
14281445
return yout, T
14291446

14301447
def lsim(sys, U=0., T=None, X0=0., **keywords):
@@ -1524,8 +1541,29 @@ def tfdata(sys, **kw):
15241541
return (tf.num, tf.den)
15251542

15261543
# Convert a continuous time system to a discrete time system
1527-
def c2d(sysc, Ts, method):
1528-
# TODO: add docstring
1544+
def c2d(sysc, Ts, method='zoh'):
1545+
'''
1546+
Return a discrete-time system
1547+
1548+
Parameters
1549+
----------
1550+
sysc: Lti (StateSpace or TransferFunction), continuous
1551+
System to be converted
1552+
1553+
Ts: number
1554+
Sample time for the conversion
1555+
1556+
method: string, optional
1557+
Method to be applied,
1558+
'zoh' Zero-order hold on the inputs (default)
1559+
'foh' First-order hold, currently not implemented
1560+
'impulse' Impulse-invariant discretization, currently not implemented
1561+
'tustin' Bilinear (Tustin) approximation, only SISO
1562+
'matched' Matched pole-zero method, only SISO
1563+
'''
15291564
# Call the sample_system() function to do the work
1530-
return sample_system(sysc, Ts, method)
1565+
sysd = sample_system(sysc, Ts, method)
1566+
if isinstance(sysc, StateSpace) and not isinstance(sysd, StateSpace):
1567+
return _convertToStateSpace(sysd)
1568+
return sysd
15311569

control/rlocus.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,9 @@ def root_locus(sys, kvect, xlim=None, ylim=None, plotstr='-', Plot=True,
6565
Linear input/output systems (SISO only, for now)
6666
kvect : gain_range (default = None)
6767
List of gains to use in computing diagram
68+
xlim : control of x-axis range, normally with tuple, for
69+
other options, see matplotlib.axes
70+
ylim : control of y-axis range
6871
Plot : boolean (default = True)
6972
If True, plot magnitude and phase
7073
PrintGain: boolean (default = True)

control/statesp.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -622,7 +622,7 @@ def _convertToStateSpace(sys, **kw):
622622
ssout[3][:sys.outputs, :states],
623623
ssout[4], sys.dt)
624624
except ImportError:
625-
# TODO: do we want to squeeze first and check dimenations?
625+
# TODO: do we want to squeeze first and check dimensions?
626626
# I think this will fail if num and den aren't 1-D after
627627
# the squeeze
628628
lti_sys = lti(squeeze(sys.num), squeeze(sys.den))

0 commit comments

Comments
 (0)