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.
172+ """FrequencyResponseData(d, w[, dt])
152173
153- The default constructor is FRD(d, w), where w is an iterable of
154- frequency points, and d is the matching frequency data.
174+ Construct a frequency response data (FRD) object.
155175
156- If d is a single list, 1D array, or tuple, a SISO system description
157- is assumed. d can also be
158-
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 )
@@ -267,11 +293,13 @@ def __init__(self, *args, **kwargs):
267293 self , 'output_index' , None ) else self .output_labels ,
268294 'name' : getattr (self , 'name' , None )}
269295 if arg_dt is not None :
270- defaults [ 'dt' ] = arg_dt # choose compatible timebase
271- name , inputs , outputs , states , dt = _process_iosys_keywords (
272- kwargs , defaults , end = True )
296+ if isinstance ( args [ 0 ], LTI ):
297+ arg_dt = common_timebase ( args [ 0 ]. dt , arg_dt )
298+ kwargs [ 'dt' ] = arg_dt
273299
274300 # Process signal names
301+ name , inputs , outputs , states , dt = _process_iosys_keywords (
302+ kwargs , defaults , end = True )
275303 InputOutputSystem .__init__ (
276304 self , name = name , inputs = inputs , outputs = outputs , dt = dt )
277305
@@ -282,17 +310,17 @@ def __init__(self, *args, **kwargs):
282310 raise ValueError ("can't smooth with only 1 frequency" )
283311 degree = 3 if self .omega .size > 3 else self .omega .size - 1
284312
285- self .ifunc = empty ((self .fresp .shape [0 ], self .fresp .shape [1 ]),
313+ self ._ifunc = empty ((self .fresp .shape [0 ], self .fresp .shape [1 ]),
286314 dtype = tuple )
287315 for i in range (self .fresp .shape [0 ]):
288316 for j in range (self .fresp .shape [1 ]):
289- self .ifunc [i , j ], u = splprep (
317+ self ._ifunc [i , j ], u = splprep (
290318 u = self .omega , x = [real (self .fresp [i , j , :]),
291319 imag (self .fresp [i , j , :])],
292320 w = 1.0 / (absolute (self .fresp [i , j , :]) + 0.001 ),
293321 s = 0.0 , k = degree )
294322 else :
295- self .ifunc = None
323+ self ._ifunc = None
296324
297325 #
298326 # Frequency response properties
@@ -395,7 +423,7 @@ def __repr__(self):
395423 """
396424 return "FrequencyResponseData({d}, {w}{smooth})" .format (
397425 d = repr (self .fresp ), w = repr (self .omega ),
398- smooth = (self .ifunc and ", smooth=True" ) or "" )
426+ smooth = (self ._ifunc and ", smooth=True" ) or "" )
399427
400428 def __neg__ (self ):
401429 """Negate a transfer function."""
@@ -454,7 +482,7 @@ def __mul__(self, other):
454482 # Convert the second argument to a transfer function.
455483 if isinstance (other , (int , float , complex , np .number )):
456484 return FRD (self .fresp * other , self .omega ,
457- smooth = (self .ifunc is not None ))
485+ smooth = (self ._ifunc is not None ))
458486 else :
459487 other = _convert_to_frd (other , omega = self .omega )
460488
@@ -472,16 +500,16 @@ def __mul__(self, other):
472500 for i in range (len (self .omega )):
473501 fresp [:, :, i ] = self .fresp [:, :, i ] @ other .fresp [:, :, i ]
474502 return FRD (fresp , self .omega ,
475- smooth = (self .ifunc is not None ) and
476- (other .ifunc is not None ))
503+ smooth = (self ._ifunc is not None ) and
504+ (other ._ifunc is not None ))
477505
478506 def __rmul__ (self , other ):
479507 """Right Multiply two LTI objects (serial connection)."""
480508
481509 # Convert the second argument to an frd function.
482510 if isinstance (other , (int , float , complex , np .number )):
483511 return FRD (self .fresp * other , self .omega ,
484- smooth = (self .ifunc is not None ))
512+ smooth = (self ._ifunc is not None ))
485513 else :
486514 other = _convert_to_frd (other , omega = self .omega )
487515
@@ -500,16 +528,16 @@ def __rmul__(self, other):
500528 for i in range (len (self .omega )):
501529 fresp [:, :, i ] = other .fresp [:, :, i ] @ self .fresp [:, :, i ]
502530 return FRD (fresp , self .omega ,
503- smooth = (self .ifunc is not None ) and
504- (other .ifunc is not None ))
531+ smooth = (self ._ifunc is not None ) and
532+ (other ._ifunc is not None ))
505533
506534 # TODO: Division of MIMO transfer function objects is not written yet.
507535 def __truediv__ (self , other ):
508536 """Divide two LTI objects."""
509537
510538 if isinstance (other , (int , float , complex , np .number )):
511539 return FRD (self .fresp * (1 / other ), self .omega ,
512- smooth = (self .ifunc is not None ))
540+ smooth = (self ._ifunc is not None ))
513541 else :
514542 other = _convert_to_frd (other , omega = self .omega )
515543
@@ -520,15 +548,15 @@ def __truediv__(self, other):
520548 "systems." )
521549
522550 return FRD (self .fresp / other .fresp , self .omega ,
523- smooth = (self .ifunc is not None ) and
524- (other .ifunc is not None ))
551+ smooth = (self ._ifunc is not None ) and
552+ (other ._ifunc is not None ))
525553
526554 # TODO: Division of MIMO transfer function objects is not written yet.
527555 def __rtruediv__ (self , other ):
528556 """Right divide two LTI objects."""
529557 if isinstance (other , (int , float , complex , np .number )):
530558 return FRD (other / self .fresp , self .omega ,
531- smooth = (self .ifunc is not None ))
559+ smooth = (self ._ifunc is not None ))
532560 else :
533561 other = _convert_to_frd (other , omega = self .omega )
534562
@@ -545,7 +573,7 @@ def __pow__(self, other):
545573 raise ValueError ("Exponent must be an integer" )
546574 if other == 0 :
547575 return FRD (ones (self .fresp .shape ), self .omega ,
548- smooth = (self .ifunc is not None )) # unity
576+ smooth = (self ._ifunc is not None )) # unity
549577 if other > 0 :
550578 return self * (self ** (other - 1 ))
551579 if other < 0 :
@@ -598,7 +626,7 @@ def eval(self, omega, squeeze=None):
598626 if any (omega_array .imag > 0 ):
599627 raise ValueError ("FRD.eval can only accept real-valued omega" )
600628
601- if self .ifunc is None :
629+ if self ._ifunc is None :
602630 elements = np .isin (self .omega , omega ) # binary array
603631 if sum (elements ) < len (omega_array ):
604632 raise ValueError (
@@ -612,7 +640,7 @@ def eval(self, omega, squeeze=None):
612640 for i in range (self .noutputs ):
613641 for j in range (self .ninputs ):
614642 for k , w in enumerate (omega_array ):
615- frraw = splev (w , self .ifunc [i , j ], der = 0 )
643+ frraw = splev (w , self ._ifunc [i , j ], der = 0 )
616644 out [i , j , k ] = frraw [0 ] + 1.0j * frraw [1 ]
617645
618646 return _process_frequency_response (self , omega , out , squeeze = squeeze )
@@ -767,7 +795,7 @@ def feedback(self, other=1, sign=-1):
767795 resfresp = (myfresp @ linalg .inv (I_AB ))
768796 fresp = np .moveaxis (resfresp , 0 , 2 )
769797
770- return FRD (fresp , other .omega , smooth = (self .ifunc is not None ))
798+ return FRD (fresp , other .omega , smooth = (self ._ifunc is not None ))
771799
772800 # Plotting interface
773801 def plot (self , plot_type = None , * args , ** kwargs ):
@@ -917,6 +945,8 @@ def frd(*args, **kwargs):
917945
918946 Parameters
919947 ----------
948+ sys : LTI (StateSpace or TransferFunction)
949+ A linear system that will be evaluated for frequency response data.
920950 response : array_like or LTI system
921951 Complex vector with the system response or an LTI system that can
922952 be used to copmute the frequency response at a list of frequencies.
@@ -933,7 +963,7 @@ def frd(*args, **kwargs):
933963
934964 Returns
935965 -------
936- sys : :class:` FrequencyResponseData`
966+ sys : FrequencyResponseData
937967 New frequency response data system.
938968
939969 Other Parameters
0 commit comments