diff --git a/control/config.py b/control/config.py index b4950ae5e..8ffe06845 100644 --- a/control/config.py +++ b/control/config.py @@ -15,7 +15,8 @@ # Package level default values _control_defaults = { - 'control.default_dt':0 + 'control.default_dt': 0, + 'control.squeeze_frequency_response': None } defaults = dict(_control_defaults) diff --git a/control/frdata.py b/control/frdata.py index 8f148a3fa..844ac9ab9 100644 --- a/control/frdata.py +++ b/control/frdata.py @@ -50,7 +50,8 @@ from numpy import angle, array, empty, ones, \ real, imag, absolute, eye, linalg, where, dot, sort from scipy.interpolate import splprep, splev -from .lti import LTI +from .lti import LTI, _process_frequency_response +from . import config __all__ = ['FrequencyResponseData', 'FRD', 'frd'] @@ -343,7 +344,7 @@ def __pow__(self, other): # G(s) for a transfer function and G(omega) for an FRD object. # update Sawyer B. Fuller 2020.08.14: __call__ added to provide a uniform # interface to systems in general and the lti.frequency_response method - def eval(self, omega, squeeze=True): + def eval(self, omega, squeeze=None): """Evaluate a transfer function at angular frequency omega. Note that a "normal" FRD only returns values for which there is an @@ -352,19 +353,33 @@ def eval(self, omega, squeeze=True): Parameters ---------- - omega : float or array_like + omega : float or 1D array_like Frequencies in radians per second - squeeze : bool, optional (default=True) - If True and `sys` is single input single output (SISO), returns a - 1D array rather than a 3D array. + squeeze : bool, optional + If squeeze=True, remove single-dimensional entries from the shape + of the output even if the system is not SISO. If squeeze=False, + keep all indices (output, input and, if omega is array_like, + frequency) even if the system is SISO. The default value can be + set using config.defaults['control.squeeze_frequency_response']. Returns ------- - fresp : (self.outputs, self.inputs, len(x)) or (len(x), ) complex ndarray - The frequency response of the system. Array is ``len(x)`` if and only - if system is SISO and ``squeeze=True``. + fresp : complex ndarray + The frequency response of the system. If the system is SISO and + squeeze is not True, the shape of the array matches the shape of + omega. If the system is not SISO or squeeze is False, the first + two dimensions of the array are indices for the output and input + and the remaining dimensions match omega. If ``squeeze`` is True + then single-dimensional axes are removed. + """ omega_array = np.array(omega, ndmin=1) # array-like version of omega + + # Make sure that we are operating on a simple list + if len(omega_array.shape) > 1: + raise ValueError("input list must be 1D") + + # Make sure that frequencies are all real-valued if any(omega_array.imag > 0): raise ValueError("FRD.eval can only accept real-valued omega") @@ -384,16 +399,12 @@ def eval(self, omega, squeeze=True): for k, w in enumerate(omega_array): frraw = splev(w, self.ifunc[i, j], der=0) out[i, j, k] = frraw[0] + 1.0j * frraw[1] - if not hasattr(omega, '__len__'): - # omega is a scalar, squeeze down array along last dim - out = np.squeeze(out, axis=2) - if squeeze and self.issiso(): - out = out[0][0] - return out - - def __call__(self, s, squeeze=True): + + return _process_frequency_response(self, omega, out, squeeze=squeeze) + + def __call__(self, s, squeeze=None): """Evaluate system's transfer function at complex frequencies. - + Returns the complex frequency response `sys(s)` of system `sys` with `m = sys.inputs` number of inputs and `p = sys.outputs` number of outputs. @@ -403,18 +414,24 @@ def __call__(self, s, squeeze=True): Parameters ---------- - s : complex scalar or array_like + s : complex scalar or 1D array_like Complex frequencies squeeze : bool, optional (default=True) - If True and `sys` is single input single output (SISO), i.e. `m=1`, - `p=1`, return a 1D array rather than a 3D array. + If squeeze=True, remove single-dimensional entries from the shape + of the output even if the system is not SISO. If squeeze=False, + keep all indices (output, input and, if omega is array_like, + frequency) even if the system is SISO. The default value can be + set using config.defaults['control.squeeze_frequency_response']. Returns ------- - fresp : (p, m, len(s)) complex ndarray or (len(s),) complex ndarray - The frequency response of the system. Array is ``(len(s), )`` if - and only if system is SISO and ``squeeze=True``. - + fresp : complex ndarray + The frequency response of the system. If the system is SISO and + squeeze is not True, the shape of the array matches the shape of + omega. If the system is not SISO or squeeze is False, the first + two dimensions of the array are indices for the output and input + and the remaining dimensions match omega. If ``squeeze`` is True + then single-dimensional axes are removed. Raises ------ @@ -423,9 +440,14 @@ def __call__(self, s, squeeze=True): :class:`FrequencyDomainData` systems are only defined at imaginary frequency values. """ - if any(abs(np.array(s, ndmin=1).real) > 0): + # Make sure that we are operating on a simple list + if len(np.atleast_1d(s).shape) > 1: + raise ValueError("input list must be 1D") + + if any(abs(np.atleast_1d(s).real) > 0): raise ValueError("__call__: FRD systems can only accept " "purely imaginary frequencies") + # need to preserve array or scalar status if hasattr(s, '__len__'): return self.eval(np.asarray(s).imag, squeeze=squeeze) diff --git a/control/lti.py b/control/lti.py index 152c5c73b..514944f75 100644 --- a/control/lti.py +++ b/control/lti.py @@ -15,6 +15,7 @@ import numpy as np from numpy import absolute, real, angle, abs from warnings import warn +from . import config __all__ = ['issiso', 'timebase', 'common_timebase', 'timebaseEqual', 'isdtime', 'isctime', 'pole', 'zero', 'damp', 'evalfr', @@ -111,7 +112,7 @@ def damp(self): Z = -real(splane_poles)/wn return wn, Z, poles - def frequency_response(self, omega, squeeze=True): + def frequency_response(self, omega, squeeze=None): """Evaluate the linear time-invariant system at an array of angular frequencies. @@ -124,30 +125,36 @@ def frequency_response(self, omega, squeeze=True): G(exp(j*omega*dt)) = mag*exp(j*phase). - In general the system may be multiple input, multiple output (MIMO), where - `m = self.inputs` number of inputs and `p = self.outputs` number of - outputs. + In general the system may be multiple input, multiple output (MIMO), + where `m = self.inputs` number of inputs and `p = self.outputs` number + of outputs. Parameters ---------- - omega : float or array_like + omega : float or 1D array_like A list, tuple, array, or scalar value of frequencies in radians/sec at which the system will be evaluated. - squeeze : bool, optional (default=True) - If True and the system is single input single output (SISO), i.e. `m=1`, - `p=1`, return a 1D array rather than a 3D array. + squeeze : bool, optional + If squeeze=True, remove single-dimensional entries from the shape + of the output even if the system is not SISO. If squeeze=False, + keep all indices (output, input and, if omega is array_like, + frequency) even if the system is SISO. The default value can be + set using config.defaults['control.squeeze_frequency_response']. Returns ------- - mag : (p, m, len(omega)) ndarray or (len(omega),) ndarray + mag : ndarray The magnitude (absolute value, not dB or log10) of the system - frequency response. Array is ``(len(omega), )`` if - and only if system is SISO and ``squeeze=True``. - phase : (p, m, len(omega)) ndarray or (len(omega),) ndarray + frequency response. If the system is SISO and squeeze is not + True, the array is 1D, indexed by frequency. If the system is not + SISO or squeeze is False, the array is 3D, indexed by the output, + input, and frequency. If ``squeeze`` is True then + single-dimensional axes are removed. + phase : ndarray The wrapped phase in radians of the system frequency response. omega : ndarray The (sorted) frequencies at which the response was evaluated. - + """ omega = np.sort(np.array(omega, ndmin=1)) if isdtime(self, strict=True): @@ -463,9 +470,8 @@ def damp(sys, doprint=True): (p.real, p.imag, d, w)) return wn, damping, poles -def evalfr(sys, x, squeeze=True): - """ - Evaluate the transfer function of an LTI system for complex frequency x. +def evalfr(sys, x, squeeze=None): + """Evaluate the transfer function of an LTI system for complex frequency x. Returns the complex frequency response `sys(x)` where `x` is `s` for continuous-time systems and `z` for discrete-time systems, with @@ -481,17 +487,24 @@ def evalfr(sys, x, squeeze=True): ---------- sys: StateSpace or TransferFunction Linear system - x : complex scalar or array_like + x : complex scalar or 1D array_like Complex frequency(s) squeeze : bool, optional (default=True) - If True and `sys` is single input single output (SISO), i.e. `m=1`, - `p=1`, return a 1D array rather than a 3D array. + If squeeze=True, remove single-dimensional entries from the shape of + the output even if the system is not SISO. If squeeze=False, keep all + indices (output, input and, if omega is array_like, frequency) even if + the system is SISO. The default value can be set using + config.defaults['control.squeeze_frequency_response']. Returns ------- - fresp : (p, m, len(x)) complex ndarray or (len(x),) complex ndarray - The frequency response of the system. Array is ``(len(x), )`` if - and only if system is SISO and ``squeeze=True``. + fresp : complex ndarray + The frequency response of the system. If the system is SISO and + squeeze is not True, the shape of the array matches the shape of + omega. If the system is not SISO or squeeze is False, the first two + dimensions of the array are indices for the output and input and the + remaining dimensions match omega. If ``squeeze`` is True then + single-dimensional axes are removed. See Also -------- @@ -511,13 +524,13 @@ def evalfr(sys, x, squeeze=True): >>> # This is the transfer function matrix evaluated at s = i. .. todo:: Add example with MIMO system + """ return sys.__call__(x, squeeze=squeeze) -def freqresp(sys, omega, squeeze=True): - """ - Frequency response of an LTI system at multiple angular frequencies. - +def freqresp(sys, omega, squeeze=None): + """Frequency response of an LTI system at multiple angular frequencies. + In general the system may be multiple input, multiple output (MIMO), where `m = sys.inputs` number of inputs and `p = sys.outputs` number of outputs. @@ -526,22 +539,27 @@ def freqresp(sys, omega, squeeze=True): ---------- sys: StateSpace or TransferFunction Linear system - omega : float or array_like + omega : float or 1D array_like A list of frequencies in radians/sec at which the system should be evaluated. The list can be either a python list or a numpy array and will be sorted before evaluation. - squeeze : bool, optional (default=True) - If True and `sys` is single input, single output (SISO), returns - 1D array rather than a 3D array. + squeeze : bool, optional + If squeeze=True, remove single-dimensional entries from the shape of + the output even if the system is not SISO. If squeeze=False, keep all + indices (output, input and, if omega is array_like, frequency) even if + the system is SISO. The default value can be set using + config.defaults['control.squeeze_frequency_response']. Returns ------- - mag : (p, m, len(omega)) ndarray or (len(omega),) ndarray + mag : ndarray The magnitude (absolute value, not dB or log10) of the system - frequency response. Array is ``(len(omega), )`` if and only if system - is SISO and ``squeeze=True``. - - phase : (p, m, len(omega)) ndarray or (len(omega),) ndarray + frequency response. If the system is SISO and squeeze is not True, + the array is 1D, indexed by frequency. If the system is not SISO or + squeeze is False, the array is 3D, indexed by the output, input, and + frequency. If ``squeeze`` is True then single-dimensional axes are + removed. + phase : ndarray The wrapped phase in radians of the system frequency response. omega : ndarray The list of sorted frequencies at which the response was @@ -579,6 +597,7 @@ def freqresp(sys, omega, squeeze=True): #>>> # input to the 1st output, and the phase (in radians) of the #>>> # frequency response from the 1st input to the 2nd output, for #>>> # s = 0.1i, i, 10i. + """ return sys.frequency_response(omega, squeeze=squeeze) @@ -593,3 +612,34 @@ def dcgain(sys): at the origin """ return sys.dcgain() + + +# Process frequency responses in a uniform way +def _process_frequency_response(sys, omega, out, squeeze=None): + # Set value of squeeze argument if not set + if squeeze is None: + squeeze = config.defaults['control.squeeze_frequency_response'] + + if not hasattr(omega, '__len__'): + # received a scalar x, squeeze down the array along last dim + out = np.squeeze(out, axis=2) + + # + # Get rid of unneeded dimensions + # + # There are three possible values for the squeeze keyword at this point: + # + # squeeze=None: squeeze input/output axes iff SISO + # squeeze=True: squeeze all single dimensional axes (ala numpy) + # squeeze-False: don't squeeze any axes + # + if squeeze is True: + # Squeeze everything that we can if that's what the user wants + return np.squeeze(out) + elif squeeze is None and sys.issiso(): + # SISO system output squeezed unless explicitly specified otherwise + return out[0][0] + elif squeeze is False or squeeze is None: + return out + else: + raise ValueError("unknown squeeze value") diff --git a/control/statesp.py b/control/statesp.py index ff4c73c4e..df4be85e6 100644 --- a/control/statesp.py +++ b/control/statesp.py @@ -62,7 +62,7 @@ from scipy.signal import cont2discrete from scipy.signal import StateSpace as signalStateSpace from warnings import warn -from .lti import LTI, common_timebase, isdtime +from .lti import LTI, common_timebase, isdtime, _process_frequency_response from . import config from copy import deepcopy @@ -640,15 +640,11 @@ def __rdiv__(self, other): raise NotImplementedError( "StateSpace.__rdiv__ is not implemented yet.") - def __call__(self, x, squeeze=True): + def __call__(self, x, squeeze=None): """Evaluate system's transfer function at complex frequency. Returns the complex frequency response `sys(x)` where `x` is `s` for continuous-time systems and `z` for discrete-time systems. - - In general the system may be multiple input, multiple output (MIMO), where - `m = self.inputs` number of inputs and `p = self.outputs` number of - outputs. To evaluate at a frequency omega in radians per second, enter ``x = omega * 1j``, for continuous-time systems, or @@ -657,28 +653,29 @@ def __call__(self, x, squeeze=True): Parameters ---------- - x : complex or complex array_like + x : complex or complex 1D array_like Complex frequencies - squeeze : bool, optional (default=True) - If True and `self` is single input single output (SISO), returns a - 1D array rather than a 3D array. + squeeze : bool, optional + If squeeze=True, remove single-dimensional entries from the shape + of the output even if the system is not SISO. If squeeze=False, + keep all indices (output, input and, if omega is array_like, + frequency) even if the system is SISO. The default value can be + set using config.defaults['control.squeeze_frequency_response']. Returns ------- - fresp : (p, m, len(x)) complex ndarray or (len(x),) complex ndarray - The frequency response of the system. Array is ``len(x)`` if and - only if system is SISO and ``squeeze=True``. + fresp : complex ndarray + The frequency response of the system. If the system is SISO and + squeeze is not True, the shape of the array matches the shape of + omega. If the system is not SISO or squeeze is False, the first + two dimensions of the array are indices for the output and input + and the remaining dimensions match omega. If ``squeeze`` is True + then single-dimensional axes are removed. """ # Use Slycot if available out = self.horner(x) - if not hasattr(x, '__len__'): - # received a scalar x, squeeze down the array along last dim - out = np.squeeze(out, axis=2) - if squeeze and self.issiso(): - return out[0][0] - else: - return out + return _process_frequency_response(self, x, out, squeeze=squeeze) def slycot_laub(self, x): """Evaluate system's transfer function at complex frequency @@ -698,9 +695,13 @@ def slycot_laub(self, x): Frequency response """ from slycot import tb05ad + x_arr = np.atleast_1d(x) # array-like version of x + + # Make sure that we are operating on a simple list + if len(x_arr.shape) > 1: + raise ValueError("input list must be 1D") # preallocate - x_arr = np.atleast_1d(x) # array-like version of x n = self.states m = self.inputs p = self.outputs @@ -760,6 +761,11 @@ def horner(self, x): # Fall back because either Slycot unavailable or cannot handle # certain cases. x_arr = np.atleast_1d(x) # force to be an array + + # Make sure that we are operating on a simple list + if len(x_arr.shape) > 1: + raise ValueError("input list must be 1D") + # Preallocate out = empty((self.outputs, self.inputs, len(x_arr)), dtype=complex) diff --git a/control/tests/lti_test.py b/control/tests/lti_test.py index ee9d95a09..e165f9c60 100644 --- a/control/tests/lti_test.py +++ b/control/tests/lti_test.py @@ -2,12 +2,14 @@ import numpy as np import pytest +from .conftest import editsdefaults +import control as ct from control import c2d, tf, tf2ss, NonlinearIOSystem from control.lti import (LTI, common_timebase, damp, dcgain, isctime, isdtime, issiso, pole, timebaseEqual, zero) from control.tests.conftest import slycotonly - +from control.exception import slycot_check class TestLTI: @@ -153,3 +155,98 @@ def test_isdtime(self, objfun, arg, dt, ref, strictref): strictref = not strictref assert isctime(obj) == ref assert isctime(obj, strict=True) == strictref + + @pytest.mark.usefixtures("editsdefaults") + @pytest.mark.parametrize("fcn", [ct.ss, ct.tf, ct.frd, ct.ss2io]) + @pytest.mark.parametrize("nstate, nout, ninp, omega, squeeze, shape", [ + [1, 1, 1, 0.1, None, ()], # SISO + [1, 1, 1, [0.1], None, (1,)], + [1, 1, 1, [0.1, 1, 10], None, (3,)], + [2, 1, 1, 0.1, True, ()], + [2, 1, 1, [0.1], True, ()], + [2, 1, 1, [0.1, 1, 10], True, (3,)], + [3, 1, 1, 0.1, False, (1, 1)], + [3, 1, 1, [0.1], False, (1, 1, 1)], + [3, 1, 1, [0.1, 1, 10], False, (1, 1, 3)], + [1, 2, 1, 0.1, None, (2, 1)], # SIMO + [1, 2, 1, [0.1], None, (2, 1, 1)], + [1, 2, 1, [0.1, 1, 10], None, (2, 1, 3)], + [2, 2, 1, 0.1, True, (2,)], + [2, 2, 1, [0.1], True, (2,)], + [3, 2, 1, 0.1, False, (2, 1)], + [3, 2, 1, [0.1], False, (2, 1, 1)], + [3, 2, 1, [0.1, 1, 10], False, (2, 1, 3)], + [1, 1, 2, [0.1, 1, 10], None, (1, 2, 3)], # MISO + [2, 1, 2, [0.1, 1, 10], True, (2, 3)], + [3, 1, 2, [0.1, 1, 10], False, (1, 2, 3)], + [1, 2, 2, [0.1, 1, 10], None, (2, 2, 3)], # MIMO + [2, 2, 2, [0.1, 1, 10], True, (2, 2, 3)], + [3, 2, 2, [0.1, 1, 10], False, (2, 2, 3)] + ]) + def test_squeeze(self, fcn, nstate, nout, ninp, omega, squeeze, shape): + # Create the system to be tested + if fcn == ct.frd: + sys = fcn(ct.rss(nstate, nout, ninp), [1e-2, 1e-1, 1, 1e1, 1e2]) + elif fcn == ct.tf and (nout > 1 or ninp > 1) and not slycot_check(): + pytest.skip("Conversion of MIMO systems to transfer functions " + "requires slycot.") + else: + sys = fcn(ct.rss(nstate, nout, ninp)) + + # Convert the frequency list to an array for easy of use + isscalar = not hasattr(omega, '__len__') + omega = np.array(omega) + + # Call the transfer function directly and make sure shape is correct + assert sys(omega * 1j, squeeze=squeeze).shape == shape + + # Make sure that evalfr also works as expected + assert ct.evalfr(sys, omega * 1j, squeeze=squeeze).shape == shape + + # Check frequency response + mag, phase, _ = sys.frequency_response(omega, squeeze=squeeze) + if isscalar and squeeze is not True: + # sys.frequency_response() expects a list as an argument + # Add the shape of the input to the expected shape + assert mag.shape == shape + (1,) + assert phase.shape == shape + (1,) + else: + assert mag.shape == shape + assert phase.shape == shape + + # Make sure the default shape lines up with squeeze=None case + if squeeze is None: + assert sys(omega * 1j).shape == shape + + # Changing config.default to False should return 3D frequency response + ct.config.set_defaults('control', squeeze_frequency_response=False) + mag, phase, _ = sys.frequency_response(omega) + if isscalar: + assert mag.shape == (sys.outputs, sys.inputs, 1) + assert phase.shape == (sys.outputs, sys.inputs, 1) + assert sys(omega * 1j).shape == (sys.outputs, sys.inputs) + assert ct.evalfr(sys, omega * 1j).shape == (sys.outputs, sys.inputs) + else: + assert mag.shape == (sys.outputs, sys.inputs, len(omega)) + assert phase.shape == (sys.outputs, sys.inputs, len(omega)) + assert sys(omega * 1j).shape == \ + (sys.outputs, sys.inputs, len(omega)) + assert ct.evalfr(sys, omega * 1j).shape == \ + (sys.outputs, sys.inputs, len(omega)) + + @pytest.mark.parametrize("fcn", [ct.ss, ct.tf, ct.frd, ct.ss2io]) + def test_squeeze_exceptions(self, fcn): + if fcn == ct.frd: + sys = fcn(ct.rss(2, 1, 1), [1e-2, 1e-1, 1, 1e1, 1e2]) + else: + sys = fcn(ct.rss(2, 1, 1)) + + with pytest.raises(ValueError, match="unknown squeeze value"): + sys.frequency_response([1], squeeze=1) + sys([1], squeeze='siso') + evalfr(sys, [1], squeeze='siso') + + with pytest.raises(ValueError, match="must be 1D"): + sys.frequency_response([[0.1, 1], [1, 10]]) + sys([[0.1, 1], [1, 10]]) + evalfr(sys, [[0.1, 1], [1, 10]]) diff --git a/control/xferfcn.py b/control/xferfcn.py index 0ff21a42a..b732bafc0 100644 --- a/control/xferfcn.py +++ b/control/xferfcn.py @@ -63,7 +63,7 @@ from warnings import warn from itertools import chain from re import sub -from .lti import LTI, common_timebase, isdtime +from .lti import LTI, common_timebase, isdtime, _process_frequency_response from . import config __all__ = ['TransferFunction', 'tf', 'ss2tf', 'tfdata'] @@ -234,16 +234,16 @@ def __init__(self, *args, **kwargs): dt = config.defaults['control.default_dt'] self.dt = dt - def __call__(self, x, squeeze=True): + def __call__(self, x, squeeze=None): """Evaluate system's transfer function at complex frequencies. Returns the complex frequency response `sys(x)` where `x` is `s` for continuous-time systems and `z` for discrete-time systems. - In general the system may be multiple input, multiple output (MIMO), where - `m = self.inputs` number of inputs and `p = self.outputs` number of - outputs. - + In general the system may be multiple input, multiple output + (MIMO), where `m = self.inputs` number of inputs and `p = + self.outputs` number of outputs. + To evaluate at a frequency omega in radians per second, enter ``x = omega * 1j``, for continuous-time systems, or ``x = exp(1j * omega * dt)`` for discrete-time systems. Or use @@ -251,28 +251,31 @@ def __call__(self, x, squeeze=True): Parameters ---------- - x : complex array_like or complex + x : complex or complex 1D array_like Complex frequencies - squeeze : bool, optional (default=True) - If True and `sys` is single input single output (SISO), returns a - 1D array rather than a 3D array. + squeeze : bool, optional + If squeeze=True, remove single-dimensional entries from the shape + of the output even if the system is not SISO. If squeeze=False, + keep all indices (output, input and, if omega is array_like, + frequency) even if the system is SISO. The default value can be + set using config.defaults['control.squeeze_frequency_response']. + If True and the system is single-input single-output (SISO), + return a 1D array rather than a 3D array. Default value (True) + set by config.defaults['control.squeeze_frequency_response']. Returns ------- - fresp : (p, m, len(x)) complex ndarray or or (len(x), ) complex ndarray - The frequency response of the system. Array is `len(x)` if and - only if system is SISO and ``squeeze=True``. + fresp : complex ndarray + The frequency response of the system. If the system is SISO and + squeeze is not True, the shape of the array matches the shape of + omega. If the system is not SISO or squeeze is False, the first + two dimensions of the array are indices for the output and input + and the remaining dimensions match omega. If ``squeeze`` is True + then single-dimensional axes are removed. """ out = self.horner(x) - if not hasattr(x, '__len__'): - # received a scalar x, squeeze down the array along last dim - out = np.squeeze(out, axis=2) - if squeeze and self.issiso(): - # return a scalar/1d array of outputs - return out[0][0] - else: - return out + return _process_frequency_response(self, x, out, squeeze=squeeze) def horner(self, x): """Evaluate system's transfer function at complex frequency @@ -295,7 +298,12 @@ def horner(self, x): Frequency response """ - x_arr = np.atleast_1d(x) # force to be an array + x_arr = np.atleast_1d(x) # force to be an array + + # Make sure that we are operating on a simple list + if len(x_arr.shape) > 1: + raise ValueError("input list must be 1D") + out = empty((self.outputs, self.inputs, len(x_arr)), dtype=complex) for i in range(self.outputs): for j in range(self.inputs): pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy