1+ # Copyright (c) 2010 by California Institute of Technology
2+ # Copyright (c) 2012 by Delft University of Technology
3+ # All rights reserved.
4+ #
5+ # Redistribution and use in source and binary forms, with or without
6+ # modification, are permitted provided that the following conditions
7+ # are met:
8+ #
9+ # 1. Redistributions of source code must retain the above copyright
10+ # notice, this list of conditions and the following disclaimer.
11+ #
12+ # 2. Redistributions in binary form must reproduce the above copyright
13+ # notice, this list of conditions and the following disclaimer in the
14+ # documentation and/or other materials provided with the distribution.
15+ #
16+ # 3. Neither the names of the California Institute of Technology nor
17+ # the Delft University of Technology nor
18+ # the names of its contributors may be used to endorse or promote
19+ # products derived from this software without specific prior
20+ # written permission.
21+ #
22+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
23+ # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
24+ # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
25+ # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL CALTECH
26+ # OR THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
27+ # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
28+ # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
29+ # USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
30+ # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
31+ # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
32+ # OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33+ # SUCH DAMAGE.
34+ #
35+ # Author: M.M. (Rene) van Paassen (using xferfcn.py as basis)
36+ # Date: 02 Oct 12
37+
138from __future__ import division
39+
240"""
341Frequency response data representation and functions.
442
543This module contains the FRD class and also functions that operate on
644FRD data.
745"""
846
9- """Copyright (c) 2010 by California Institute of Technology
10- Copyright (c) 2012 by Delft University of Technology
11- All rights reserved.
12-
13- Redistribution and use in source and binary forms, with or without
14- modification, are permitted provided that the following conditions
15- are met:
16-
17- 1. Redistributions of source code must retain the above copyright
18- notice, this list of conditions and the following disclaimer.
19-
20- 2. Redistributions in binary form must reproduce the above copyright
21- notice, this list of conditions and the following disclaimer in the
22- documentation and/or other materials provided with the distribution.
23-
24- 3. Neither the names of the California Institute of Technology nor
25- the Delft University of Technology nor
26- the names of its contributors may be used to endorse or promote
27- products derived from this software without specific prior
28- written permission.
29-
30- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
31- "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
32- LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
33- FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL CALTECH
34- OR THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
35- SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
36- LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
37- USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
38- ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
39- OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
40- OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
41- SUCH DAMAGE.
42-
43- Author: M.M. (Rene) van Paassen (using xferfcn.py as basis)
44- Date: 02 Oct 12
45- Revised:
46-
47- $Id: frd.py 185 2012-08-30 05:44:32Z murrayrm $
48-
49- """
50-
5147# External function declarations
5248from warnings import warn
5349import numpy as np
5652from scipy .interpolate import splprep , splev
5753from .lti import LTI
5854
59- __all__ = ['FRD' , 'frd' ]
55+ __all__ = ['FrequencyResponseData' , 'FRD' , 'frd' ]
56+
6057
61- class FRD (LTI ):
62- """FRD (d, w)
58+ class FrequencyResponseData (LTI ):
59+ """FrequencyResponseData (d, w)
6360
6461 A class for models defined by frequency response data (FRD)
6562
66- The FRD class is used to represent systems in frequency response data form.
63+ The FrequencyResponseData (FRD) class is used to represent systems in
64+ frequency response data form.
6765
68- The main data members are 'omega' and 'fresp', where `omega` is a 1D
69- array with the frequency points of the response, and `fresp` is a 3D array,
70- with the first dimension corresponding to the output index of the FRD,
71- the second dimension corresponding to the input index, and the 3rd dimension
72- corresponding to the frequency points in omega.
73- For example,
66+ The main data members are 'omega' and 'fresp', where `omega` is a 1D array
67+ with the frequency points of the response, and `fresp` is a 3D array, with
68+ the first dimension corresponding to the output index of the FRD, the
69+ second dimension corresponding to the input index, and the 3rd dimension
70+ corresponding to the frequency points in omega. For example,
7471
7572 >>> frdata[2,5,:] = numpy.array([1., 0.8-0.2j, 0.2-0.8j])
7673
@@ -88,9 +85,7 @@ class FRD(LTI):
8885 epsw = 1e-8
8986
9087 def __init__ (self , * args , ** kwargs ):
91- """FRD(d, w)
92-
93- Construct an FRD object
88+ """Construct an FRD object.
9489
9590 The default constructor is FRD(d, w), where w is an iterable of
9691 frequency points, and d is the matching frequency data.
@@ -154,10 +149,10 @@ def __init__(self, *args, **kwargs):
154149 dtype = tuple )
155150 for i in range (self .fresp .shape [0 ]):
156151 for j in range (self .fresp .shape [1 ]):
157- self .ifunc [i ,j ],u = splprep (
152+ self .ifunc [i , j ], u = splprep (
158153 u = self .omega , x = [real (self .fresp [i , j , :]),
159154 imag (self .fresp [i , j , :])],
160- w = 1.0 / (absolute (self .fresp [i , j , :])+ 0.001 ), s = 0.0 )
155+ w = 1.0 / (absolute (self .fresp [i , j , :]) + 0.001 ), s = 0.0 )
161156 else :
162157 self .ifunc = None
163158 LTI .__init__ (self , self .fresp .shape [1 ], self .fresp .shape [0 ])
@@ -166,7 +161,7 @@ def __str__(self):
166161 """String representation of the transfer function."""
167162
168163 mimo = self .inputs > 1 or self .outputs > 1
169- outstr = [ 'frequency response data ' ]
164+ outstr = ['frequency response data ' ]
170165
171166 mt , pt , wt = self .freqresp (self .omega )
172167 for i in range (self .inputs ):
@@ -176,9 +171,9 @@ def __str__(self):
176171 outstr .append ('Freq [rad/s] Response ' )
177172 outstr .append ('------------ ---------------------' )
178173 outstr .extend (
179- [ '%12.3f %10.4g%+10.4gj' % (w , m , p )
180- for m , p , w in zip (real (self .fresp [j ,i ,:]), imag ( self . fresp [ j , i , :]), wt ) ])
181-
174+ ['%12.3f %10.4g%+10.4gj' % (w , m , p )
175+ for m , p , w in zip (real (self .fresp [j , i , :]),
176+ imag ( self . fresp [ j , i , :]), wt )])
182177
183178 return '\n ' .join (outstr )
184179
@@ -194,8 +189,8 @@ def __add__(self, other):
194189 # verify that the frequencies match
195190 if len (other .omega ) != len (self .omega ) or \
196191 (other .omega != self .omega ).any ():
197- warn ("Frequency points do not match; expect"
198- " truncation and interpolation." )
192+ warn ("Frequency points do not match; expect "
193+ " truncation and interpolation." )
199194
200195 # Convert the second argument to a frequency response function.
201196 # or re-base the frd to the current omega (if needed)
@@ -214,7 +209,7 @@ def __add__(self, other):
214209 def __radd__ (self , other ):
215210 """Right add two LTI objects (parallel connection)."""
216211
217- return self + other ;
212+ return self + other
218213
219214 def __sub__ (self , other ):
220215 """Subtract two LTI objects."""
@@ -238,16 +233,17 @@ def __mul__(self, other):
238233
239234 # Check that the input-output sizes are consistent.
240235 if self .inputs != other .outputs :
241- raise ValueError ("H = G1*G2: input-output size mismatch"
242- " G1 has %i input(s), G2 has %i output(s)." %
236+ raise ValueError (
237+ "H = G1*G2: input-output size mismatch: "
238+ "G1 has %i input(s), G2 has %i output(s)." %
243239 (self .inputs , other .outputs ))
244240
245241 inputs = other .inputs
246242 outputs = self .outputs
247243 fresp = empty ((outputs , inputs , len (self .omega )),
248244 dtype = self .fresp .dtype )
249245 for i in range (len (self .omega )):
250- fresp [:,:, i ] = dot (self .fresp [:,:, i ], other .fresp [:,:, i ])
246+ fresp [:, :, i ] = dot (self .fresp [:, :, i ], other .fresp [:, :, i ])
251247 return FRD (fresp , self .omega ,
252248 smooth = (self .ifunc is not None ) and
253249 (other .ifunc is not None ))
@@ -264,8 +260,9 @@ def __rmul__(self, other):
264260
265261 # Check that the input-output sizes are consistent.
266262 if self .outputs != other .inputs :
267- raise ValueError ("H = G1*G2: input-output size mismatch"
268- " G1 has %i input(s), G2 has %i output(s)." %
263+ raise ValueError (
264+ "H = G1*G2: input-output size mismatch: "
265+ "G1 has %i input(s), G2 has %i output(s)." %
269266 (other .inputs , self .outputs ))
270267
271268 inputs = self .inputs
@@ -274,7 +271,7 @@ def __rmul__(self, other):
274271 fresp = empty ((outputs , inputs , len (self .omega )),
275272 dtype = self .fresp .dtype )
276273 for i in range (len (self .omega )):
277- fresp [:,:, i ] = dot (other .fresp [:,:, i ], self .fresp [:,:, i ])
274+ fresp [:, :, i ] = dot (other .fresp [:, :, i ], self .fresp [:, :, i ])
278275 return FRD (fresp , self .omega ,
279276 smooth = (self .ifunc is not None ) and
280277 (other .ifunc is not None ))
@@ -289,11 +286,11 @@ def __truediv__(self, other):
289286 else :
290287 other = _convertToFRD (other , omega = self .omega )
291288
292-
293289 if (self .inputs > 1 or self .outputs > 1 or
294290 other .inputs > 1 or other .outputs > 1 ):
295291 raise NotImplementedError (
296- "FRD.__truediv__ is currently implemented only for SISO systems." )
292+ "FRD.__truediv__ is currently only implemented for SISO "
293+ "systems." )
297294
298295 return FRD (self .fresp / other .fresp , self .omega ,
299296 smooth = (self .ifunc is not None ) and
@@ -315,20 +312,21 @@ def __rtruediv__(self, other):
315312 if (self .inputs > 1 or self .outputs > 1 or
316313 other .inputs > 1 or other .outputs > 1 ):
317314 raise NotImplementedError (
318- "FRD.__rtruediv__ is currently implemented only for SISO systems." )
315+ "FRD.__rtruediv__ is currently only implemented for "
316+ "SISO systems." )
319317
320318 return other / self
321319
322320 # TODO: Remove when transition to python3 complete
323321 def __rdiv__ (self , other ):
324322 return self .__rtruediv__ (other )
325323
326- def __pow__ (self ,other ):
324+ def __pow__ (self , other ):
327325 if not type (other ) == int :
328326 raise ValueError ("Exponent must be an integer" )
329327 if other == 0 :
330- return FRD (ones (self .fresp .shape ),self .omega ,
331- smooth = (self .ifunc is not None )) # unity
328+ return FRD (ones (self .fresp .shape ), self .omega ,
329+ smooth = (self .ifunc is not None )) # unity
332330 if other > 0 :
333331 return self * (self ** (other - 1 ))
334332 if other < 0 :
@@ -347,7 +345,7 @@ def evalfr(self, omega):
347345
348346 """
349347 warn ("FRD.evalfr(omega) will be deprecated in a future release "
350- "of python-control; use sys.eval(omega) instead" ,
348+ "of python-control; use sys.eval(omega) instead" ,
351349 PendingDeprecationWarning ) # pragma: no coverage
352350 return self ._evalfr (omega )
353351
@@ -381,22 +379,22 @@ def _evalfr(self, omega):
381379 if self .ifunc is None :
382380 try :
383381 out = self .fresp [:, :, where (self .omega == omega )[0 ][0 ]]
384- except :
382+ except Exception :
385383 raise ValueError (
386384 "Frequency %f not in frequency list, try an interpolating"
387385 " FRD if you want additional points" % omega )
388386 else :
389387 if getattr (omega , '__iter__' , False ):
390388 for i in range (self .outputs ):
391389 for j in range (self .inputs ):
392- for k ,w in enumerate (omega ):
393- frraw = splev (w , self .ifunc [i ,j ], der = 0 )
394- out [i ,j , k ] = frraw [0 ] + 1.0j * frraw [1 ]
390+ for k , w in enumerate (omega ):
391+ frraw = splev (w , self .ifunc [i , j ], der = 0 )
392+ out [i , j , k ] = frraw [0 ] + 1.0j * frraw [1 ]
395393 else :
396394 for i in range (self .outputs ):
397395 for j in range (self .inputs ):
398- frraw = splev (omega , self .ifunc [i ,j ], der = 0 )
399- out [i ,j ] = frraw [0 ] + 1.0j * frraw [1 ]
396+ frraw = splev (omega , self .ifunc [i , j ], der = 0 )
397+ out [i , j ] = frraw [0 ] + 1.0j * frraw [1 ]
400398
401399 return out
402400
@@ -406,9 +404,10 @@ def freqresp(self, omega):
406404
407405 mag, phase, omega = self.freqresp(omega)
408406
409- reports the value of the magnitude, phase, and angular frequency of the
410- transfer function matrix evaluated at s = i * omega, where omega is a
411- list of angular frequencies, and is a sorted version of the input omega.
407+ reports the value of the magnitude, phase, and angular frequency of
408+ the transfer function matrix evaluated at s = i * omega, where omega
409+ is a list of angular frequencies, and is a sorted version of the input
410+ omega.
412411
413412 """
414413
@@ -431,8 +430,7 @@ def feedback(self, other=1, sign=-1):
431430
432431 other = _convertToFRD (other , omega = self .omega )
433432
434- if (self .outputs != other .inputs or
435- self .inputs != other .outputs ):
433+ if (self .outputs != other .inputs or self .inputs != other .outputs ):
436434 raise ValueError (
437435 "FRD.feedback, inputs/outputs mismatch" )
438436 fresp = empty ((self .outputs , self .inputs , len (other .omega )),
@@ -452,6 +450,20 @@ def feedback(self, other=1, sign=-1):
452450
453451 return FRD (fresp , other .omega , smooth = (self .ifunc is not None ))
454452
453+ #
454+ # Allow FRD as an alias for the FrequencyResponseData class
455+ #
456+ # Note: This class was initially given the name "FRD", but this caused
457+ # problems with documentation on MacOS platforms, since files were generated
458+ # for control.frd and control.FRD, which are not differentiated on most MacOS
459+ # filesystems, which are case insensitive. Renaming the FRD class to be
460+ # FrequenceResponseData and then assigning FRD to point to the same object
461+ # fixes this problem.
462+ #
463+
464+ FRD = FrequencyResponseData
465+
466+
455467def _convertToFRD (sys , omega , inputs = 1 , outputs = 1 ):
456468 """Convert a system to frequency response data form (if needed).
457469
@@ -495,18 +507,19 @@ def _convertToFRD(sys, omega, inputs=1, outputs=1):
495507 # try converting constant matrices
496508 try :
497509 sys = array (sys )
498- outputs ,inputs = sys .shape
510+ outputs , inputs = sys .shape
499511 fresp = empty ((outputs , inputs , len (omega )), dtype = float )
500512 for i in range (outputs ):
501513 for j in range (inputs ):
502- fresp [i ,j , :] = sys [i ,j ]
514+ fresp [i , j , :] = sys [i , j ]
503515 return FRD (fresp , omega , smooth = True )
504- except :
516+ except Exception :
505517 pass
506518
507519 raise TypeError ('''Can't convert given type "%s" to FRD system.''' %
508520 sys .__class__ )
509521
522+
510523def frd (* args ):
511524 """frd(d, w)
512525
0 commit comments