Skip to content

Commit db37f05

Browse files
committed
Initial support for discrete time systems (version 0.6)
* Added dt class variable to keep track of whether system is discrete * Algebraic operations check for timebase consistency * Functions that don't support discrete time generate NotImplementedError * Added unit tests for code that has been implemented so far * Updated version number to 0.6a
1 parent cbb2df2 commit db37f05

14 files changed

+591
-47
lines changed

ChangeLog

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,48 @@
1+
2012-10-06 Richard Murray <murray@altura.local>
2+
3+
* src/dtime.py: new module with functions for operating on discrete
4+
time systems
5+
6+
* src/modelsimp.py: added NotImplementedError for discrete time
7+
* src/freqplot.py: added NotImplementedError for discrete time
8+
9+
* src/bdalg.py: added documentation about timebases (discrete versus
10+
continuous)
11+
12+
* src/statesp.py (_mimo2siso): include sampling time
13+
14+
* src/xferfcn.py (TransferFunction.__str__): added optional var
15+
keyword to allow any symbol to be used
16+
17+
* src/matlab.py (tf2ss): added code required to keep track of dt
18+
19+
* src/xferfcn.py (TransferFunction.__str__): added additional string for
20+
discrete time system, plus use 'z' for polynomial variable
21+
(TransferFunction.__neg__): set sampling time for negation
22+
(TransferFunction.__add__): added test for same sampling time
23+
(TransferFunction.__mul__): added test for same sampling time
24+
(TransferFunction.__div__): added test for same sampling time
25+
(TransferFunction.feedback): added test for same sampling time
26+
27+
* src/statesp.py (StateSpace.__str__): added additional string for
28+
discrete time system
29+
(StateSpace.__add__): added test for same sampling time
30+
(StateSpace.__mul__): added test for same sampling time
31+
(StateSpace.__rmul__): set sampling time for product
32+
(StateSpace.__neg__): set sampling time for negation
33+
(StateSpace.feedback): added test for same sampling time
34+
35+
* tests/discrete_test.py (TestDiscrete): unit tests for discrete
36+
time systems
37+
38+
* src/xferfcn.py (TransferFunction.__init__): added dt class
39+
variable for discrete time systems
40+
41+
* src/statesp.py (StateSpace.__init__): added dt class variable for
42+
discrete time systems
43+
44+
---- control-0.5c released -----
45+
146
2012-10-03 Richard Murray <murray@altura.local>
247

348
* tests/matlab_test.py (TestMatlab.testSsdata): unit test

doc/conf.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,9 @@
5858
# built documents.
5959
#
6060
# The short X.Y version.
61-
version = '0.5c'
61+
version = '0.6'
6262
# The full version, including alpha/beta/rc tags.
63-
release = '0.5c'
63+
release = '0.6c'
6464

6565
# The language for content autogenerated by Sphinx. Refer to documentation
6666
# for a list of supported languages.

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from setuptools import setup
55

66
setup(name = 'control',
7-
version = '0.5c',
7+
version = '0.6a',
88
description = 'Python Control Systems Library',
99
author = 'Richard Murray',
1010
author_email = 'murray@cds.caltech.edu',

src/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
#! Should probably only import the exact functions we use...
6060
from bdalg import series, parallel, negate, feedback
6161
from delay import pade
62+
from dtime import timebase, isdtime, isctime
6263
from freqplot import bode_plot, nyquist_plot, gangof4_plot
6364
from freqplot import bode, nyquist, gangof4
6465
from margins import stability_margins, phase_crossover_frequencies

src/bdalg.py

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ def series(sys1, sys2):
7171
------
7272
ValueError
7373
if `sys2.inputs` does not equal `sys1.outputs`
74+
if `sys1.dt` is not compatible with `sys2.dt`
7475
7576
See Also
7677
--------
@@ -83,6 +84,11 @@ def series(sys1, sys2):
8384
TransferFunction classes. The output type is usually the type of `sys2`.
8485
If `sys2` is a scalar, then the output type is the type of `sys1`.
8586
87+
If both systems have a defined timebase (dt = 0 for continuous time,
88+
dt > 0 for discrete time), then the timebase for both systems much
89+
match. If only one of the system has a timebase, the return
90+
timebase will be set to match it.
91+
8692
Examples
8793
--------
8894
>>> sys3 = series(sys1, sys2) # Same as sys3 = sys2 * sys1.
@@ -116,9 +122,15 @@ def parallel(sys1, sys2):
116122
117123
Notes
118124
-----
119-
This function is a wrapper for the __add__ function in the StateSpace and
120-
TransferFunction classes. The output type is usually the type of `sys1`. If
121-
`sys1` is a scalar, then the output type is the type of `sys2`.
125+
This function is a wrapper for the __add__ function in the
126+
StateSpace and TransferFunction classes. The output type is usually
127+
the type of `sys1`. If `sys1` is a scalar, then the output type is
128+
the type of `sys2`.
129+
130+
If both systems have a defined timebase (dt = 0 for continuous time,
131+
dt > 0 for discrete time), then the timebase for both systems much
132+
match. If only one of the system has a timebase, the return
133+
timebase will be set to match it.
122134
123135
Examples
124136
--------
@@ -145,6 +157,11 @@ def negate(sys):
145157
This function is a wrapper for the __neg__ function in the StateSpace and
146158
TransferFunction classes. The output type is the same as the input type.
147159
160+
If both systems have a defined timebase (dt = 0 for continuous time,
161+
dt > 0 for discrete time), then the timebase for both systems much
162+
match. If only one of the system has a timebase, the return
163+
timebase will be set to match it.
164+
148165
Examples
149166
--------
150167
>>> sys2 = negate(sys1) # Same as sys2 = -sys1.

src/dtime.py

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
"""dtime.py
2+
3+
Utility functions for disrete time systems.
4+
5+
Routines in this module:
6+
7+
isdtime()
8+
isctime()
9+
timebase()
10+
"""
11+
12+
"""Copyright (c) 2012 by California Institute of Technology
13+
All rights reserved.
14+
15+
Redistribution and use in source and binary forms, with or without
16+
modification, are permitted provided that the following conditions
17+
are met:
18+
19+
1. Redistributions of source code must retain the above copyright
20+
notice, this list of conditions and the following disclaimer.
21+
22+
2. Redistributions in binary form must reproduce the above copyright
23+
notice, this list of conditions and the following disclaimer in the
24+
documentation and/or other materials provided with the distribution.
25+
26+
3. Neither the name of the California Institute of Technology nor
27+
the names of its contributors may be used to endorse or promote
28+
products derived from this software without specific prior
29+
written permission.
30+
31+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
32+
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
33+
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
34+
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL CALTECH
35+
OR THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
36+
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
37+
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
38+
USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
39+
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
40+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
41+
OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
42+
SUCH DAMAGE.
43+
44+
Author: Richard M. Murray
45+
Date: 6 October 2012
46+
47+
$Id: dtime.py 185 2012-08-30 05:44:32Z murrayrm $
48+
49+
"""
50+
51+
from statesp import StateSpace
52+
from xferfcn import TransferFunction
53+
54+
# Return the timebase of a system
55+
def timebase(sys):
56+
# TODO: add docstring
57+
# If we get passed a constant, timebase is None
58+
if isinstance(sys, (int, float, long, complex)):
59+
return None
60+
61+
# Check for a transfer fucntion or state space object
62+
if isinstance(sys, (StateSpace, TransferFunction)):
63+
if sys.dt > 0:
64+
return 'dtime';
65+
elif sys.dt == 0:
66+
return 'ctime';
67+
elif sys.dt == None:
68+
return None
69+
70+
# Got pased something we don't recognize or bad timebase
71+
return False;
72+
73+
# Check to see if a system is a discrete time system
74+
def isdtime(sys, strict=False):
75+
# TODO: add docstring
76+
# Check to see if this is a constant
77+
if isinstance(sys, (int, float, long, complex)):
78+
# OK as long as strict checking is off
79+
return True if not strict else False
80+
81+
# Check for a transfer fucntion or state space object
82+
if isinstance(sys, (StateSpace, TransferFunction)):
83+
# Look for dt > 0 or dt == None (if not strict)
84+
return sys.dt > 0 or (not strict and sys.dt == None)
85+
86+
# Got possed something we don't recognize
87+
return False
88+
89+
# Check to see if a system is a continuous time system
90+
def isctime(sys, strict=False):
91+
# TODO: add docstring
92+
# Check to see if this is a constant
93+
if isinstance(sys, (int, float, long, complex)):
94+
# OK as long as strict checking is off
95+
return True if not strict else False
96+
97+
# Check for a transfer fucntion or state space object
98+
if isinstance(sys, (StateSpace, TransferFunction)):
99+
# Look for dt == 0 or dt == None (if not strict)
100+
return sys.dt == 0 or (not strict and sys.dt == None)
101+
102+
# Got possed something we don't recognize
103+
return False
104+
105+
106+

src/freqplot.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
import numpy as np
4747
from ctrlutil import unwrap
4848
from bdalg import feedback
49+
from dtime import isdtime
4950

5051
#
5152
# Main plotting functions
@@ -104,6 +105,10 @@ def bode_plot(syslist, omega=None, dB=False, Hz=False, deg=True,
104105

105106
mags, phases, omegas = [], [], []
106107
for sys in syslist:
108+
# TODO: implement for discrete time systems
109+
if (isdtime(sys, strict=True)):
110+
raise(NotImplementedError("Function not implemented in discrete time"))
111+
107112
if (sys.inputs > 1 or sys.outputs > 1):
108113
#TODO: Add MIMO bode plots.
109114
raise NotImplementedError("Bode is currently only implemented for SISO systems.")
@@ -209,6 +214,10 @@ def nyquist_plot(syslist, omega=None, Plot=True, color='b',
209214
omega = np.logspace(np.log10(omega[0]), np.log10(omega[1]),
210215
num=50, endpoint=True, base=10.0)
211216
for sys in syslist:
217+
# TODO: implement for discrete time systems
218+
if (isdtime(sys, strict=True)):
219+
raise(NotImplementedError("Function not implemented in discrete time"))
220+
212221
if (sys.inputs > 1 or sys.outputs > 1):
213222
#TODO: Add MIMO nyquist plots.
214223
raise NotImplementedError("Nyquist is currently only implemented for SISO systems.")
@@ -272,6 +281,10 @@ def gangof4_plot(P, C, omega=None):
272281
-------
273282
None
274283
"""
284+
# TODO: implement for discrete time systems
285+
if (isdtime(P, strict=True) or isdtime(C, strict=True)):
286+
raise(NotImplementedError("Function not implemented in discrete time"))
287+
275288
if (P.inputs > 1 or P.outputs > 1 or C.inputs > 1 or C.outputs >1):
276289
#TODO: Add MIMO go4 plots.
277290
raise NotImplementedError("Gang of four is currently only implemented for SISO systems.")
@@ -353,6 +366,10 @@ def default_frequency_range(syslist):
353366
if (not getattr(syslist, '__iter__', False)):
354367
syslist = (syslist,)
355368
for sys in syslist:
369+
# TODO: implement for discrete time systems
370+
if (isdtime(sys, strict=True)):
371+
raise(NotImplementedError("Function not implemented in discrete time"))
372+
356373
# Add new features to the list
357374
features = np.concatenate((features, np.abs(sys.pole())))
358375
features = np.concatenate((features, np.abs(sys.zero())))

src/margins.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
import xferfcn
5151
from freqplot import bode
5252
import numpy as np
53+
from dtime import isdtime
5354

5455
# gain and phase margins
5556
# contributed by Sawyer B. Fuller <minster@caltech.edu>
@@ -88,8 +89,14 @@ def stability_margins(sysdata, deg=True):
8889

8990
if (not getattr(sysdata, '__iter__', False)):
9091
sys = sysdata
92+
93+
# TODO: implement for discrete time systems
94+
if (isdtime(sys, strict=True)):
95+
raise(NotImplementedError("Function not implemented in discrete time"))
96+
9197
mag, phase, omega = bode(sys, deg=deg, Plot=False)
9298
elif len(sysdata) == 3:
99+
# TODO: replace with FRD object type?
93100
mag, phase, omega = sysdata
94101
else:
95102
raise ValueError("Margin sysdata must be either a linear system or a 3-sequence of mag, phase, omega.")

src/matlab.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -613,10 +613,10 @@ def ss2tf(*args):
613613
614614
"""
615615

616-
if len(args) == 4:
617-
# Assume we were given the A, B, C, D matrix
618-
return _convertToTransferFunction(StateSpace(args[0], args[1], args[2],
619-
args[3]))
616+
if len(args) == 4 or len(args) == 5:
617+
# Assume we were given the A, B, C, D matrix and (optional) dt
618+
return _convertToTransferFunction(StateSpace(*args))
619+
620620
elif len(args) == 1:
621621
sys = args[0]
622622
if isinstance(sys, StateSpace):
@@ -683,9 +683,10 @@ def tf2ss(*args):
683683
684684
"""
685685

686-
if len(args) == 2:
686+
if len(args) == 2 or len(args) == 3:
687687
# Assume we were given the num, den
688-
return _convertToStateSpace(TransferFunction(args[0], args[1]))
688+
return _convertToStateSpace(TransferFunction(*args))
689+
689690
elif len(args) == 1:
690691
sys = args[0]
691692
if not isinstance(sys, TransferFunction):

src/modelsimp.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
from exception import *
4646
from statefbk import *
4747
from statesp import StateSpace
48+
from dtime import isdtime, isctime
4849

4950
# Hankel Singular Value Decomposition
5051
# The following returns the Hankel singular values, which are singular values
@@ -80,6 +81,10 @@ def hsvd(sys):
8081
>>> H = hsvd(sys)
8182
8283
"""
84+
# TODO: implement for discrete time systems
85+
if (isdtime(sys, strict=True)):
86+
raise(NotImplementedError("Function not implemented in discrete time"))
87+
8388
Wc = gram(sys,'c')
8489
Wo = gram(sys,'o')
8590
WoWc = np.dot(Wo, Wc)
@@ -132,7 +137,11 @@ def modred(sys, ELIM, method='matchdc'):
132137
# elif isDisc():
133138
# dico = 'D'
134139
# else:
135-
dico = 'C'
140+
if (isctime(sys)):
141+
dico = 'C'
142+
else:
143+
raise(NotImplementedError("Function not implemented in discrete time"))
144+
136145

137146
#Check system is stable
138147
D,V = np.linalg.eig(sys.A)

0 commit comments

Comments
 (0)