Skip to content

Commit 69129fb

Browse files
committed
Allow dt=True for a discrete time system with unspecified timebase
Use timebaseEqual(dt1, dt2) to check if two time bases are the same
1 parent db37f05 commit 69129fb

File tree

9 files changed

+102
-28
lines changed

9 files changed

+102
-28
lines changed

ChangeLog

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,18 @@
1+
2012-10-06 Richard Murray <murray@altura.local>
2+
3+
* tests/discrete_test.py: added additional tests for dt = True cases
4+
5+
* src/statesp.py (StateSpace.__str__): show sampling time as
6+
'unspecified' if dt is a boolean and equal to True
7+
8+
* src/xferfcn.py (TransferFunction.__str__): don't print sampling
9+
time (dt) if dt = True (discrete time with unspecified time base)
10+
11+
* src/dtime.py: added code to support dt = True for unspecified
12+
sampling time for a discrete time system
13+
14+
* src/bdalg.py: much -> must in docstrings
15+
116
2012-10-06 Richard Murray <murray@altura.local>
217

318
* src/dtime.py: new module with functions for operating on discrete

src/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@
6262
from dtime import timebase, isdtime, isctime
6363
from freqplot import bode_plot, nyquist_plot, gangof4_plot
6464
from freqplot import bode, nyquist, gangof4
65+
from lti import timebaseEqual
6566
from margins import stability_margins, phase_crossover_frequencies
6667
from mateqn import lyap, dlyap, care, dare
6768
from modelsimp import hsvd, modred, balred, era, markov

src/bdalg.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ def series(sys1, sys2):
8585
If `sys2` is a scalar, then the output type is the type of `sys1`.
8686
8787
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
88+
dt > 0 for discrete time), then the timebase for both systems must
8989
match. If only one of the system has a timebase, the return
9090
timebase will be set to match it.
9191
@@ -128,7 +128,7 @@ def parallel(sys1, sys2):
128128
the type of `sys2`.
129129
130130
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
131+
dt > 0 for discrete time), then the timebase for both systems must
132132
match. If only one of the system has a timebase, the return
133133
timebase will be set to match it.
134134
@@ -158,7 +158,7 @@ def negate(sys):
158158
TransferFunction classes. The output type is the same as the input type.
159159
160160
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
161+
dt > 0 for discrete time), then the timebase for both systems must
162162
match. If only one of the system has a timebase, the return
163163
timebase will be set to match it.
164164

src/dtime.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
isdtime()
88
isctime()
99
timebase()
10+
timebaseEqual()
1011
"""
1112

1213
"""Copyright (c) 2012 by California Institute of Technology
@@ -60,7 +61,7 @@ def timebase(sys):
6061

6162
# Check for a transfer fucntion or state space object
6263
if isinstance(sys, (StateSpace, TransferFunction)):
63-
if sys.dt > 0:
64+
if sys.dt > 0 or sys.dt == True:
6465
return 'dtime';
6566
elif sys.dt == 0:
6667
return 'ctime';
@@ -81,6 +82,7 @@ def isdtime(sys, strict=False):
8182
# Check for a transfer fucntion or state space object
8283
if isinstance(sys, (StateSpace, TransferFunction)):
8384
# Look for dt > 0 or dt == None (if not strict)
85+
# Note that dt = True will be checked by dt > 0
8486
return sys.dt > 0 or (not strict and sys.dt == None)
8587

8688
# Got possed something we don't recognize
@@ -101,6 +103,3 @@ def isctime(sys, strict=False):
101103

102104
# Got possed something we don't recognize
103105
return False
104-
105-
106-

src/lti.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,3 +47,15 @@ def __init__(self, inputs=1, outputs=1):
4747
# Data members common to StateSpace and TransferFunction.
4848
self.inputs = inputs
4949
self.outputs = outputs
50+
51+
# Check to see if two timebases are equal
52+
def timebaseEqual(dt1, dt2):
53+
# TODO: add docstring
54+
if (type(dt1) == bool or type(dt2) == bool):
55+
# Make sure both are unspecified discrete timebases
56+
return type(dt1) == type(dt2) and dt1 == dt2
57+
elif (type(dt1) == None or type(dt2) == None):
58+
# One or the other is unspecified => the other can be anything
59+
return True
60+
else:
61+
return dt1 == dt2

src/matlab.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -478,6 +478,11 @@ def tf(*args):
478478
of array_like objects. (A 3 dimensional data structure in total.)
479479
(For details see note below.)
480480
481+
``tf(num, den, dt)``
482+
Create a discrete time transfer function system; dt can either be a
483+
positive number indicating the sampling time or 'True' if no
484+
specific timebase is given.
485+
481486
Parameters
482487
----------
483488
sys: Lti (StateSpace or TransferFunction)
@@ -537,8 +542,8 @@ def tf(*args):
537542
538543
"""
539544

540-
if len(args) == 2:
541-
return TransferFunction(args[0], args[1])
545+
if len(args) == 2 or len(args) == 3:
546+
return TransferFunction(*args)
542547
elif len(args) == 1:
543548
sys = args[0]
544549
if isinstance(sys, StateSpace):

src/statesp.py

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@
8181
from numpy.linalg.linalg import LinAlgError
8282
from scipy.signal import lti
8383
import warnings
84-
from lti import Lti
84+
from lti import Lti, timebaseEqual
8585
import xferfcn
8686

8787
class StateSpace(Lti):
@@ -99,7 +99,9 @@ class StateSpace(Lti):
9999
variable and setting it to the sampling period. If 'dt' is not None,
100100
then it must match whenever two state space systems are combined.
101101
Setting dt = 0 specifies a continuous system, while leaving dt = None
102-
means the system timebase is not specified.
102+
means the system timebase is not specified. If 'dt' is set to True, the
103+
system will be treated as a discrete time system with unspecified
104+
sampling time.
103105
"""
104106

105107
def __init__(self, *args):
@@ -214,7 +216,9 @@ def __str__(self):
214216
str += "B = " + self.B.__str__() + "\n\n"
215217
str += "C = " + self.C.__str__() + "\n\n"
216218
str += "D = " + self.D.__str__() + "\n"
217-
if (self.dt > 0):
219+
if (type(self.dt) == bool and self.dt == True):
220+
str += "\ndt unspecified\n"
221+
elif (self.dt > 0):
218222
str += "\ndt = " + self.dt.__str__() + "\n"
219223
return str
220224

@@ -246,7 +250,7 @@ def __add__(self, other):
246250
if (self.dt == None and other.dt != None):
247251
dt = other.dt # use dt from second argument
248252
elif (other.dt == None and self.dt != None) or \
249-
(self.dt == other.dt):
253+
(timebaseEqual(self.dt, other.dt)):
250254
dt = self.dt # use dt from first argument
251255
else:
252256
raise ValueError, "Systems have different sampling times"
@@ -304,7 +308,7 @@ def __mul__(self, other):
304308
if (self.dt == None and other.dt != None):
305309
dt = other.dt # use dt from second argument
306310
elif (other.dt == None and self.dt != None) or \
307-
(self.dt == other.dt):
311+
(timebaseEqual(self.dt, other.dt)):
308312
dt = self.dt # use dt from first argument
309313
else:
310314
raise ValueError, "Systems have different sampling times"
@@ -435,7 +439,8 @@ def feedback(self, other, sign=-1):
435439
# Figure out the sampling time to use
436440
if (self.dt == None and other.dt != None):
437441
dt = other.dt # use dt from second argument
438-
elif (other.dt == None and self.dt != None) or (self.dt == other.dt):
442+
elif (other.dt == None and self.dt != None) or \
443+
timebaseEqual(self.dt, other.dt):
439444
dt = self.dt # use dt from first argument
440445
else:
441446
raise ValueError, "Systems have different sampling times"

src/xferfcn.py

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@
7979
polyadd, polymul, polyval, roots, sort, sqrt, zeros, squeeze
8080
from scipy.signal import lti
8181
from copy import deepcopy
82-
from lti import Lti
82+
from lti import Lti, timebaseEqual
8383
import statesp
8484

8585
class TransferFunction(Lti):
@@ -98,10 +98,11 @@ class TransferFunction(Lti):
9898
means that the numerator of the transfer function from the 6th input to the
9999
3rd output is set to s^2 + 4s + 8.
100100
101-
Discrete time transfer functions are implemented by using the 'dt'
102-
class variable and setting it to something other than 'None'. If
103-
'dt' has a non-zero value, then it must match whenever two transfer
104-
functions are combined.
101+
Discrete time transfer functions are implemented by using the 'dt' class
102+
variable and setting it to something other than 'None'. If 'dt' has a
103+
non-zero value, then it must match whenever two transfer functions are
104+
combined. If 'dt' is set to True, the system will be treated as a
105+
discrete time system with unspecified sampling time.
105106
"""
106107

107108
def __init__(self, *args):
@@ -288,7 +289,7 @@ def __str__(self, var=None):
288289
outstr += "\n" + numstr + "\n" + dashes + "\n" + denstr + "\n"
289290

290291
# See if this is a discrete time system with specific sampling time
291-
if (self.dt > 0):
292+
if (type(self.dt) != bool and self.dt > 0):
292293
outstr += "\ndt = " + self.dt.__str__() + "\n"
293294

294295
return outstr
@@ -324,7 +325,8 @@ def __add__(self, other):
324325
# Figure out the sampling time to use
325326
if (self.dt == None and other.dt != None):
326327
dt = other.dt # use dt from second argument
327-
elif (other.dt == None and self.dt != None) or (self.dt == other.dt):
328+
elif (other.dt == None and self.dt != None) or \
329+
(timebaseEqual(self.dt, other.dt)):
328330
dt = self.dt # use dt from first argument
329331
else:
330332
raise ValueError, "Systems have different sampling times"

tests/discrete_test.py

Lines changed: 41 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ def setUp(self):
1919
self.siso_ss1c = StateSpace(sys.A, sys.B, sys.C, sys.D, 0.0)
2020
self.siso_ss1d = StateSpace(sys.A, sys.B, sys.C, sys.D, 0.1)
2121
self.siso_ss2d = StateSpace(sys.A, sys.B, sys.C, sys.D, 0.2)
22+
self.siso_ss3d = StateSpace(sys.A, sys.B, sys.C, sys.D, True)
2223

2324
# Two input, two output continuous time system
2425
A = [[-3., 4., 2.], [-1., -3., 0.], [2., 5., 3.]]
@@ -39,19 +40,35 @@ def setUp(self):
3940
self.siso_tf1c = TransferFunction([1, 1], [1, 2, 1], 0)
4041
self.siso_tf1d = TransferFunction([1, 1], [1, 2, 1], 0.1)
4142
self.siso_tf2d = TransferFunction([1, 1], [1, 2, 1], 0.2)
43+
self.siso_tf3d = TransferFunction([1, 1], [1, 2, 1], True)
44+
45+
def testTimebaseEqual(self):
46+
self.assertEqual(timebaseEqual(None, None), True)
47+
self.assertEqual(timebaseEqual(1, 1), True)
48+
self.assertEqual(timebaseEqual(1, 1.0), True)
49+
self.assertEqual(timebaseEqual(True, True), True)
50+
51+
self.assertEqual(timebaseEqual(1, 0.1), False)
52+
self.assertEqual(timebaseEqual(None, 0.1), False)
53+
self.assertEqual(timebaseEqual(None, True), False)
54+
self.assertEqual(timebaseEqual(None, 0), False)
55+
self.assertEqual(timebaseEqual(1, True), False)
4256

4357
def testSystemInitialization(self):
4458
# Check to make sure systems are discrete time with proper variables
4559
self.assertEqual(self.siso_ss1.dt, None)
4660
self.assertEqual(self.siso_ss1c.dt, 0)
4761
self.assertEqual(self.siso_ss1d.dt, 0.1)
4862
self.assertEqual(self.siso_ss2d.dt, 0.2)
63+
self.assertEqual(self.siso_ss3d.dt, True)
4964
self.assertEqual(self.mimo_ss1c.dt, 0)
5065
self.assertEqual(self.mimo_ss1d.dt, 0.1)
5166
self.assertEqual(self.mimo_ss2d.dt, 0.2)
5267
self.assertEqual(self.siso_tf1.dt, None)
5368
self.assertEqual(self.siso_tf1c.dt, 0)
5469
self.assertEqual(self.siso_tf1d.dt, 0.1)
70+
self.assertEqual(self.siso_tf2d.dt, 0.2)
71+
self.assertEqual(self.siso_tf3d.dt, True)
5572

5673
def testCopyConstructor(self):
5774
for sys in (self.siso_ss1, self.siso_ss1c, self.siso_ss1d):
@@ -66,9 +83,11 @@ def testTimebase(self):
6683
self.assertEqual(timebase(self.siso_ss1), None);
6784
self.assertEqual(timebase(self.siso_ss1c), 'ctime');
6885
self.assertEqual(timebase(self.siso_ss1d), 'dtime');
86+
self.assertEqual(timebase(self.siso_ss3d), 'dtime');
6987
self.assertEqual(timebase(self.siso_tf1), None);
7088
self.assertEqual(timebase(self.siso_tf1c), 'ctime');
7189
self.assertEqual(timebase(self.siso_tf1d), 'dtime');
90+
self.assertEqual(timebase(self.siso_tf3d), 'dtime');
7291

7392
def testisdtime(self):
7493
# Constant
@@ -82,6 +101,7 @@ def testisdtime(self):
82101
self.assertEqual(isdtime(self.siso_ss1c, strict=True), False);
83102
self.assertEqual(isdtime(self.siso_ss1d), True);
84103
self.assertEqual(isdtime(self.siso_ss1d, strict=True), True);
104+
self.assertEqual(isdtime(self.siso_ss3d, strict=True), True);
85105

86106
# Transfer function
87107
self.assertEqual(isdtime(self.siso_tf1), True);
@@ -90,6 +110,7 @@ def testisdtime(self):
90110
self.assertEqual(isdtime(self.siso_tf1c, strict=True), False);
91111
self.assertEqual(isdtime(self.siso_tf1d), True);
92112
self.assertEqual(isdtime(self.siso_tf1d, strict=True), True);
113+
self.assertEqual(isdtime(self.siso_tf3d, strict=True), True);
93114

94115
def testisctime(self):
95116
# Constant
@@ -103,14 +124,16 @@ def testisctime(self):
103124
self.assertEqual(isctime(self.siso_ss1c, strict=True), True);
104125
self.assertEqual(isctime(self.siso_ss1d), False);
105126
self.assertEqual(isctime(self.siso_ss1d, strict=True), False);
127+
self.assertEqual(isctime(self.siso_ss3d, strict=True), False);
106128

107129
# Transfer Function
108-
self.assertEqual(isctime(self.siso_ss1), True);
109-
self.assertEqual(isctime(self.siso_ss1, strict=True), False);
110-
self.assertEqual(isctime(self.siso_ss1c), True);
111-
self.assertEqual(isctime(self.siso_ss1c, strict=True), True);
112-
self.assertEqual(isctime(self.siso_ss1d), False);
113-
self.assertEqual(isctime(self.siso_ss1d, strict=True), False);
130+
self.assertEqual(isctime(self.siso_tf1), True);
131+
self.assertEqual(isctime(self.siso_tf1, strict=True), False);
132+
self.assertEqual(isctime(self.siso_tf1c), True);
133+
self.assertEqual(isctime(self.siso_tf1c, strict=True), True);
134+
self.assertEqual(isctime(self.siso_tf1d), False);
135+
self.assertEqual(isctime(self.siso_tf1d, strict=True), False);
136+
self.assertEqual(isctime(self.siso_tf3d, strict=True), False);
114137

115138
def testAddition(self):
116139
# State space addition
@@ -120,10 +143,13 @@ def testAddition(self):
120143
sys = self.siso_ss1d + self.siso_ss1
121144
sys = self.siso_ss1c + self.siso_ss1c
122145
sys = self.siso_ss1d + self.siso_ss1d
146+
sys = self.siso_ss3d + self.siso_ss3d
123147
self.assertRaises(ValueError, StateSpace.__add__, self.mimo_ss1c,
124148
self.mimo_ss1d)
125149
self.assertRaises(ValueError, StateSpace.__add__, self.mimo_ss1d,
126150
self.mimo_ss2d)
151+
self.assertRaises(ValueError, StateSpace.__add__, self.siso_ss1d,
152+
self.siso_ss3d)
127153

128154
# Transfer function addition
129155
sys = self.siso_tf1 + self.siso_tf1d
@@ -132,10 +158,13 @@ def testAddition(self):
132158
sys = self.siso_tf1d + self.siso_tf1
133159
sys = self.siso_tf1c + self.siso_tf1c
134160
sys = self.siso_tf1d + self.siso_tf1d
161+
sys = self.siso_tf2d + self.siso_tf2d
135162
self.assertRaises(ValueError, TransferFunction.__add__, self.siso_tf1c,
136163
self.siso_tf1d)
137164
self.assertRaises(ValueError, TransferFunction.__add__, self.siso_tf1d,
138165
self.siso_tf2d)
166+
self.assertRaises(ValueError, TransferFunction.__add__, self.siso_tf1d,
167+
self.siso_tf3d)
139168

140169
# State space + transfer function
141170
sys = self.siso_ss1c + self.siso_tf1c
@@ -157,6 +186,8 @@ def testMultiplication(self):
157186
self.mimo_ss1d)
158187
self.assertRaises(ValueError, StateSpace.__mul__, self.mimo_ss1d,
159188
self.mimo_ss2d)
189+
self.assertRaises(ValueError, StateSpace.__mul__, self.siso_ss1d,
190+
self.siso_ss3d)
160191

161192
# Transfer function addition
162193
sys = self.siso_tf1 * self.siso_tf1d
@@ -169,6 +200,8 @@ def testMultiplication(self):
169200
self.siso_tf1d)
170201
self.assertRaises(ValueError, TransferFunction.__mul__, self.siso_tf1d,
171202
self.siso_tf2d)
203+
self.assertRaises(ValueError, TransferFunction.__mul__, self.siso_tf1d,
204+
self.siso_tf3d)
172205

173206
# State space * transfer function
174207
sys = self.siso_ss1c * self.siso_tf1c
@@ -189,6 +222,7 @@ def testFeedback(self):
189222
sys = feedback(self.siso_ss1d, self.siso_ss1d)
190223
self.assertRaises(ValueError, feedback, self.mimo_ss1c, self.mimo_ss1d)
191224
self.assertRaises(ValueError, feedback, self.mimo_ss1d, self.mimo_ss2d)
225+
self.assertRaises(ValueError, feedback, self.siso_ss1d, self.siso_ss3d)
192226

193227
# Transfer function addition
194228
sys = feedback(self.siso_tf1, self.siso_tf1d)
@@ -199,6 +233,7 @@ def testFeedback(self):
199233
sys = feedback(self.siso_tf1d, self.siso_tf1d)
200234
self.assertRaises(ValueError, feedback, self.siso_tf1c, self.siso_tf1d)
201235
self.assertRaises(ValueError, feedback, self.siso_tf1d, self.siso_tf2d)
236+
self.assertRaises(ValueError, feedback, self.siso_tf1d, self.siso_tf3d)
202237

203238
# State space, transfer function
204239
sys = feedback(self.siso_ss1c, self.siso_tf1c)

0 commit comments

Comments
 (0)