1515import numpy as np
1616from numpy import absolute , real , angle , abs
1717from warnings import warn
18+ from . import config
1819
1920__all__ = ['issiso' , 'timebase' , 'common_timebase' , 'timebaseEqual' ,
2021 'isdtime' , 'isctime' , 'pole' , 'zero' , 'damp' , 'evalfr' ,
@@ -111,7 +112,7 @@ def damp(self):
111112 Z = - real (splane_poles )/ wn
112113 return wn , Z , poles
113114
114- def frequency_response (self , omega , squeeze = True ):
115+ def frequency_response (self , omega , squeeze = None ):
115116 """Evaluate the linear time-invariant system at an array of angular
116117 frequencies.
117118
@@ -124,30 +125,36 @@ def frequency_response(self, omega, squeeze=True):
124125
125126 G(exp(j*omega*dt)) = mag*exp(j*phase).
126127
127- In general the system may be multiple input, multiple output (MIMO), where
128- `m = self.inputs` number of inputs and `p = self.outputs` number of
129- outputs.
128+ In general the system may be multiple input, multiple output (MIMO),
129+ where `m = self.inputs` number of inputs and `p = self.outputs` number
130+ of outputs.
130131
131132 Parameters
132133 ----------
133- omega : float or array_like
134+ omega : float or 1D array_like
134135 A list, tuple, array, or scalar value of frequencies in
135136 radians/sec at which the system will be evaluated.
136- squeeze : bool, optional (default=True)
137- If True and the system is single input single output (SISO), i.e. `m=1`,
138- `p=1`, return a 1D array rather than a 3D array.
137+ squeeze : bool, optional
138+ If squeeze=True, remove single-dimensional entries from the shape
139+ of the output even if the system is not SISO. If squeeze=False,
140+ keep all indices (output, input and, if omega is array_like,
141+ frequency) even if the system is SISO. The default value can be
142+ set using config.defaults['control.squeeze_frequency_response'].
139143
140144 Returns
141145 -------
142- mag : (p, m, len(omega)) ndarray or (len(omega),) ndarray
146+ mag : ndarray
143147 The magnitude (absolute value, not dB or log10) of the system
144- frequency response. Array is ``(len(omega), )`` if
145- and only if system is SISO and ``squeeze=True``.
146- phase : (p, m, len(omega)) ndarray or (len(omega),) ndarray
148+ frequency response. If the system is SISO and squeeze is not
149+ True, the array is 1D, indexed by frequency. If the system is not
150+ SISO or squeeze is False, the array is 3D, indexed by the output,
151+ input, and frequency. If ``squeeze`` is True then
152+ single-dimensional axes are removed.
153+ phase : ndarray
147154 The wrapped phase in radians of the system frequency response.
148155 omega : ndarray
149156 The (sorted) frequencies at which the response was evaluated.
150-
157+
151158 """
152159 omega = np .sort (np .array (omega , ndmin = 1 ))
153160 if isdtime (self , strict = True ):
@@ -463,9 +470,8 @@ def damp(sys, doprint=True):
463470 (p .real , p .imag , d , w ))
464471 return wn , damping , poles
465472
466- def evalfr (sys , x , squeeze = True ):
467- """
468- Evaluate the transfer function of an LTI system for complex frequency x.
473+ def evalfr (sys , x , squeeze = None ):
474+ """Evaluate the transfer function of an LTI system for complex frequency x.
469475
470476 Returns the complex frequency response `sys(x)` where `x` is `s` for
471477 continuous-time systems and `z` for discrete-time systems, with
@@ -481,17 +487,24 @@ def evalfr(sys, x, squeeze=True):
481487 ----------
482488 sys: StateSpace or TransferFunction
483489 Linear system
484- x : complex scalar or array_like
490+ x : complex scalar or 1D array_like
485491 Complex frequency(s)
486492 squeeze : bool, optional (default=True)
487- If True and `sys` is single input single output (SISO), i.e. `m=1`,
488- `p=1`, return a 1D array rather than a 3D array.
493+ If squeeze=True, remove single-dimensional entries from the shape of
494+ the output even if the system is not SISO. If squeeze=False, keep all
495+ indices (output, input and, if omega is array_like, frequency) even if
496+ the system is SISO. The default value can be set using
497+ config.defaults['control.squeeze_frequency_response'].
489498
490499 Returns
491500 -------
492- fresp : (p, m, len(x)) complex ndarray or (len(x),) complex ndarray
493- The frequency response of the system. Array is ``(len(x), )`` if
494- and only if system is SISO and ``squeeze=True``.
501+ fresp : complex ndarray
502+ The frequency response of the system. If the system is SISO and
503+ squeeze is not True, the shape of the array matches the shape of
504+ omega. If the system is not SISO or squeeze is False, the first two
505+ dimensions of the array are indices for the output and input and the
506+ remaining dimensions match omega. If ``squeeze`` is True then
507+ single-dimensional axes are removed.
495508
496509 See Also
497510 --------
@@ -511,13 +524,13 @@ def evalfr(sys, x, squeeze=True):
511524 >>> # This is the transfer function matrix evaluated at s = i.
512525
513526 .. todo:: Add example with MIMO system
527+
514528 """
515529 return sys .__call__ (x , squeeze = squeeze )
516530
517- def freqresp (sys , omega , squeeze = True ):
518- """
519- Frequency response of an LTI system at multiple angular frequencies.
520-
531+ def freqresp (sys , omega , squeeze = None ):
532+ """Frequency response of an LTI system at multiple angular frequencies.
533+
521534 In general the system may be multiple input, multiple output (MIMO), where
522535 `m = sys.inputs` number of inputs and `p = sys.outputs` number of
523536 outputs.
@@ -526,22 +539,27 @@ def freqresp(sys, omega, squeeze=True):
526539 ----------
527540 sys: StateSpace or TransferFunction
528541 Linear system
529- omega : float or array_like
542+ omega : float or 1D array_like
530543 A list of frequencies in radians/sec at which the system should be
531544 evaluated. The list can be either a python list or a numpy array
532545 and will be sorted before evaluation.
533- squeeze : bool, optional (default=True)
534- If True and `sys` is single input, single output (SISO), returns
535- 1D array rather than a 3D array.
546+ squeeze : bool, optional
547+ If squeeze=True, remove single-dimensional entries from the shape of
548+ the output even if the system is not SISO. If squeeze=False, keep all
549+ indices (output, input and, if omega is array_like, frequency) even if
550+ the system is SISO. The default value can be set using
551+ config.defaults['control.squeeze_frequency_response'].
536552
537553 Returns
538554 -------
539- mag : (p, m, len(omega)) ndarray or (len(omega),) ndarray
555+ mag : ndarray
540556 The magnitude (absolute value, not dB or log10) of the system
541- frequency response. Array is ``(len(omega), )`` if and only if system
542- is SISO and ``squeeze=True``.
543-
544- phase : (p, m, len(omega)) ndarray or (len(omega),) ndarray
557+ frequency response. If the system is SISO and squeeze is not True,
558+ the array is 1D, indexed by frequency. If the system is not SISO or
559+ squeeze is False, the array is 3D, indexed by the output, input, and
560+ frequency. If ``squeeze`` is True then single-dimensional axes are
561+ removed.
562+ phase : ndarray
545563 The wrapped phase in radians of the system frequency response.
546564 omega : ndarray
547565 The list of sorted frequencies at which the response was
@@ -579,6 +597,7 @@ def freqresp(sys, omega, squeeze=True):
579597 #>>> # input to the 1st output, and the phase (in radians) of the
580598 #>>> # frequency response from the 1st input to the 2nd output, for
581599 #>>> # s = 0.1i, i, 10i.
600+
582601 """
583602 return sys .frequency_response (omega , squeeze = squeeze )
584603
@@ -593,3 +612,34 @@ def dcgain(sys):
593612 at the origin
594613 """
595614 return sys .dcgain ()
615+
616+
617+ # Process frequency responses in a uniform way
618+ def _process_frequency_response (sys , omega , out , squeeze = None ):
619+ # Set value of squeeze argument if not set
620+ if squeeze is None :
621+ squeeze = config .defaults ['control.squeeze_frequency_response' ]
622+
623+ if not hasattr (omega , '__len__' ):
624+ # received a scalar x, squeeze down the array along last dim
625+ out = np .squeeze (out , axis = 2 )
626+
627+ #
628+ # Get rid of unneeded dimensions
629+ #
630+ # There are three possible values for the squeeze keyword at this point:
631+ #
632+ # squeeze=None: squeeze input/output axes iff SISO
633+ # squeeze=True: squeeze all single dimensional axes (ala numpy)
634+ # squeeze-False: don't squeeze any axes
635+ #
636+ if squeeze is True :
637+ # Squeeze everything that we can if that's what the user wants
638+ return np .squeeze (out )
639+ elif squeeze is None and sys .issiso ():
640+ # SISO system output squeezed unless explicitly specified otherwise
641+ return out [0 ][0 ]
642+ elif squeeze is False or squeeze is None :
643+ return out
644+ else :
645+ raise ValueError ("unknown squeeze value" )
0 commit comments