33# Author: M.M. (Rene) van Paassen (using xferfcn.py as basis)
44# Date: 02 Oct 12
55
6- """
7- Frequency response data representation and functions.
6+ """Frequency response data representation and functions.
7+
8+ This module contains the FrequencyResponseData (FRD) class and also
9+ functions that operate on FRD data.
810
9- This module contains the FRD class and also functions that operate on
10- FRD data.
1111"""
1212
1313from collections .abc import Iterable
@@ -35,38 +35,29 @@ class FrequencyResponseData(LTI):
3535
3636 The FrequencyResponseData (FRD) class is used to represent systems in
3737 frequency response data form. It can be created manually using the
38- class constructor, using the :func:`~control.frd` factory function or
38+ class constructor, using the :func:`~control.frd` factory function, or
3939 via the :func:`~control.frequency_response` function.
4040
4141 Parameters
4242 ----------
43- d : 1D or 3D complex array_like
43+ response : 1D or 3D complex array_like
4444 The frequency response at each frequency point. If 1D, the system is
4545 assumed to be SISO. If 3D, the system is MIMO, with the first
4646 dimension corresponding to the output index of the FRD, the second
4747 dimension corresponding to the input index, and the 3rd dimension
4848 corresponding to the frequency points in omega
49- w : iterable of real frequencies
49+ omega : iterable of real frequencies
5050 List of frequency points for which data are available.
51- sysname : str or None
52- Name of the system that generated the data.
5351 smooth : bool, optional
5452 If ``True``, create an interpolation function that allows the
5553 frequency response to be computed at any frequency within the range of
5654 frequencies give in ``w``. If ``False`` (default), frequency response
5755 can only be obtained at the frequencies specified in ``w``.
58-
59- Attributes
60- ----------
61- ninputs, noutputs : int
62- Number of input and output variables.
63- omega : 1D array
64- Frequency points of the response.
65- fresp : 3D array
66- Frequency response, indexed by output index, input index, and
67- frequency point.
68- dt : float, True, or None
69- System timebase.
56+ dt : None, True or float, optional
57+ System timebase. 0 (default) indicates continuous time, True
58+ indicates discrete time with unspecified sampling time, positive
59+ number is discrete time with specified sampling time, None
60+ indicates unspecified timebase (either continuous or discrete time).
7061 squeeze : bool
7162 By default, if a system is single-input, single-output (SISO) then
7263 the outputs (and inputs) are returned as a 1D array (indexed by
@@ -79,16 +70,46 @@ class constructor, using the :func:`~control.frd` factory function or
7970 returned as a 3D array (indexed by the output, input, and
8071 frequency) even if the system is SISO. The default value can be set
8172 using config.defaults['control.squeeze_frequency_response'].
82- ninputs, noutputs, nstates : int
83- Number of inputs, outputs, and states of the underlying system.
73+ sysname : str or None
74+ Name of the system that generated the data.
75+
76+ Attributes
77+ ----------
78+ fresp : 3D array
79+ Frequency response, indexed by output index, input index, and
80+ frequency point.
81+ frequency : 1D array
82+ Array of frequency points for which data are available.
83+ ninputs, noutputs : int
84+ Number of inputs and outputs signals.
85+ shape : tuple
86+ 2-tuple of I/O system dimension, (noutputs, ninputs).
8487 input_labels, output_labels : array of str
85- Names for the input and output variables.
86- sysname : str, optional
87- Name of the system. For data generated using
88- :func:`~control.frequency_response`, stores the name of the system
89- that created the data.
88+ Names for the input and output signals.
89+ name : str
90+ System name. For data generated using
91+ :func:`~control.frequency_response`, stores the name of the
92+ system that created the data.
93+ magnitude : array
94+ Magnitude of the frequency response, indexed by frequency.
95+ phase : array
96+ Magnitude of the frequency response, indexed by frequency.
97+
98+ Other Parameters
99+ ----------------
100+ plot_type : str, optional
101+ Set the type of plot to generate with ``plot()`` ('bode', 'nichols').
90102 title : str, optional
91103 Set the title to use when plotting.
104+ plot_magnitude, plot_phase : bool, optional
105+ If set to `False`, don't plot the magnitude or phase, respectively.
106+ return_magphase : bool, optional
107+ If True, then a frequency response data object will enumerate as a
108+ tuple of the form (mag, phase, omega) where where ``mag`` is the
109+ magnitude (absolute value, not dB or log10) of the system
110+ frequency response, ``phase`` is the wrapped phase in radians of
111+ the system frequency response, and ``omega`` is the (sorted)
112+ frequencies at which the response was evaluated.
92113
93114 See Also
94115 --------
@@ -148,22 +169,26 @@ class constructor, using the :func:`~control.frd` factory function or
148169 _epsw = 1e-8 #: Bound for exact frequency match
149170
150171 def __init__ (self , * args , ** kwargs ):
151- """Construct an FRD object.
152-
153- The default constructor is FRD(d, w), where w is an iterable of
154- frequency points, and d is the matching frequency data.
172+ """FrequencyResponseData(d, w[, dt])
155173
156- If d is a single list, 1D array, or tuple, a SISO system description
157- is assumed. d can also be
174+ Construct a frequency response data (FRD) object.
158175
159- To call the copy constructor, call FRD(sys), where sys is a
160- FRD object.
176+ The default constructor is FrequencyResponseData(d, w), where w is
177+ an iterable of frequency points, and d is the matching frequency
178+ data. If d is a single list, 1D array, or tuple, a SISO system
179+ description is assumed. d can also be a 2D array, in which case a
180+ MIMO response is created. To call the copy constructor, call
181+ FrequencyResponseData(sys), where sys is a FRD object. The
182+ timebase for the frequency response can be provided using an
183+ optional third argument or the 'dt' keyword.
161184
162- To construct frequency response data for an existing LTI
163- object, other than an FRD, call FRD(sys, omega).
185+ To construct frequency response data for an existing LTI object,
186+ other than an FRD, call FrequencyResponseData(sys, omega). This
187+ functionality can also be obtained using :func:`frequency_response`
188+ (which has additional options available).
164189
165- The timebase for the frequency response can be provided using an
166- optional third argument or the 'dt' keyword .
190+ See :class:`FrequencyResponseData` and :func:`frd` for more
191+ information .
167192
168193 """
169194 smooth = kwargs .pop ('smooth' , False )
@@ -182,11 +207,12 @@ def __init__(self, *args, **kwargs):
182207
183208 if len (args ) == 2 :
184209 if not isinstance (args [0 ], FRD ) and isinstance (args [0 ], LTI ):
185- # not an FRD, but still a system, second argument should be
186- # the frequency range
210+ # not an FRD, but still an LTI system, second argument
211+ # should be the frequency range
187212 otherlti = args [0 ]
188213 self .omega = sort (np .asarray (args [1 ], dtype = float ))
189- # calculate frequency response at my points
214+
215+ # calculate frequency response at specified points
190216 if otherlti .isctime ():
191217 s = 1j * self .omega
192218 self .fresp = otherlti (s , squeeze = False )
@@ -270,10 +296,9 @@ def __init__(self, *args, **kwargs):
270296 arg_dt = common_timebase (args [0 ].dt , arg_dt )
271297 kwargs ['dt' ] = arg_dt
272298
299+ # Process signal names
273300 name , inputs , outputs , states , dt = _process_iosys_keywords (
274301 kwargs , defaults , end = True )
275-
276- # Process signal names
277302 InputOutputSystem .__init__ (
278303 self , name = name , inputs = inputs , outputs = outputs , dt = dt )
279304
@@ -284,17 +309,17 @@ def __init__(self, *args, **kwargs):
284309 raise ValueError ("can't smooth with only 1 frequency" )
285310 degree = 3 if self .omega .size > 3 else self .omega .size - 1
286311
287- self .ifunc = empty ((self .fresp .shape [0 ], self .fresp .shape [1 ]),
312+ self ._ifunc = empty ((self .fresp .shape [0 ], self .fresp .shape [1 ]),
288313 dtype = tuple )
289314 for i in range (self .fresp .shape [0 ]):
290315 for j in range (self .fresp .shape [1 ]):
291- self .ifunc [i , j ], u = splprep (
316+ self ._ifunc [i , j ], u = splprep (
292317 u = self .omega , x = [real (self .fresp [i , j , :]),
293318 imag (self .fresp [i , j , :])],
294319 w = 1.0 / (absolute (self .fresp [i , j , :]) + 0.001 ),
295320 s = 0.0 , k = degree )
296321 else :
297- self .ifunc = None
322+ self ._ifunc = None
298323
299324 #
300325 # Frequency response properties
@@ -424,7 +449,7 @@ def iosys_repr(self, format='loadable'):
424449 # Loadable format
425450 out = "FrequencyResponseData(\n {d},\n {w}{smooth}" .format (
426451 d = repr (self .fresp ), w = repr (self .omega ),
427- smooth = (self .ifunc and ", smooth=True" ) or "" )
452+ smooth = (self ._ifunc and ", smooth=True" ) or "" )
428453
429454 if config .defaults ['control.default_dt' ] != self .dt :
430455 out += ",\n dt={dt}" .format (
@@ -493,7 +518,7 @@ def __mul__(self, other):
493518 # Convert the second argument to a transfer function.
494519 if isinstance (other , (int , float , complex , np .number )):
495520 return FRD (self .fresp * other , self .omega ,
496- smooth = (self .ifunc is not None ))
521+ smooth = (self ._ifunc is not None ))
497522 else :
498523 other = _convert_to_frd (other , omega = self .omega )
499524
@@ -511,16 +536,16 @@ def __mul__(self, other):
511536 for i in range (len (self .omega )):
512537 fresp [:, :, i ] = self .fresp [:, :, i ] @ other .fresp [:, :, i ]
513538 return FRD (fresp , self .omega ,
514- smooth = (self .ifunc is not None ) and
515- (other .ifunc is not None ))
539+ smooth = (self ._ifunc is not None ) and
540+ (other ._ifunc is not None ))
516541
517542 def __rmul__ (self , other ):
518543 """Right Multiply two LTI objects (serial connection)."""
519544
520545 # Convert the second argument to an frd function.
521546 if isinstance (other , (int , float , complex , np .number )):
522547 return FRD (self .fresp * other , self .omega ,
523- smooth = (self .ifunc is not None ))
548+ smooth = (self ._ifunc is not None ))
524549 else :
525550 other = _convert_to_frd (other , omega = self .omega )
526551
@@ -539,16 +564,16 @@ def __rmul__(self, other):
539564 for i in range (len (self .omega )):
540565 fresp [:, :, i ] = other .fresp [:, :, i ] @ self .fresp [:, :, i ]
541566 return FRD (fresp , self .omega ,
542- smooth = (self .ifunc is not None ) and
543- (other .ifunc is not None ))
567+ smooth = (self ._ifunc is not None ) and
568+ (other ._ifunc is not None ))
544569
545570 # TODO: Division of MIMO transfer function objects is not written yet.
546571 def __truediv__ (self , other ):
547572 """Divide two LTI objects."""
548573
549574 if isinstance (other , (int , float , complex , np .number )):
550575 return FRD (self .fresp * (1 / other ), self .omega ,
551- smooth = (self .ifunc is not None ))
576+ smooth = (self ._ifunc is not None ))
552577 else :
553578 other = _convert_to_frd (other , omega = self .omega )
554579
@@ -559,15 +584,15 @@ def __truediv__(self, other):
559584 "systems." )
560585
561586 return FRD (self .fresp / other .fresp , self .omega ,
562- smooth = (self .ifunc is not None ) and
563- (other .ifunc is not None ))
587+ smooth = (self ._ifunc is not None ) and
588+ (other ._ifunc is not None ))
564589
565590 # TODO: Division of MIMO transfer function objects is not written yet.
566591 def __rtruediv__ (self , other ):
567592 """Right divide two LTI objects."""
568593 if isinstance (other , (int , float , complex , np .number )):
569594 return FRD (other / self .fresp , self .omega ,
570- smooth = (self .ifunc is not None ))
595+ smooth = (self ._ifunc is not None ))
571596 else :
572597 other = _convert_to_frd (other , omega = self .omega )
573598
@@ -584,7 +609,7 @@ def __pow__(self, other):
584609 raise ValueError ("Exponent must be an integer" )
585610 if other == 0 :
586611 return FRD (ones (self .fresp .shape ), self .omega ,
587- smooth = (self .ifunc is not None )) # unity
612+ smooth = (self ._ifunc is not None )) # unity
588613 if other > 0 :
589614 return self * (self ** (other - 1 ))
590615 if other < 0 :
@@ -637,7 +662,7 @@ def eval(self, omega, squeeze=None):
637662 if any (omega_array .imag > 0 ):
638663 raise ValueError ("FRD.eval can only accept real-valued omega" )
639664
640- if self .ifunc is None :
665+ if self ._ifunc is None :
641666 elements = np .isin (self .omega , omega ) # binary array
642667 if sum (elements ) < len (omega_array ):
643668 raise ValueError (
@@ -651,7 +676,7 @@ def eval(self, omega, squeeze=None):
651676 for i in range (self .noutputs ):
652677 for j in range (self .ninputs ):
653678 for k , w in enumerate (omega_array ):
654- frraw = splev (w , self .ifunc [i , j ], der = 0 )
679+ frraw = splev (w , self ._ifunc [i , j ], der = 0 )
655680 out [i , j , k ] = frraw [0 ] + 1.0j * frraw [1 ]
656681
657682 return _process_frequency_response (self , omega , out , squeeze = squeeze )
@@ -806,7 +831,7 @@ def feedback(self, other=1, sign=-1):
806831 resfresp = (myfresp @ linalg .inv (I_AB ))
807832 fresp = np .moveaxis (resfresp , 0 , 2 )
808833
809- return FRD (fresp , other .omega , smooth = (self .ifunc is not None ))
834+ return FRD (fresp , other .omega , smooth = (self ._ifunc is not None ))
810835
811836 # Plotting interface
812837 def plot (self , plot_type = None , * args , ** kwargs ):
@@ -956,6 +981,8 @@ def frd(*args, **kwargs):
956981
957982 Parameters
958983 ----------
984+ sys : LTI (StateSpace or TransferFunction)
985+ A linear system that will be evaluated for frequency response data.
959986 response : array_like or LTI system
960987 Complex vector with the system response or an LTI system that can
961988 be used to copmute the frequency response at a list of frequencies.
@@ -972,7 +999,7 @@ def frd(*args, **kwargs):
972999
9731000 Returns
9741001 -------
975- sys : :class:` FrequencyResponseData`
1002+ sys : FrequencyResponseData
9761003 New frequency response data system.
9771004
9781005 Other Parameters
0 commit comments