3
3
Frequency response data representation and functions.
4
4
5
5
This file contains the FRD class and also functions
6
- that operate on transfer functions. This is the primary representation
6
+ that operate on FRD data. This is the primary representation
7
7
for the python-control library.
8
8
9
9
Routines in this module:
10
10
11
11
FRD.__init__
12
- FRD._truncatecoeff
13
12
FRD.copy
14
13
FRD.__str__
15
14
FRD.__neg__
26
25
FRD.pole
27
26
FRD.zero
28
27
FRD.feedback
29
- FRD.returnScipySignalLti
30
28
FRD._common_den
31
- _tfpolyToString
32
- _addSISO
33
29
_convertToFRD
34
30
35
31
"""
36
32
37
33
"""Copyright (c) 2010 by California Institute of Technology
38
- and 2012 Delft University of Technology
34
+ Copyright (c) 2012 by Delft University of Technology
39
35
All rights reserved.
40
36
41
37
Redistribution and use in source and binary forms, with or without
49
45
notice, this list of conditions and the following disclaimer in the
50
46
documentation and/or other materials provided with the distribution.
51
47
52
- 3. Neither the name of the California Institute of Technology nor
48
+ 3. Neither the names of the California Institute of Technology nor
49
+ the Delft University of Technology nor
53
50
the names of its contributors may be used to endorse or promote
54
51
products derived from this software without specific prior
55
52
written permission.
67
64
OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
68
65
SUCH DAMAGE.
69
66
70
- Author: M.M. (Rene) van Paassen
67
+ Author: M.M. (Rene) van Paassen (using xferfcn.py as basis)
71
68
Date: 02 Oct 12
72
69
Revised:
73
70
77
74
78
75
# External function declarations
79
76
from numpy import angle , any , array , empty , finfo , insert , ndarray , ones , \
80
- polyadd , polymul , polyval , roots , sort , sqrt , zeros , squeeze
81
- from scipy .signal import lti
77
+ polyadd , polymul , polyval , roots , sort , sqrt , zeros , squeeze , inner
78
+ from scipy .interpolate import interp1d
82
79
from copy import deepcopy
83
80
from lti import Lti
84
81
import statesp
@@ -87,7 +84,7 @@ class FRD(Lti):
87
84
88
85
"""The FRD class represents (measured?) frequency response
89
86
TF instances and functions.
90
-
87
+ 4
91
88
The FRD class is derived from the Lti parent class. It is used
92
89
throughout the python-control library to represent systems in frequency
93
90
response data form.
@@ -99,11 +96,15 @@ class FRD(Lti):
99
96
100
97
>>> frdata[2][5] = numpy.array([1., 0.8-0.2j, 0.2-0.8j])
101
98
102
- means that the frequency response from the 6th input to the
103
- 3rd output at the frequencies defined in omega is set the array above.
99
+ means that the frequency response from the 6th input to the 3rd
100
+ output at the frequencies defined in omega is set to the array
101
+ above, i.e. the rows represent the outputs and the columns
102
+ represent the inputs.
104
103
105
104
"""
106
105
106
+ epsw = 1e-8
107
+
107
108
def __init__ (self , * args ):
108
109
"""Construct a transfer function.
109
110
@@ -116,21 +117,26 @@ def __init__(self, *args):
116
117
To call the copy constructor, call FRD(sys), where sys is a
117
118
FRD object.
118
119
120
+ To construct frequency response data for an existing Lti
121
+ object, other than an FRD, call FRD(sys, omega)
122
+
119
123
"""
120
124
121
125
if len (args ) == 2 :
122
126
if not isinstance (args [0 ], FRD ) and isinstance (args [0 ], Lti ):
123
127
# not an FRD, but still a system, second argument should be
124
128
# the frequency range
125
129
otherlti = args [0 ]
126
-
127
- self .omega = array (args [1 ].sort (), dtype = float )
130
+ print args [1 ]
131
+ self .omega = array (args [1 ], dtype = float )
132
+ self .omega .sort ()
133
+ numfreq = len (self .omega )
128
134
129
135
# calculate frequency response at my points
130
136
self .fresp = empty (
131
137
(otherlti .outputs , otherlti .inputs , numfreq ),
132
138
dtype = complex )
133
- for k , w in enumerate (omega ):
139
+ for k , w in enumerate (self . omega ):
134
140
self .fresp [:, :, k ] = otherlti .evalfr (w )
135
141
136
142
else :
@@ -158,6 +164,13 @@ def __init__(self, *args):
158
164
else :
159
165
raise ValueError ("Needs 1 or 2 arguments; receivd %i." % len (args ))
160
166
167
+ # create interpolation functions
168
+ self .ifunc = empty ((self .fresp .shape [1 ], self .fresp .shape [0 ]),
169
+ dtype = interp1d )
170
+ for i in range (self .fresp .shape [1 ]):
171
+ for j in range (self .fresp .shape [0 ]):
172
+ self .ifunc [i ,j ] = interp1d (self .omega , self .fresp [i , j , :])
173
+
161
174
Lti .__init__ (self , self .fresp .shape [1 ], self .fresp .shape [0 ])
162
175
163
176
def __str__ (self ):
@@ -180,7 +193,7 @@ def __str__(self):
180
193
def __neg__ (self ):
181
194
"""Negate a transfer function."""
182
195
183
- return FRD (self .omega , - self .fresp )
196
+ return FRD (- self .fresp , self .omega )
184
197
185
198
def __add__ (self , other ):
186
199
"""Add two LTI objects (parallel connection)."""
@@ -203,7 +216,7 @@ def __add__(self, other):
203
216
raise ValueError ("The first summand has %i output(s), but the \
204
217
second has %i." % (self .outputs , other .outputs ))
205
218
206
- return FRD (other . omega , self .frd + other .frd )
219
+ return FRD (self .fresp + other .fresp , other . omega )
207
220
208
221
def __radd__ (self , other ):
209
222
"""Right add two LTI objects (parallel connection)."""
@@ -226,9 +239,9 @@ def __mul__(self, other):
226
239
# Convert the second argument to a transfer function.
227
240
if isinstance (other , (int , float , long , complex )):
228
241
other = _convertToFRD (other , inputs = self .inputs ,
229
- outputs = self .inputs )
242
+ outputs = self .inputs , omega = self . omega )
230
243
else :
231
- other = _convertToFRD (other )
244
+ other = _convertToFRD (other , omega = self . omega )
232
245
233
246
# Check that the input-output sizes are consistent.
234
247
if self .inputs != other .outputs :
@@ -268,9 +281,9 @@ def __div__(self, other):
268
281
269
282
if isinstance (other , (int , float , long , complex )):
270
283
other = _convertToFRD (other , inputs = self .inputs ,
271
- outputs = self .inputs )
284
+ outputs = self .inputs , omega = self . omega )
272
285
else :
273
- other = _convertToFRD (other )
286
+ other = _convertToFRD (other , omega = self . omega )
274
287
275
288
276
289
if (self .inputs > 1 or self .outputs > 1 or
@@ -288,9 +301,9 @@ def __rdiv__(self, other):
288
301
"""Right divide two LTI objects."""
289
302
if isinstance (other , (int , float , long , complex )):
290
303
other = _convertToFRD (other , inputs = self .inputs ,
291
- outputs = self .inputs )
304
+ outputs = self .inputs , omega = self . omega )
292
305
else :
293
- other = _convertToFRD (other )
306
+ other = _convertToFRD (other , omega = self . omega )
294
307
295
308
if (self .inputs > 1 or self .outputs > 1 or
296
309
other .inputs > 1 or other .outputs > 1 ):
@@ -322,8 +335,7 @@ def evalfr(self, omega):
322
335
323
336
for i in range (self .outputs ):
324
337
for j in range (self .inputs ):
325
- out [i ][j ] = (polyval (self .num [i ][j ], omega * 1.j ) /
326
- polyval (self .den [i ][j ], omega * 1.j ))
338
+ out [i ,j ] = self .ifunc [i ,j ](omega )
327
339
328
340
return out
329
341
@@ -348,71 +360,43 @@ def freqresp(self, omega):
348
360
349
361
for i in range (self .outputs ):
350
362
for j in range (self .inputs ):
351
- fresp = map (lambda w : (polyval (self .num [i ][j ], w * 1.j ) /
352
- polyval (self .den [i ][j ], w * 1.j )), omega )
353
- fresp = array (fresp )
354
-
363
+ fresp = self .ifunc [i ,j ](omega )
355
364
mag [i , j , :] = abs (fresp )
356
365
phase [i , j , :] = angle (fresp )
357
366
358
367
return mag , phase , omega
359
368
360
369
def feedback (self , other , sign = - 1 ):
361
- """Feedback interconnection between two LTI objects."""
362
-
363
- other = _convertToFRD (other )
364
-
365
- if (self .inputs > 1 or self .outputs > 1 or
366
- other .inputs > 1 or other .outputs > 1 ):
367
- # TODO: MIMO feedback
368
- raise NotImplementedError ("FRD.feedback is currently \
369
- only implemented for SISO functions." )
370
-
371
- num1 = self .num [0 ][0 ]
372
- den1 = self .den [0 ][0 ]
373
- num2 = other .num [0 ][0 ]
374
- den2 = other .den [0 ][0 ]
375
-
376
- num = polymul (num1 , den2 )
377
- den = polyadd (polymul (den2 , den1 ), - sign * polymul (num2 , num1 ))
378
-
379
- return FRD (num , den )
380
-
381
- # For MIMO or SISO systems, the analytic expression is
382
- # self / (1 - sign * other * self)
383
- # But this does not work correctly because the state size will be too
384
- # large.
385
-
386
- def returnScipySignalLti (self ):
387
- """Return a list of a list of scipy.signal.lti objects.
388
-
389
- For instance,
370
+ """Feedback interconnection between two FRD objects."""
390
371
391
- >>> out = tfobject.returnScipySignalLti()
392
- >>> out[3][5]
393
-
394
- is a signal.scipy.lti object corresponding to the transfer function from
395
- the 6th input to the 4th output.
396
-
397
- """
398
-
399
- # Preallocate the output.
400
- out = [[[] for j in range (self .inputs )] for i in range (self .outputs )]
372
+ other = _convertToFRD (other , omega = self .omega )
401
373
402
- for i in range (self .outputs ):
403
- for j in range (self .inputs ):
404
- out [i ][j ] = lti (self .num [i ][j ], self .den [i ][j ])
405
-
406
- return out
374
+ if (self .outputs != other .inputs or
375
+ self .inputs != other .outputs ):
376
+ raise ValueError (
377
+ "FRD.feedback, inputs/outputs mismatch" )
378
+ fresp = empty (self .outputs , self .inputs , len (other .omega ))
379
+ # TODO: vectorize this
380
+ # TODO: handle omega re-mapping
381
+ for k , w in enumerate (other .omega ):
382
+ for i in range (0 , self .inputs ):
383
+ for j in range (0 , self .outputs ):
384
+ fresp [i , j , k ] = \
385
+ self .fresp [i , j , k ] / \
386
+ (1.0 + inner (self .fresp [i , :, k ], other .fresp [:, i , k ]))
387
+
388
+ return FRD (fresp , other .omega )
407
389
408
390
409
- def _convertToFRD (sys , omega ):
410
- """Convert a system to transfer function form (if needed).
391
+ def _convertToFRD (sys , omega , inputs = 1 , outputs = 1 ):
392
+ """Convert a system to frequency response data form (if needed).
411
393
412
- If sys is already a transfer function, then it is returned. If sys is a
413
- state space object, then it is converted to a transfer function and
414
- returned. If sys is a scalar, then the number of inputs and outputs can be
415
- specified manually, as in:
394
+ If sys is already an frd, and its frequency range matches or
395
+ overlaps the range given in omega then it is returned. If sys is
396
+ another Lti object or a transfer function, then it is converted to
397
+ a frequency response data at the specified omega. If sys is a
398
+ scalar, then the number of inputs and outputs can be specified
399
+ manually, as in:
416
400
417
401
>>> sys = _convertToFRD(3.) # Assumes inputs = outputs = 1
418
402
>>> sys = _convertToFRD(1., inputs=3, outputs=2)
@@ -424,7 +408,8 @@ def _convertToFRD(sys, omega):
424
408
425
409
if isinstance (sys , FRD ):
426
410
427
- if (abs (omega - sys .omega ) < eps ).all ():
411
+ omega .sort ()
412
+ if (abs (omega - sys .omega ) < FRD .epsw ).all ():
428
413
# frequencies match, and system was already frd; simply use
429
414
return sys
430
415
@@ -433,56 +418,33 @@ def _convertToFRD(sys, omega):
433
418
omega = omega [omega <= max (sys .omega )]
434
419
if not omega :
435
420
raise ValueError ("Frequency ranges of FRD do not overlap" )
436
- return FRDsys
437
-
438
- elif isinstance (sys , statesp .StateSpace ):
439
- try :
440
- from slycot import tb04ad
441
- if len (kw ):
442
- raise TypeError ("If sys is a StateSpace, \
443
- _convertToFRD cannot take keywords." )
444
-
445
- # Use Slycot to make the transformation
446
- # Make sure to convert system matrices to numpy arrays
447
- tfout = tb04ad (sys .states , sys .inputs , sys .outputs , array (sys .A ),
448
- array (sys .B ), array (sys .C ), array (sys .D ), tol1 = 0.0 )
449
-
450
- # Preallocate outputs.
451
- num = [[[] for j in range (sys .inputs )] for i in range (sys .outputs )]
452
- den = [[[] for j in range (sys .inputs )] for i in range (sys .outputs )]
453
-
454
- for i in range (sys .outputs ):
455
- for j in range (sys .inputs ):
456
- num [i ][j ] = list (tfout [6 ][i , j , :])
457
- # Each transfer function matrix row has a common denominator.
458
- den [i ][j ] = list (tfout [5 ][i , :])
459
- # print num
460
- # print den
461
- except ImportError :
462
- # If slycot is not available, use signal.lti (SISO only)
463
- if (sys .inputs != 1 or sys .outputs != 1 ):
464
- raise TypeError ("No support for MIMO without slycot" )
465
-
466
- lti_sys = lti (sys .A , sys .B , sys .C , sys .D )
467
- num = squeeze (lti_sys .num )
468
- den = squeeze (lti_sys .den )
469
- print num
470
- print den
421
+
422
+ # if there would be data beyond the extremes, add omega points at the
423
+ # edges
424
+ if omega [0 ] - sys .omega [0 ] > FRD .epsw :
425
+ omega .insert (sys .omega [0 ], 0 )
426
+ if sys .omega [- 1 ] - omega [- 1 ] > FRD .epsw :
427
+ omega .append (sys .omega [- 1 ])
428
+ print "Adjusting frequency range in FRD"
429
+
430
+ fresp = empty ((sys .outputs , sys .inputs , len (omega )), dtype = complex )
431
+ for k , w in enumerate (omega ):
432
+ fresp [:, :, k ] = sys .evalfr (w )
433
+
434
+ return FRD (fresp , omega )
471
435
472
- return FRD (num , den )
473
- elif isinstance (sys , (int , long , float , complex )):
474
- if "inputs" in kw :
475
- inputs = kw ["inputs" ]
476
- else :
477
- inputs = 1
478
- if "outputs" in kw :
479
- outputs = kw ["outputs" ]
480
- else :
481
- outputs = 1
436
+ elif isinstance (sys , Lti ):
437
+ omega .sort ()
438
+ fresp = empty ((sys .outputs , sys .inputs , len (omega )), dtype = complex )
439
+ for k , w in enumerate (omega ):
440
+ fresp [:, :, k ] = sys .evalfr (w )
441
+
442
+ return FRD (fresp , omega )
482
443
483
- num = [[[sys ] for j in range (inputs )] for i in range (outputs )]
484
- den = [[[1 ] for j in range (inputs )] for i in range (outputs )]
444
+ elif isinstance (sys , (int , long , float , complex )):
485
445
486
- return FRD (num , den )
446
+ fresp = ones ((outputs , inputs , len (omega )), dtype = float )* sys
447
+
448
+ return FRD (fresp , omega )
487
449
else :
488
450
raise TypeError ("Can't convert given type to FRD system." )
0 commit comments