Skip to content

Commit 848112d

Browse files
committed
unify use of squeeze keyword in frequency response functions
1 parent feb9fc9 commit 848112d

File tree

6 files changed

+66
-40
lines changed

6 files changed

+66
-40
lines changed

control/config.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@
1515

1616
# Package level default values
1717
_control_defaults = {
18-
'control.default_dt':0
18+
'control.default_dt': 0,
19+
'control.squeeze': True
1920
}
2021
defaults = dict(_control_defaults)
2122

control/frdata.py

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
real, imag, absolute, eye, linalg, where, dot, sort
5252
from scipy.interpolate import splprep, splev
5353
from .lti import LTI
54+
from . import config
5455

5556
__all__ = ['FrequencyResponseData', 'FRD', 'frd']
5657

@@ -343,7 +344,7 @@ def __pow__(self, other):
343344
# G(s) for a transfer function and G(omega) for an FRD object.
344345
# update Sawyer B. Fuller 2020.08.14: __call__ added to provide a uniform
345346
# interface to systems in general and the lti.frequency_response method
346-
def eval(self, omega, squeeze=True):
347+
def eval(self, omega, squeeze=None):
347348
"""Evaluate a transfer function at angular frequency omega.
348349
349350
Note that a "normal" FRD only returns values for which there is an
@@ -355,15 +356,21 @@ def eval(self, omega, squeeze=True):
355356
omega : float or array_like
356357
Frequencies in radians per second
357358
squeeze : bool, optional (default=True)
358-
If True and `sys` is single input single output (SISO), returns a
359-
1D array rather than a 3D array.
359+
If True and the system is single-input single-output (SISO),
360+
return a 1D array rather than a 3D array. Default value (True)
361+
set by config.defaults['control.squeeze'].
360362
361363
Returns
362364
-------
363365
fresp : (self.outputs, self.inputs, len(x)) or (len(x), ) complex ndarray
364-
The frequency response of the system. Array is ``len(x)`` if and only
365-
if system is SISO and ``squeeze=True``.
366+
The frequency response of the system. Array is ``len(x)``
367+
if and only if system is SISO and ``squeeze=True``.
368+
366369
"""
370+
# Set value of squeeze argument if not set
371+
if squeeze is None:
372+
squeeze = config.defaults['control.squeeze']
373+
367374
omega_array = np.array(omega, ndmin=1) # array-like version of omega
368375
if any(omega_array.imag > 0):
369376
raise ValueError("FRD.eval can only accept real-valued omega")
@@ -406,8 +413,9 @@ def __call__(self, s, squeeze=True):
406413
s : complex scalar or array_like
407414
Complex frequencies
408415
squeeze : bool, optional (default=True)
409-
If True and `sys` is single input single output (SISO), i.e. `m=1`,
410-
`p=1`, return a 1D array rather than a 3D array.
416+
If True and the system is single-input single-output (SISO),
417+
return a 1D array rather than a 3D array. Default value (True)
418+
set by config.defaults['control.squeeze'].
411419
412420
Returns
413421
-------
@@ -423,6 +431,10 @@ def __call__(self, s, squeeze=True):
423431
:class:`FrequencyDomainData` systems are only defined at imaginary
424432
frequency values.
425433
"""
434+
# Set value of squeeze argument if not set
435+
if squeeze is None:
436+
squeeze = config.defaults['control.squeeze']
437+
426438
if any(abs(np.array(s, ndmin=1).real) > 0):
427439
raise ValueError("__call__: FRD systems can only accept "
428440
"purely imaginary frequencies")

control/lti.py

Lines changed: 21 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ def damp(self):
111111
Z = -real(splane_poles)/wn
112112
return wn, Z, poles
113113

114-
def frequency_response(self, omega, squeeze=True):
114+
def frequency_response(self, omega, squeeze=None):
115115
"""Evaluate the linear time-invariant system at an array of angular
116116
frequencies.
117117
@@ -124,18 +124,19 @@ def frequency_response(self, omega, squeeze=True):
124124
125125
G(exp(j*omega*dt)) = mag*exp(j*phase).
126126
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.
127+
In general the system may be multiple input, multiple output (MIMO),
128+
where `m = self.inputs` number of inputs and `p = self.outputs` number
129+
of outputs.
130130
131131
Parameters
132132
----------
133133
omega : float or array_like
134134
A list, tuple, array, or scalar value of frequencies in
135135
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.
136+
squeeze : bool, optional
137+
If True and the system is single-input single-output (SISO),
138+
return a 1D array rather than a 3D array. Default value (True)
139+
set by config.defaults['control.squeeze'].
139140
140141
Returns
141142
-------
@@ -147,7 +148,7 @@ def frequency_response(self, omega, squeeze=True):
147148
The wrapped phase in radians of the system frequency response.
148149
omega : ndarray
149150
The (sorted) frequencies at which the response was evaluated.
150-
151+
151152
"""
152153
omega = np.sort(np.array(omega, ndmin=1))
153154
if isdtime(self, strict=True):
@@ -463,9 +464,8 @@ def damp(sys, doprint=True):
463464
(p.real, p.imag, d, w))
464465
return wn, damping, poles
465466

466-
def evalfr(sys, x, squeeze=True):
467-
"""
468-
Evaluate the transfer function of an LTI system for complex frequency x.
467+
def evalfr(sys, x, squeeze=None):
468+
"""Evaluate the transfer function of an LTI system for complex frequency x.
469469
470470
Returns the complex frequency response `sys(x)` where `x` is `s` for
471471
continuous-time systems and `z` for discrete-time systems, with
@@ -484,8 +484,9 @@ def evalfr(sys, x, squeeze=True):
484484
x : complex scalar or array_like
485485
Complex frequency(s)
486486
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.
487+
If True and the system is single-input single-output (SISO), return a
488+
1D array rather than a 3D array. Default value (True) set by
489+
config.defaults['control.squeeze'].
489490
490491
Returns
491492
-------
@@ -511,12 +512,12 @@ def evalfr(sys, x, squeeze=True):
511512
>>> # This is the transfer function matrix evaluated at s = i.
512513
513514
.. todo:: Add example with MIMO system
515+
514516
"""
515517
return sys.__call__(x, squeeze=squeeze)
516518

517-
def freqresp(sys, omega, squeeze=True):
518-
"""
519-
Frequency response of an LTI system at multiple angular frequencies.
519+
def freqresp(sys, omega, squeeze=None):
520+
"""Frequency response of an LTI system at multiple angular frequencies.
520521
521522
In general the system may be multiple input, multiple output (MIMO), where
522523
`m = sys.inputs` number of inputs and `p = sys.outputs` number of
@@ -531,8 +532,9 @@ def freqresp(sys, omega, squeeze=True):
531532
evaluated. The list can be either a python list or a numpy array
532533
and will be sorted before evaluation.
533534
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.
535+
If True and the system is single-input single-output (SISO), return a
536+
1D array rather than a 3D array. Default value (True) set by
537+
config.defaults['control.squeeze'].
536538
537539
Returns
538540
-------
@@ -579,6 +581,7 @@ def freqresp(sys, omega, squeeze=True):
579581
#>>> # input to the 1st output, and the phase (in radians) of the
580582
#>>> # frequency response from the 1st input to the 2nd output, for
581583
#>>> # s = 0.1i, i, 10i.
584+
582585
"""
583586
return sys.frequency_response(omega, squeeze=squeeze)
584587

control/margins.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -294,25 +294,25 @@ def stability_margins(sysdata, returnall=False, epsw=0.0):
294294
# frequency for gain margin: phase crosses -180 degrees
295295
w_180 = _poly_iw_real_crossing(num_iw, den_iw, epsw)
296296
with np.errstate(all='ignore'): # den=0 is okay
297-
w180_resp = evalfr(sys, 1J * w_180)
297+
w180_resp = evalfr(sys, 1J * w_180, squeeze=True)
298298

299299
# frequency for phase margin : gain crosses magnitude 1
300300
wc = _poly_iw_mag1_crossing(num_iw, den_iw, epsw)
301-
wc_resp = evalfr(sys, 1J * wc)
301+
wc_resp = evalfr(sys, 1J * wc, squeeze=True)
302302

303303
# stability margin
304304
wstab = _poly_iw_wstab(num_iw, den_iw, epsw)
305-
ws_resp = evalfr(sys, 1J * wstab)
305+
ws_resp = evalfr(sys, 1J * wstab, squeeze=True)
306306

307307
else: # Discrete Time
308308
zargs = _poly_z_invz(sys)
309309
# gain margin
310310
z, w_180 = _poly_z_real_crossing(*zargs, epsw=epsw)
311-
w180_resp = evalfr(sys, z)
311+
w180_resp = evalfr(sys, z, squeeze=True)
312312

313313
# phase margin
314314
z, wc = _poly_z_mag1_crossing(*zargs, epsw=epsw)
315-
wc_resp = evalfr(sys, z)
315+
wc_resp = evalfr(sys, z, squeeze=True)
316316

317317
# stability margin
318318
z, wstab = _poly_z_wstab(*zargs, epsw=epsw)
@@ -437,11 +437,11 @@ def phase_crossover_frequencies(sys):
437437
omega = _poly_iw_real_crossing(num_iw, den_iw, 0.)
438438

439439
# using real() to avoid rounding errors and results like 1+0j
440-
gain = np.real(evalfr(sys, 1J * omega))
440+
gain = np.real(evalfr(sys, 1J * omega, squeeze=True))
441441
else:
442442
zargs = _poly_z_invz(sys)
443443
z, omega = _poly_z_real_crossing(*zargs, epsw=0.)
444-
gain = np.real(evalfr(sys, z))
444+
gain = np.real(evalfr(sys, z, squeeze=True))
445445

446446
return omega, gain
447447

control/statesp.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -640,7 +640,7 @@ def __rdiv__(self, other):
640640
raise NotImplementedError(
641641
"StateSpace.__rdiv__ is not implemented yet.")
642642

643-
def __call__(self, x, squeeze=True):
643+
def __call__(self, x, squeeze=None):
644644
"""Evaluate system's transfer function at complex frequency.
645645
646646
Returns the complex frequency response `sys(x)` where `x` is `s` for
@@ -659,9 +659,10 @@ def __call__(self, x, squeeze=True):
659659
----------
660660
x : complex or complex array_like
661661
Complex frequencies
662-
squeeze : bool, optional (default=True)
663-
If True and `self` is single input single output (SISO), returns a
664-
1D array rather than a 3D array.
662+
squeeze : bool, optional
663+
If True and the system is single-input single-output (SISO),
664+
return a 1D array rather than a 3D array. Default value (True)
665+
set by config.defaults['control.squeeze'].
665666
666667
Returns
667668
-------
@@ -670,6 +671,10 @@ def __call__(self, x, squeeze=True):
670671
only if system is SISO and ``squeeze=True``.
671672
672673
"""
674+
# Set value of squeeze argument if not set
675+
if squeeze is None:
676+
squeeze = config.defaults['control.squeeze']
677+
673678
# Use Slycot if available
674679
out = self.horner(x)
675680
if not hasattr(x, '__len__'):

control/xferfcn.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,7 @@ def __init__(self, *args, **kwargs):
234234
dt = config.defaults['control.default_dt']
235235
self.dt = dt
236236

237-
def __call__(self, x, squeeze=True):
237+
def __call__(self, x, squeeze=None):
238238
"""Evaluate system's transfer function at complex frequencies.
239239
240240
Returns the complex frequency response `sys(x)` where `x` is `s` for
@@ -254,8 +254,9 @@ def __call__(self, x, squeeze=True):
254254
x : complex array_like or complex
255255
Complex frequencies
256256
squeeze : bool, optional (default=True)
257-
If True and `sys` is single input single output (SISO), returns a
258-
1D array rather than a 3D array.
257+
If True and the system is single-input single-output (SISO),
258+
return a 1D array rather than a 3D array. Default value (True)
259+
set by config.defaults['control.squeeze'].
259260
260261
Returns
261262
-------
@@ -264,6 +265,10 @@ def __call__(self, x, squeeze=True):
264265
only if system is SISO and ``squeeze=True``.
265266
266267
"""
268+
# Set value of squeeze argument if not set
269+
if squeeze is None:
270+
squeeze = config.defaults['control.squeeze']
271+
267272
out = self.horner(x)
268273
if not hasattr(x, '__len__'):
269274
# received a scalar x, squeeze down the array along last dim

0 commit comments

Comments
 (0)