~ubuntu-branches/ubuntu/karmic/python-scipy/karmic

« back to all changes in this revision

Viewing changes to Lib/signal/ltisys.py

  • Committer: Bazaar Package Importer
  • Author(s): Daniel T. Chen (new)
  • Date: 2005-03-16 02:15:29 UTC
  • Revision ID: james.westby@ubuntu.com-20050316021529-xrjlowsejs0cijig
Tags: upstream-0.3.2
ImportĀ upstreamĀ versionĀ 0.3.2

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#
 
2
# Author: Travis Oliphant 2001
 
3
#
 
4
 
 
5
from filter_design import tf2zpk, zpk2tf, normalize
 
6
from Numeric import product, zeros, concatenate, \
 
7
     array, dot, transpose, arange, ones, Float
 
8
import Numeric
 
9
import scipy.interpolate as interpolate
 
10
import scipy.integrate as integrate
 
11
import scipy.linalg as linalg
 
12
from scipy_base import r_, c_, eye, real, atleast_1d, atleast_2d, poly, \
 
13
     squeeze, diag, asarray
 
14
from Matrix import Matrix as Mat
 
15
 
 
16
def tf2ss(num, den):
 
17
    """Transfer function to state-space representation.
 
18
 
 
19
    Inputs:
 
20
 
 
21
      num, den -- sequences representing the numerator and denominator polynomials.
 
22
 
 
23
    Outputs:
 
24
 
 
25
      A, B, C, D -- state space representation of the system.
 
26
    """
 
27
    # Controller canonical state-space representation.
 
28
    #  if M+1 = len(num) and K+1 = len(den) then we must have M <= K 
 
29
    #  states are found by asserting that X(s) = U(s) / D(s)
 
30
    #  then Y(s) = N(s) * X(s) 
 
31
    #
 
32
    #   A, B, C, and D follow quite naturally.
 
33
    #
 
34
    num, den = normalize(num, den)   # Strips zeros, checks arrays
 
35
    nn = len(num.shape)
 
36
    if nn == 1:
 
37
        num = asarray([num],num.typecode())
 
38
    M = num.shape[1] 
 
39
    K = len(den) 
 
40
    if (M > K):
 
41
        raise ValueError, "Improper transfer function."
 
42
    if (M == 0 or K == 0):  # Null system
 
43
        return array([],Float), array([], Float), array([], Float), \
 
44
               array([], Float)
 
45
 
 
46
    # pad numerator to have same number of columns has denominator
 
47
    num = c_[zeros((num.shape[0],K-M),num.typecode()), num]
 
48
 
 
49
    if num.shape[-1] > 0:
 
50
        D = num[:,0]
 
51
    else:
 
52
        D = array([],Float)
 
53
 
 
54
    if K == 1:
 
55
        return array([], Float), array([], Float), array([], Float), D
 
56
    
 
57
    frow = -array([den[1:]])
 
58
    A = r_[frow, eye(K-2, K-1)]
 
59
    B = eye(K-1, 1)
 
60
    C = num[:,1:] - num[:,0] * den[1:]
 
61
    return A, B, C, D
 
62
 
 
63
def none_to_empty(arg):
 
64
    if arg is None:
 
65
        return []
 
66
    else:
 
67
        return arg
 
68
 
 
69
def abcd_normalize(A=None, B=None, C=None, D=None):
 
70
    """Check state-space matrices and ensure they are rank-2.
 
71
    """
 
72
    A, B, C, D = map(none_to_empty, (A, B, C, D))
 
73
    A, B, C, D = map(atleast_2d, (A, B, C, D))
 
74
 
 
75
    if ((len(A.shape) > 2) or (len(B.shape) > 2) or \
 
76
        (len(C.shape) > 2) or (len(D.shape) > 2)):
 
77
        raise ValueError, "A, B, C, D arrays can be no larger than rank-2."
 
78
 
 
79
    MA, NA = A.shape
 
80
    MB, NB = B.shape
 
81
    MC, NC = C.shape
 
82
    MD, ND = D.shape
 
83
 
 
84
    if (MC == 0) and (NC == 0) and (MD != 0) and (NA != 0):
 
85
        MC, NC = MD, NA
 
86
        C = zeros((MC, NC))
 
87
    if (MB == 0) and (NB == 0) and (MA != 0) and (ND != 0):
 
88
        MB, NB = MA, ND
 
89
        B = zeros(MB, NB)
 
90
    if (MD == 0) and (ND == 0) and (MC != 0) and (NB != 0):
 
91
        MD, ND = MC, NB
 
92
        D = zeros(MD, ND)
 
93
    if (MA == 0) and (NA == 0) and (MB != 0) and (NC != 0):
 
94
        MA, NA = MB, NC
 
95
        A = zeros(MA, NA)
 
96
 
 
97
    if MA != NA:
 
98
        raise ValueError, "A must be square."
 
99
    if MA != MB:
 
100
        raise ValueError, "A and B must have the same number of rows."
 
101
    if NA != NC:
 
102
        raise ValueError, "A and C must have the same number of columns."
 
103
    if MD != MC:
 
104
        raise ValueError, "C and D must have the same number of rows."
 
105
    if ND != NB:
 
106
        raise ValueErrro, "B and D must have the same number of columns."
 
107
 
 
108
    return A, B, C, D
 
109
    
 
110
def ss2tf(A, B, C, D, input=0):
 
111
    """State-space to transfer function.
 
112
 
 
113
    Inputs:
 
114
 
 
115
      A, B, C, D -- state-space representation of linear system.
 
116
      input -- For multiple-input systems, the input to use.
 
117
 
 
118
    Outputs:
 
119
 
 
120
      num, den -- Numerator and denominator polynomials (as sequences)
 
121
                  respectively.
 
122
    """
 
123
    # transfer function is C (sI - A)**(-1) B + D
 
124
    A, B, C, D = map(asarray, (A, B, C, D))
 
125
    # Check consistency and 
 
126
    #     make them all rank-2 arrays
 
127
    A, B, C, D = abcd_normalize(A, B, C, D)
 
128
 
 
129
    nout, nin = D.shape
 
130
    if input >= nin:
 
131
        raise ValueError, "System does not have the input specified."
 
132
 
 
133
    # make MOSI from possibly MOMI system.
 
134
    if B.shape[-1] != 0:
 
135
        B = B[:,input]
 
136
    B.shape = (B.shape[0],1)
 
137
    if D.shape[-1] != 0:
 
138
        D = D[:,input]
 
139
 
 
140
    den = poly(A)
 
141
 
 
142
    if (product(B.shape) == 0) and (product(C.shape) == 0):
 
143
        num = Numeric.ravel(D)
 
144
        if (product(D.shape) == 0) and (product(A.shape) == 0):
 
145
            den = []
 
146
        end
 
147
        return num, den
 
148
 
 
149
    num_states = A.shape[0] 
 
150
    type_test = A[:,0] + B[:,0] + C[0,:] + D
 
151
    num = Numeric.zeros((nout, num_states+1),type_test.typecode())
 
152
    for k in range(nout):
 
153
        Ck = atleast_2d(C[k,:])
 
154
        num[k] = poly(A - dot(B,Ck)) + (D[k]-1)*den
 
155
 
 
156
    return num, den
 
157
 
 
158
def zpk2ss(z,p,k):
 
159
    """Zero-pole-gain representation to state-space representation
 
160
 
 
161
    Inputs:
 
162
 
 
163
      z, p, k -- zeros, poles (sequences), and gain of system
 
164
 
 
165
    Outputs:
 
166
 
 
167
      A, B, C, D -- state-space matrices.
 
168
    """
 
169
    return tf2ss(*zpk2tf(z,p,k))
 
170
 
 
171
def ss2zpk(A,B,C,D,input=0):
 
172
    """State-space representation to zero-pole-gain representation.
 
173
 
 
174
    Inputs:
 
175
 
 
176
      A, B, C, D -- state-space matrices.
 
177
      input -- for multiple-input systems, the input to use.
 
178
 
 
179
    Outputs:
 
180
 
 
181
      z, p, k -- zeros and poles in sequences and gain constant.
 
182
    """    
 
183
    return tf2zpk(*ss2tf(A,B,C,D,input=input))
 
184
 
 
185
class lti:
 
186
    """Linear Time Invariant class which simplifies representation.
 
187
    """
 
188
    def __init__(self,*args,**kwords):
 
189
        """Initialize the LTI system using either:
 
190
           (numerator, denominator)
 
191
           (zeros, poles, gain)
 
192
           (A, B, C, D) -- state-space.
 
193
        """
 
194
        N = len(args)
 
195
        if N == 2:  # Numerator denominator transfer function input
 
196
            self.__dict__['num'], self.__dict__['den'] = normalize(*args)
 
197
            self.__dict__['zeros'], self.__dict__['poles'], \
 
198
            self.__dict__['gain'] = tf2zpk(*args)
 
199
            self.__dict__['A'], self.__dict__['B'], \
 
200
                                self.__dict__['C'], \
 
201
                                self.__dict__['D'] = tf2ss(*args)
 
202
            self.inputs = 1
 
203
            if len(self.num.shape) > 1:
 
204
                self.outputs = self.num.shape[0]
 
205
            else:
 
206
                self.outputs = 1
 
207
        elif N == 3:      # Zero-pole-gain form
 
208
            self.__dict__['zeros'], self.__dict__['poles'], \
 
209
                                    self.__dict__['gain'] = args
 
210
            self.__dict__['num'], self.__dict__['den'] = zpk2tf(*args)
 
211
            self.__dict__['A'], self.__dict__['B'], \
 
212
                                self.__dict__['C'], \
 
213
                                self.__dict__['D'] = zpk2ss(*args)
 
214
            self.inputs = 1
 
215
            if len(self.zeros.shape) > 1:
 
216
                self.outputs = self.zeros.shape[0]
 
217
            else:
 
218
                self.outputs = 1
 
219
        elif N == 4:       # State-space form
 
220
            self.__dict__['A'], self.__dict__['B'], \
 
221
                                self.__dict__['C'], \
 
222
                                self.__dict__['D'] = abcd_normalize(*args)
 
223
            self.__dict__['zeros'], self.__dict__['poles'], \
 
224
                                    self.__dict__['gain'] = ss2zpk(*args)
 
225
            self.__dict__['num'], self.__dict__['den'] = ss2tf(*args)
 
226
            self.inputs = B.shape[-1]
 
227
            self.outputs = C.shape[0]
 
228
        else:
 
229
            raise ValueError, "Needs 2, 3, or 4 arguments."
 
230
 
 
231
    def __setattr__(self, attr, val):
 
232
        if attr in ['num','den']:
 
233
            self.__dict__[attr] = val
 
234
            self.__dict__['zeros'], self.__dict__['poles'], \
 
235
                                    self.__dict__['gain'] = \
 
236
                                    tf2zpk(self.num, self.den)
 
237
            self.__dict__['A'], self.__dict__['B'], \
 
238
                                self.__dict__['C'], \
 
239
                                self.__dict__['D'] = \
 
240
                                tf2ss(self.num, self.den)
 
241
        elif attr in ['zeros', 'poles', 'gain']:
 
242
            self.__dict__[attr] = val
 
243
            self.__dict__['num'], self.__dict__['den'] = \
 
244
                                  zpk2tf(self.zeros,
 
245
                                         self.poles, self.gain)
 
246
            self.__dict__['A'], self.__dict__['B'], \
 
247
                                self.__dict__['C'], \
 
248
                                self.__dict__['D'] = \
 
249
                                zpk2ss(self.zeros,
 
250
                                       self.poles, self.gain)
 
251
        elif attr in ['A', 'B', 'C', 'D']:
 
252
            self.__dict__[attr] = val
 
253
            self.__dict__['zeros'], self.__dict__['poles'], \
 
254
                                    self.__dict__['gain'] = \
 
255
                                    ss2zpk(self.A, self.B,
 
256
                                           self.C, self.D)
 
257
            self.__dict__['num'], self.__dict__['den'] = \
 
258
                                  ss2tf(self.A, self.B,
 
259
                                        self.C, self.D)            
 
260
        else:
 
261
            self.__dict__[attr] = val
 
262
 
 
263
    def impulse(self, X0=None, T=None, N=None):
 
264
        return impulse(self, X0=X0, T=T, N=N)
 
265
 
 
266
    def step(self, X0=None, T=None, N=None):
 
267
        return step(self, X0=X0, T=T, N=N)
 
268
 
 
269
    def output(self, U, T, X0=None):
 
270
        return lsim(self, U, T, X0=X0)
 
271
 
 
272
 
 
273
def lsim2(system, U, T, X0=None):
 
274
    """Simulate output of a continuous-time linear system, using ODE solver.
 
275
 
 
276
    Inputs:
 
277
 
 
278
      system -- an instance of the LTI class or a tuple describing the
 
279
                system.  The following gives the number of elements in
 
280
                the tuple and the interpretation.
 
281
                  2 (num, den)
 
282
                  3 (zeros, poles, gain)
 
283
                  4 (A, B, C, D)
 
284
      U -- an input array describing the input at each time T
 
285
           (linear interpolation is assumed between given times).
 
286
           If there are multiple inputs, then each column of the 
 
287
           rank-2 array represents an input.
 
288
      T -- the time steps at which the input is defined and at which
 
289
           the output is desired.
 
290
      X0 -- (optional, default=0) the initial conditions on the state vector.
 
291
 
 
292
    Outputs: (T, yout, xout)
 
293
 
 
294
      T -- the time values for the output.
 
295
      yout -- the response of the system.
 
296
      xout -- the time-evolution of the state-vector.
 
297
    """
 
298
    # system is an lti system or a sequence
 
299
    #  with 2 (num, den)
 
300
    #       3 (zeros, poles, gain)
 
301
    #       4 (A, B, C, D)
 
302
    #  describing the system
 
303
    #  U is an input vector at times T
 
304
    #   if system describes multiple outputs
 
305
    #   then U can be a rank-2 array with the number of columns
 
306
    #   being the number of inputs
 
307
 
 
308
    # rather than use lsim, use direct integration and matrix-exponential.
 
309
    if isinstance(system, lti):        
 
310
        sys = system
 
311
    else:
 
312
        sys = lti(*system)
 
313
    U = atleast_1d(U)
 
314
    T = atleast_1d(T)
 
315
    if len(U.shape) == 1:
 
316
        U.shape = (U.shape[0],1)
 
317
    sU = U.shape        
 
318
    if len(T.shape) != 1:
 
319
        raise ValueError, "T must be a rank-1 array."
 
320
    if sU[0] != len(T):
 
321
        raise ValueError, "U must have the same number of rows as elements in T."
 
322
    if sU[1] != sys.inputs:
 
323
        raise ValueError, "System does not define that many inputs."
 
324
 
 
325
    if X0 is None:
 
326
        X0 = zeros(sys.B.shape[0],sys.A.typecode())
 
327
 
 
328
    # for each output point directly integrate assume zero-order hold
 
329
    #   or linear interpolation.
 
330
 
 
331
    ufunc = interpolate.linear_1d(T, U, axis=0, bounds_error=0, fill_value=0)
 
332
 
 
333
    def fprime(x, t, sys, ufunc):
 
334
        return dot(sys.A,x) + squeeze(dot(sys.B,ufunc([t])))
 
335
 
 
336
    xout = integrate.odeint(fprime, X0, T, args=(sys, ufunc))
 
337
    yout = dot(sys.C,transpose(xout)) + dot(sys.D,transpose(U))
 
338
    return T, squeeze(transpose(yout)), xout
 
339
 
 
340
 
 
341
def lsim(system, U, T, X0=None, interp=1):
 
342
    """Simulate output of a continuous-time linear system.
 
343
 
 
344
    Inputs:
 
345
 
 
346
      system -- an instance of the LTI class or a tuple describing the
 
347
                system.  The following gives the number of elements in
 
348
                the tuple and the interpretation.
 
349
                  2 (num, den)
 
350
                  3 (zeros, poles, gain)
 
351
                  4 (A, B, C, D)
 
352
      U -- an input array describing the input at each time T
 
353
           (interpolation is assumed between given times).
 
354
           If there are multiple inputs, then each column of the 
 
355
           rank-2 array represents an input.
 
356
      T -- the time steps at which the input is defined and at which
 
357
           the output is desired.
 
358
      X0 -- (optional, default=0) the initial conditions on the state vector.
 
359
      interp -- linear (1) or zero-order hold (0) interpolation
 
360
 
 
361
    Outputs: (T, yout, xout)
 
362
 
 
363
      T -- the time values for the output.
 
364
      yout -- the response of the system.
 
365
      xout -- the time-evolution of the state-vector.
 
366
    """
 
367
    # system is an lti system or a sequence
 
368
    #  with 2 (num, den)
 
369
    #       3 (zeros, poles, gain)
 
370
    #       4 (A, B, C, D)
 
371
    #  describing the system
 
372
    #  U is an input vector at times T
 
373
    #   if system describes multiple inputs
 
374
    #   then U can be a rank-2 array with the number of columns
 
375
    #   being the number of inputs
 
376
    if isinstance(system, lti):
 
377
        sys = system
 
378
    else:
 
379
        sys = lti(*system)
 
380
    U = atleast_1d(U)
 
381
    T = atleast_1d(T)
 
382
    if len(U.shape) == 1:
 
383
        U.shape = (U.shape[0],1)
 
384
    sU = U.shape        
 
385
    if len(T.shape) != 1:
 
386
        raise ValueError, "T must be a rank-1 array."
 
387
    if sU[0] != len(T):
 
388
        raise ValueError, "U must have the same number of rows as elements in T."
 
389
    if sU[1] != sys.inputs:
 
390
        raise ValueError, "System does not define that many inputs."
 
391
 
 
392
    if X0 is None:
 
393
        X0 = zeros(sys.B.shape[0],sys.A.typecode())
 
394
 
 
395
    xout = zeros((len(T),sys.B.shape[0]),sys.A.typecode())
 
396
    xout[0] = X0
 
397
    A = sys.A
 
398
    AT, BT = transpose(sys.A), transpose(sys.B)
 
399
    dt = T[1]-T[0]
 
400
    lam, v = linalg.eig(A)
 
401
    vt = transpose(v)
 
402
    vti = linalg.inv(vt)
 
403
    GT = dot(dot(vti,diag(Numeric.exp(dt*lam))),vt).astype(xout.typecode())
 
404
    ATm1 = linalg.inv(AT)
 
405
    ATm2 = dot(ATm1,ATm1)
 
406
    I = eye(A.shape[0],typecode=A.typecode())
 
407
    GTmI = GT-I
 
408
    F1T = dot(dot(BT,GTmI),ATm1)
 
409
    if interp:
 
410
        F2T = dot(BT,dot(GTmI,ATm2)/dt - ATm1)
 
411
 
 
412
    for k in xrange(1,len(T)):
 
413
        dt1 = T[k] - T[k-1]
 
414
        if dt1 != dt:
 
415
            dt = dt1
 
416
            GT = dot(dot(vti,diag(Numeric.exp(dt*lam))),vt).astype(xout.typecode())
 
417
            GTmI = GT-I
 
418
            F1T = dot(dot(BT,GTmI),ATm1)
 
419
            if interp:
 
420
                F2T = dot(BT,dot(GTmI,ATm2)/dt - ATm1)
 
421
 
 
422
        xout[k] = dot(xout[k-1],GT) + dot(U[k-1],F1T)
 
423
        if interp:
 
424
            xout[k] = xout[k] + dot((U[k]-U[k-1]),F2T)
 
425
 
 
426
    yout = squeeze(dot(U,transpose(sys.D))) + squeeze(dot(xout,transpose(sys.C)))
 
427
    return T, squeeze(yout), squeeze(xout)
 
428
 
 
429
 
 
430
def impulse(system, X0=None, T=None, N=None):
 
431
    """Impulse response of continuous-time system.
 
432
 
 
433
    Inputs:
 
434
 
 
435
      system -- an instance of the LTI class or a tuple with 2, 3, or 4
 
436
                elements representing (num, den), (zero, pole, gain), or
 
437
                (A, B, C, D) representation of the system.
 
438
      X0 -- (optional, default = 0) inital state-vector.
 
439
      T -- (optional) time points (autocomputed if not given).
 
440
      N -- (optional) number of time points to autocompute (100 if not given).
 
441
 
 
442
    Ouptuts: (T, yout)
 
443
 
 
444
      T -- output time points,
 
445
      yout -- impulse response of system (except possible singularities at 0).
 
446
    """
 
447
    if isinstance(system, lti):
 
448
        sys = system
 
449
    else:
 
450
        sys = lti(*system)
 
451
    if X0 is None:
 
452
        B = sys.B
 
453
    else:
 
454
        B = sys.B + X0
 
455
    if N is None:
 
456
        N = 100
 
457
    if T is None:
 
458
        vals = linalg.eigvals(sys.A)
 
459
        tc = 1.0/min(abs(real(vals)))
 
460
        T = arange(0,7*tc,7*tc / float(N))
 
461
    h = zeros(T.shape, sys.A.typecode())
 
462
    s,v = linalg.eig(sys.A)
 
463
    vi = linalg.inv(v)
 
464
    C = sys.C
 
465
    for k in range(len(h)):
 
466
        es = diag(Numeric.exp(s*T[k]))
 
467
        eA = (dot(dot(v,es),vi)).astype(h.typecode())
 
468
        h[k] = squeeze(dot(dot(C,eA),B))
 
469
    return T, h
 
470
 
 
471
def step(system, X0=None, T=None, N=None):
 
472
    """Step response of continuous-time system.
 
473
 
 
474
    Inputs:
 
475
 
 
476
      system -- an instance of the LTI class or a tuple with 2, 3, or 4
 
477
                elements representing (num, den), (zero, pole, gain), or
 
478
                (A, B, C, D) representation of the system.
 
479
      X0 -- (optional, default = 0) inital state-vector.
 
480
      T -- (optional) time points (autocomputed if not given).
 
481
      N -- (optional) number of time points to autocompute (100 if not given).
 
482
 
 
483
    Ouptuts: (T, yout)
 
484
 
 
485
      T -- output time points,
 
486
      yout -- step response of system.
 
487
    """
 
488
    if isinstance(system, lti):
 
489
        sys = system
 
490
    else:
 
491
        sys = lti(*system)
 
492
    if N is None:
 
493
        N = 100
 
494
    if T is None:
 
495
        vals = linalg.eigvals(sys.A)
 
496
        tc = 1.0/min(abs(real(vals)))
 
497
        T = arange(0,7*tc,7*tc / float(N))
 
498
    U = ones(T.shape, sys.A.typecode())
 
499
    vals = lsim(sys, U, T, X0=X0)
 
500
    return vals[0], vals[1]
 
501