def test_gbt_with_sio_tf_and_zpk(self):
        """Test method='gbt' with alpha=0.25 for tf and zpk cases."""
        # State space coefficients for the continuous SIO system.
        A = -1.0
        B = 1.0
        C = 1.0
        D = 0.5

        # The continuous transfer function coefficients.
        cnum, cden = ss2tf(A, B, C, D)

        # Continuous zpk representation
        cz, cp, ck = ss2zpk(A, B, C, D)

        h = 1.0
        alpha = 0.25

        # Explicit formulas, in the scalar case.
        Ad = (1 + (1 - alpha) * h * A) / (1 - alpha * h * A)
        Bd = h * B / (1 - alpha * h * A)
        Cd = C / (1 - alpha * h * A)
        Dd = D + alpha * C * Bd

        # Convert the explicit solution to tf
        dnum, dden = ss2tf(Ad, Bd, Cd, Dd)

        # Compute the discrete tf using cont2discrete.
        c2dnum, c2dden, dt = c2d((cnum, cden), h, method='gbt', alpha=alpha)

        assert_allclose(dnum, c2dnum)
        assert_allclose(dden, c2dden)

        # Convert explicit solution to zpk.
        dz, dp, dk = ss2zpk(Ad, Bd, Cd, Dd)

        # Compute the discrete zpk using cont2discrete.
        c2dz, c2dp, c2dk, dt = c2d((cz, cp, ck), h, method='gbt', alpha=alpha)

        assert_allclose(dz, c2dz)
        assert_allclose(dp, c2dp)
        assert_allclose(dk, c2dk)
Esempio n. 2
0
    def test_gbt_with_sio_tf_and_zpk(self):
        """Test method='gbt' with alpha=0.25 for tf and zpk cases."""
        # State space coefficients for the continuous SIO system.
        A = -1.0
        B = 1.0
        C = 1.0
        D = 0.5

        # The continuous transfer function coefficients.
        cnum, cden = ss2tf(A, B, C, D)

        # Continuous zpk representation
        cz, cp, ck = ss2zpk(A, B, C, D)

        h = 1.0
        alpha = 0.25

        # Explicit formulas, in the scalar case.
        Ad = (1 + (1 - alpha) * h * A) / (1 - alpha * h * A)
        Bd = h * B / (1 - alpha * h * A)
        Cd = C / (1 - alpha * h * A)
        Dd = D + alpha * C * Bd

        # Convert the explicit solution to tf
        dnum, dden = ss2tf(Ad, Bd, Cd, Dd)

        # Compute the discrete tf using cont2discrete.
        c2dnum, c2dden, dt = d2c((cnum, cden), h, method='gbt', alpha=alpha)

        assert_allclose(dnum, c2dnum)
        assert_allclose(dden, c2dden)

        # Convert explicit solution to zpk.
        dz, dp, dk = ss2zpk(Ad, Bd, Cd, Dd)

        # Compute the discrete zpk using cont2discrete.
        c2dz, c2dp, c2dk, dt = d2c((cz, cp, ck), h, method='gbt', alpha=alpha)

        assert_allclose(dz, c2dz)
        assert_allclose(dp, c2dp)
        assert_allclose(dk, c2dk)
Esempio n. 3
0
def pole_zero(sys, xlim=None, ylim=None, figax=None, rcParams=None):
    if len(sys) == 2:
        z, p, k = signal.tf2zpk(*sys)
    elif len(sys) == 3:
        z, p, k = sys
    elif len(sys) == 4:
        z, p, k = signal.ss2zpk(*sys)
    else:
        ValueError("""\
sys must have 2 (transfer function), 3 (zeros, poles, gain),
or 4 (state space) elements. sys is: {}""".format(sys))

    return _pole_zero(z, p, k, xlim=xlim, ylim=ylim, figax=figax,
                      rcParams=rcParams)
Esempio n. 4
0
def _get_zpk(arg, input=0):
    """Utility method to convert the input arg to a z, p, k representation.

    **Parameters:**

    arg, which may be:

    * ZPK tuple,
    * num, den tuple,
    * A, B, C, D tuple,
    * a scipy LTI object,
    * a sequence of the tuples of any of the above types.

    input : scalar
        In case the system has multiple inputs, which input is to be
        considered. Input `0` means first input, and so on.

    **Returns:**

    The sequence of ndarrays z, p and a scalar k

    **Raises:**

    TypeError, ValueError

    .. warn: support for MISO transfer functions is experimental.
    """
    z, p, k = None, None, None
    if isinstance(arg, np.ndarray):
        # ABCD matrix
        A, B, C, D = partitionABCD(arg)
        z, p, k = ss2zpk(A, B, C, D, input=input)
    elif isinstance(arg, lti):
        z, p, k = arg.zeros, arg.poles, arg.gain
    elif _is_zpk(arg):
        z, p, k = np.atleast_1d(arg[0]), np.atleast_1d(arg[1]), arg[2]
    elif _is_num_den(arg):
        sys = lti(*arg).to_zpk()
        z, p, k = sys.zeros, sys.poles, sys.gain
    elif _is_A_B_C_D(arg):
        z, p, k = ss2zpk(*arg, input=input)
    elif isinstance(arg, collections.Iterable):
        ri = 0
        for i in arg:
            # Note we do not check if the user has assembled a list with
            # mismatched lti representations.
            if hasattr(i, 'B'):
                iis = i.B.shape[1]
                if input < ri + iis:
                    z, p, k = ss2zpk(i.A, i.B, i.C, i.D, input=input - ri)
                    break
                else:
                    ri += iis
                    continue
            elif _is_A_B_C_D(arg):
                iis = arg[1].shape[1]
                if input < ri + iis:
                    z, p, k = ss2zpk(*arg, input=input - ri)
                    break
                else:
                    ri += iis
                    continue
            else:
                if ri == input:
                    sys = lti(*i)
                    z, p, k = sys.zeros, sys.poles, sys.gain
                    break
                else:
                    ri += 1
                    continue
                ri += 1
        if (z, p, k) == (None, None, None):
            raise ValueError("The LTI representation does not have enough" +
                             "inputs: max %d, looking for input %d" %
                             (ri - 1, input))
    else:
        raise TypeError("Unknown LTI representation: %s" % arg)
    return z, p, k
Esempio n. 5
0
    def filter(self, *filt):
        """Apply the given filter to this `TimeSeries`.

        All recognised filter arguments are converted either into cascading
        second-order sections (if scipy >= 0.16 is installed), or into the
        ``(numerator, denominator)`` representation before being applied
        to this `TimeSeries`.

        .. note::

           All filters are presumed to be digital (Z-domain), if you have
           an analog ZPK (in Hertz or in rad/s) you should be using
           `TimeSeries.zpk` instead.

        .. note::

           When using `scipy` < 0.16 some higher-order filters may be
           unstable. With `scipy` >= 0.16 higher-order filters are
           decomposed into second-order-sections, and so are much more stable.

        Parameters
        ----------
        *filt
            one of:

            - :class:`scipy.signal.lti`
            - `MxN` `numpy.ndarray` of second-order-sections
              (`scipy` >= 0.16 only)
            - ``(numerator, denominator)`` polynomials
            - ``(zeros, poles, gain)``
            - ``(A, B, C, D)`` 'state-space' representation

        Returns
        -------
        result : `TimeSeries`
            the filtered version of the input `TimeSeries`

        See also
        --------
        TimeSeries.zpk
            for instructions on how to filter using a ZPK with frequencies
            in Hertz
        scipy.signal.sosfilter
            for details on the second-order section filtering method
            (`scipy` >= 0.16 only)
        scipy.signal.lfilter
            for details on the filtering method

        Raises
        ------
        ValueError
            If ``filt`` arguments cannot be interpreted properly
        """
        sos = None
        # single argument given
        if len(filt) == 1:
            filt = filt[0]
            # detect LTI
            if isinstance(filt, signal.lti):
                filt = filt
                a = filt.den
                b = filt.num
            # detect SOS
            elif isinstance(filt, numpy.ndarray) and filt.ndim == 2:
                sos = filt
            # detect taps
            else:
                b = filt
                a = [1]
        # detect TF
        elif len(filt) == 2:
            b, a = filt
        elif len(filt) == 3:
            try:
                sos = signal.zpk2sos(*filt)
            except AttributeError:
                b, a = signal.zpk2tf(*filt)
        elif len(filt) == 4:
            try:
                zpk = signal.ss2zpk(*filt)
                sos = signal.zpk2sos(zpk)
            except AttributeError:
                b, a = signal.ss2tf(*filt)
        else:
            raise ValueError("Cannot interpret filter arguments. Please "
                             "give either a signal.lti object, or a "
                             "tuple in zpk or ba format. See "
                             "scipy.signal docs for details.")
        if sos is not None:
            new = signal.sosfilt(sos, self, axis=0).view(self.__class__)
        else:
            new = signal.lfilter(b, a, self, axis=0).view(self.__class__)
        new.__dict__ = self.copy_metadata()
        return new
Esempio n. 6
0
def _get_zpk(arg, input=0):
    """Utility method to convert the input arg to a z, p, k representation.

    **Parameters:**

    arg, which may be:

    * ZPK tuple,
    * num, den tuple,
    * A, B, C, D tuple,
    * a scipy LTI object,
    * a sequence of the tuples of any of the above types.

    input : scalar
        In case the system has multiple inputs, which input is to be
        considered. Input `0` means first input, and so on.

    **Returns:**

    The sequence of ndarrays z, p and a scalar k

    **Raises:**

    TypeError, ValueError

    .. warn: support for MISO transfer functions is experimental.
    """
    z, p, k = None, None, None
    if isinstance(arg, np.ndarray):
        # ABCD matrix
        A, B, C, D = partitionABCD(arg)
        z, p, k = ss2zpk(A, B, C, D, input=input)
    elif hasattr(arg, '__class__') and arg.__class__.__name__ == 'lti':
        z, p, k = arg.zeros, arg.poles, arg.gain
    elif _is_zpk(arg):
        z, p, k = np.atleast_1d(arg[0]), np.atleast_1d(arg[1]), arg[2]
    elif _is_num_den(arg):
        sys = lti(*arg)
        z, p, k = sys.zeros, sys.poles, sys.gain
    elif _is_A_B_C_D(arg):
        z, p, k = ss2zpk(*arg, input=input)
    elif isinstance(arg, collections.Iterable):
        ri = 0
        for i in arg:
            # Note we do not check if the user has assembled a list with
            # mismatched lti representations.
            if hasattr(i, 'B'):
                iis = i.B.shape[1]
                if input < ri + iis:
                    z, p, k = ss2zpk(i.A, i.B, i.C, i.D, input=input - ri)
                    break
                else:
                    ri += iis
                    continue
            elif _is_A_B_C_D(arg):
                iis = arg[1].shape[1]
                if input < ri + iis:
                    z, p, k = ss2zpk(*arg, input=input - ri)
                    break
                else:
                    ri += iis
                    continue
            else:
                if ri == input:
                    sys = lti(*i)
                    z, p, k = sys.zeros, sys.poles, sys.gain
                    break
                else:
                    ri += 1
                    continue
                ri += 1
        if (z, p, k) == (None, None, None):
            raise ValueError("The LTI representation does not have enough" +
                             "inputs: max %d, looking for input %d" %
                             (ri - 1, input))
    else:
        raise TypeError("Unknown LTI representation: %s" % arg)
    return z, p, k
Esempio n. 7
0
def mapCtoD(sys_c, t=(0, 1), f0=0.0):
    """Map a MIMO continuous-time to an equiv. SIMO discrete-time system.

    The criterion for equivalence is that the sampled pulse response
    of the CT system must be identical to the impulse response of the DT system.
    i.e. If ``yc`` is the output of the CT system with an input ``vc`` taken
    from a set of DACs fed with a single DT input ``v``, then ``y``, the output
    of the equivalent DT system with input ``v`` satisfies:
    ``y(n) = yc(n-)`` for integer ``n``. The DACs are characterized by
    rectangular impulse responses with edge times specified in the t list.

    **Input:**

    sys_c : object
           the LTI description of the CT system, which can be:

     * the ABCD matrix,
     * a list-like containing the A, B, C, D matrices,
     * a list of zpk tuples (internally converted to SS representation),
     * a list of LTI objects.

    t : array_like
        The edge times of the DAC pulse used to make CT waveforms
        from DT inputs. Each row corresponds to one of the system
        inputs; [-1 -1] denotes a CT input. The default is [0 1],
        for all inputs except the first.

    f0 : float
         The (normalized) frequency at which the Gp filters' gains are
         to be set to unity. Default 0 (DC).

    **Output:**

    sys : tuple
         the LTI description for the DT equivalent, in A, B, C, D
         representation.

    Gp : list of lists
         the mixed CT/DT prefilters which form the samples
         fed to each state for the CT inputs.

    **Example:**

    Map the standard second order CT modulator shown below to its CT
    equivalent and verify that its NTF is :math:`(1-z^{-1})^2`.

    .. image:: ../doc/_static/mapCtoD.png
        :align: center
        :alt: mapCtoD block diagram

    It can be done as follows::

        from __future__ import print_function
        import numpy as np
        from scipy.signal import lti
        from deltasigma import *
        LFc = lti([[0, 0], [1, 0]], [[1, -1], [0, -1.5]], [[0, 1]], [[0, 0]])
        tdac = [0, 1]
        LF, Gp = mapCtoD(LFc, tdac)
        LF = lti(*LF)
        ABCD = np.vstack((
                np.hstack((LF.A, LF.B)),
                np.hstack((LF.C, LF.D))
               ))
        NTF, STF = calculateTF(ABCD)
        print("NTF:") # after rounding to a 1e-6 resolution
        print("Zeros:", np.real_if_close(np.round(NTF.zeros, 6)))
        print("Poles:", np.real_if_close(np.round(NTF.poles, 6)))

    Prints::

        Zeros: [ 1.  1.]
        Poles: [ 0.  0.]

    Equivalent to::

               (z -1)^2
        NTF = ----------
                 z^2

    .. seealso:: R. Schreier and B. Zhang, "Delta-sigma modulators employing \
    continuous-time circuitry," IEEE Transactions on Circuits and Systems I, \
    vol. 43, no. 4, pp. 324-332, April 1996.
    """
    # You need to have A, B, C, D specification of the system
    Ac, Bc, Cc, Dc = _getABCD(sys_c)
    ni = Bc.shape[1]
    # Sanitize t
    if hasattr(t, "tolist"):
        t = t.tolist()
    if (type(t) == tuple or type(t) == list) and np.isscalar(t[0]):
        t = [t]  # we got a simple list, like the default value
    if not (type(t) == tuple or type(t) == list) and not (type(t[0]) == tuple or type(t[0]) == list):
        raise ValueError("The t argument has an unrecognized shape")
    # back to business
    t = np.array(t)
    if t.shape == (1, 2) and ni > 1:
        t = np.vstack((np.array([[-1, -1]]), np.dot(np.ones((ni - 1, 1)), t)))
    if t.shape != (ni, 2):
        raise ValueError("The t argument has the wrong dimensions.")
    di = np.ones(ni).astype(bool)
    for i in range(ni):
        if t[i, 0] == -1 and t[i, 1] == -1:
            di[i] = False

    # c2d assumes t1=0, t2=1.
    # Also c2d often complains about poor scaling and can even produce
    # incorrect results.
    A, B, C, D, _ = cont2discrete((Ac, Bc, Cc, Dc), 1, method="zoh")
    Bc1 = Bc[:, ~di]

    # Examine the discrete-time inputs to see how big the
    # augmented matrices need to be.
    B1 = B[:, ~di]
    D1 = D[:, ~di]
    n = A.shape[0]
    t2 = np.ceil(t[di, 1]).astype(np.int_)
    esn = (t2 == t[di, 1]) and (D[0, di] != 0).T  # extra states needed?
    npp = n + np.max(t2 - 1 + 1 * esn)

    # Augment A to npp x npp, B to np x 1, C to 1 x np.
    Ap = padb(padr(A, npp), npp)
    for i in range(n + 1, npp):
        Ap[i, i - 1] = 1
    Bp = np.zeros((npp, 1))
    if npp > n:
        Bp[n, 0] = 1
    Cp = padr(C, npp)
    Dp = np.zeros((1, 1))

    # Add in the contributions from each DAC
    for i in np.flatnonzero(di):
        t1 = t[i, 0]
        t2 = t[i, 1]
        B2 = B[:, i]
        D2 = D[:, i]
        if t1 == 0 and t2 == 1 and D2 == 0:  # No fancy stuff necessary
            Bp = Bp + padb(B2, npp)
        else:
            n1 = np.floor(t1)
            n2 = np.ceil(t2) - n1 - 1
            t1 = t1 - n1
            t2 = t2 - n2 - n1
            if t2 == 1 and D2 != 0:
                n2 = n2 + 1
                extraStateNeeded = 1
            else:
                extraStateNeeded = 0
            nt = n + n1 + n2
            if n2 > 0:
                if t2 == 1:
                    Ap[:n, nt - n2 : nt] = Ap[:n, nt - n2 : nt] + np.tile(B2, (1, n2))
                else:
                    Ap[:n, nt - n2 : nt - 1] = Ap[:n, nt - n2 : nt - 1] + np.tile(B2, (1, n2 - 1))
                    Ap[:n, (nt - 1)] = Ap[:n, (nt - 1)] + _B2formula(Ac, 0, t2, B2)
            if n2 > 0:  # pulse extends to the next period
                Btmp = _B2formula(Ac, t1, 1, B2)
            else:  # pulse ends in this period
                Btmp = _B2formula(Ac, t1, t2, B2)
            if n1 > 0:
                Ap[:n, n + n1 - 1] = Ap[:n, n + n1 - 1] + Btmp
            else:
                Bp = Bp + padb(Btmp, npp)
            if n2 > 0:
                Cp = Cp + padr(np.hstack((np.zeros((D2.shape[0], n + n1)), D2 * np.ones((1, n2)))), npp)
    sys = (Ap, Bp, Cp, Dp)
    if np.any(~di):
        # Compute the prefilters and add in the CT feed-ins.
        # Gp = inv(sI - Ac)*(zI - A)/z*Bc1
        n, m = Bc1.shape
        Gp = np.empty_like(np.zeros((n, m)), dtype=object)
        # !!Make this like stf: an array of zpk objects
        ztf = np.empty_like(Bc1, dtype=object)
        # Compute the z-domain portions of the filters
        ABc1 = np.dot(A, Bc1)
        for h in range(m):
            for i in range(n):
                if Bc1[i, h] == 0:
                    ztf[i, h] = (np.array([]), np.array([0.0]), -ABc1[i, h])  # dt=1
                else:
                    ztf[i, h] = (np.atleast_1d(ABc1[i, h] / Bc1[i, h]), np.array([0.0]), Bc1[i, h])  # dt = 1
        # Compute the s-domain portions of each of the filters
        stf = np.empty_like(np.zeros((n, n)), dtype=object)  # stf[out, in] = zpk
        for oi in range(n):
            for ii in range(n):
                # Doesn't do pole-zero cancellation
                stf[oi, ii] = ss2zpk(Ac, np.eye(n), np.eye(n)[oi, :], np.zeros((1, n)), input=ii)
                # scipy as of v 0.13 has no support for LTI MIMO systems
                # only 'MISO', therefore you can't write:
                # stf = ss2zpk(Ac, eye(n), eye(n), np.zeros(n, n)))
        for h in range(m):
            for i in range(n):
                # k = 1 unneded, see below
                for j in range(n):
                    # check the k values for a non-zero term
                    if stf[i, j][2] != 0 and ztf[j, h][2] != 0:
                        if Gp[i, h] is None:
                            Gp[i, h] = {}
                            Gp[i, h].update({"Hs": [list(stf[i, j])]})
                            Gp[i, h].update({"Hz": [list(ztf[j, h])]})
                        else:
                            Gp[i, h].update({"Hs": Gp[i, h]["Hs"] + [list(stf[i, j])]})
                            Gp[i, h].update({"Hz": Gp[i, h]["Hz"] + [list(ztf[j, h])]})
                        # the MATLAB-like cell code for the above statements would have
                        # been:
                        # Gp[i, h](k).Hs = stf[i, j]
                        # Gp[i, h](k).Hz = ztf[j, h]
                        # k = k + 1
        if f0 != 0:  # Need to correct the gain terms calculated by c2d
            # B1 = gains of Gp @f0;
            for h in range(m):
                for i in range(n):
                    B1ih = np.real_if_close(evalMixedTF(Gp[i, h], f0))
                    # abs() used because ss() whines if B has complex entries...
                    # This is clearly incorrect.
                    # I've fudged the complex stuff by including a sign....
                    B1[i, h] = np.abs(B1ih) * np.sign(np.real(B1ih))
                    if np.abs(B1[i, h]) < 1e-09:
                        B1[i, h] = 1e-09  # This prevents NaN in "line 174" below
        # Adjust the gains of the pre-filters
        for h in range(m):
            for i in range(n):
                for j in range(max(len(Gp[i, h]["Hs"]), len(Gp[i, h]["Hz"]))):
                    # The next is "line 174"
                    Gp[i, h]["Hs"][j][2] = Gp[i, h]["Hs"][j][2] / B1[i, h]
        sys = (
            sys[0],  # Ap
            np.hstack((padb(B1, npp), sys[1])),  # new B
            sys[2],  # Cp
            np.hstack((D1, sys[3])),
        )  # new D
    return sys, Gp
Esempio n. 8
0
def mapCtoD(sys_c, t=(0, 1), f0=0.):
    """Map a MIMO continuous-time to an equiv. SIMO discrete-time system.

    The criterion for equivalence is that the sampled pulse response
    of the CT system must be identical to the impulse response of the DT system.
    i.e. If ``yc`` is the output of the CT system with an input ``vc`` taken
    from a set of DACs fed with a single DT input ``v``, then ``y``, the output
    of the equivalent DT system with input ``v`` satisfies:
    ``y(n) = yc(n-)`` for integer ``n``. The DACs are characterized by
    rectangular impulse responses with edge times specified in the t list.

    **Input:**

    sys_c : object
           the LTI description of the CT system, which can be:

     * the ABCD matrix,
     * a list-like containing the A, B, C, D matrices,
     * a list of zpk tuples (internally converted to SS representation),
     * a list of LTI objects.

    t : array_like
        The edge times of the DAC pulse used to make CT waveforms
        from DT inputs. Each row corresponds to one of the system
        inputs; [-1 -1] denotes a CT input. The default is [0 1],
        for all inputs except the first.

    f0 : float
         The (normalized) frequency at which the Gp filters' gains are
         to be set to unity. Default 0 (DC).

    **Output:**

    sys : tuple
         the LTI description for the DT equivalent, in A, B, C, D
         representation.

    Gp : list of lists
         the mixed CT/DT prefilters which form the samples
         fed to each state for the CT inputs.

    **Example:**

    Map the standard second order CT modulator shown below to its CT
    equivalent and verify that its NTF is :math:`(1-z^{-1})^2`.

    .. image:: ../doc/_static/mapCtoD.png
        :align: center
        :alt: mapCtoD block diagram

    It can be done as follows::

        from __future__ import print_function
        import numpy as np
        from scipy.signal import lti
        from deltasigma import *
        LFc = lti([[0, 0], [1, 0]], [[1, -1], [0, -1.5]], [[0, 1]], [[0, 0]])
        tdac = [0, 1]
        LF, Gp = mapCtoD(LFc, tdac)
        LF = lti(*LF)
        ABCD = np.vstack((
                np.hstack((LF.A, LF.B)),
                np.hstack((LF.C, LF.D))
               ))
        NTF, STF = calculateTF(ABCD)
        print("NTF:") # after rounding to a 1e-6 resolution
        print("Zeros:", np.real_if_close(np.round(NTF.zeros, 6)))
        print("Poles:", np.real_if_close(np.round(NTF.poles, 6)))

    Prints::

        Zeros: [ 1.  1.]
        Poles: [ 0.  0.]

    Equivalent to::

               (z -1)^2
        NTF = ----------
                 z^2

    .. seealso:: R. Schreier and B. Zhang, "Delta-sigma modulators employing \
    continuous-time circuitry," IEEE Transactions on Circuits and Systems I, \
    vol. 43, no. 4, pp. 324-332, April 1996.
    """
    # You need to have A, B, C, D specification of the system
    Ac, Bc, Cc, Dc = _getABCD(sys_c)
    ni = Bc.shape[1]
    # Sanitize t
    if hasattr(t, 'tolist'):
        t = t.tolist()
    if (type(t) == tuple or type(t) == list) and np.isscalar(t[0]):
        t = [t]  # we got a simple list, like the default value
    if not (type(t) == tuple or type(t) == list) and \
       not (type(t[0]) == tuple or type(t[0]) == list):
        raise ValueError("The t argument has an unrecognized shape")
    # back to business
    t = np.array(t)
    if t.shape == (1, 2) and ni > 1:
        t = np.vstack((np.array([[-1, -1]]), np.dot(np.ones((ni - 1, 1)), t)))
    if t.shape != (ni, 2):
        raise ValueError('The t argument has the wrong dimensions.')
    di = np.ones(ni).astype(bool)
    for i in range(ni):
        if t[i, 0] == -1 and t[i, 1] == -1:
            di[i] = False

    # c2d assumes t1=0, t2=1.
    # Also c2d often complains about poor scaling and can even produce
    # incorrect results.
    A, B, C, D, _ = cont2discrete((Ac, Bc, Cc, Dc), 1, method='zoh')
    Bc1 = Bc[:, ~di]

    # Examine the discrete-time inputs to see how big the
    # augmented matrices need to be.
    B1 = B[:, ~di]
    D1 = D[:, ~di]
    n = A.shape[0]
    t2 = np.ceil(t[di, 1]).astype(np.int_)
    esn = (t2 == t[di, 1]) and (D[0, di] != 0).T  # extra states needed?
    npp = n + np.max(t2 - 1 + 1 * esn)

    # Augment A to npp x npp, B to np x 1, C to 1 x np.
    Ap = padb(padr(A, npp), npp)
    for i in range(n + 1, npp):
        Ap[i, i - 1] = 1
    Bp = np.zeros((npp, 1))
    if npp > n:
        Bp[n, 0] = 1
    Cp = padr(C, npp)
    Dp = np.zeros((1, 1))

    # Add in the contributions from each DAC
    for i in np.flatnonzero(di):
        t1 = t[i, 0]
        t2 = t[i, 1]
        B2 = B[:, i]
        D2 = D[:, i]
        if t1 == 0 and t2 == 1 and D2 == 0:  # No fancy stuff necessary
            Bp = Bp + padb(B2, npp)
        else:
            n1 = np.floor(t1)
            n2 = np.ceil(t2) - n1 - 1
            t1 = t1 - n1
            t2 = t2 - n2 - n1
            if t2 == 1 and D2 != 0:
                n2 = n2 + 1
                extraStateNeeded = 1
            else:
                extraStateNeeded = 0
            nt = n + n1 + n2
            if n2 > 0:
                if t2 == 1:
                    Ap[:n,
                       nt - n2:nt] = Ap[:n, nt - n2:nt] + np.tile(B2, (1, n2))
                else:
                    Ap[:n, nt - n2:nt -
                       1] = Ap[:n, nt - n2:nt - 1] + np.tile(B2, (1, n2 - 1))
                    Ap[:n,
                       (nt - 1)] = Ap[:n,
                                      (nt - 1)] + _B2formula(Ac, 0, t2, B2)
            if n2 > 0:  # pulse extends to the next period
                Btmp = _B2formula(Ac, t1, 1, B2)
            else:  # pulse ends in this period
                Btmp = _B2formula(Ac, t1, t2, B2)
            if n1 > 0:
                Ap[:n, n + n1 - 1] = Ap[:n, n + n1 - 1] + Btmp
            else:
                Bp = Bp + padb(Btmp, npp)
            if n2 > 0:
                Cp = Cp + padr(
                    np.hstack((np.zeros((D2.shape[0], n + n1)), D2 * np.ones(
                        (1, n2)))), npp)
    sys = (Ap, Bp, Cp, Dp)
    if np.any(~di):
        # Compute the prefilters and add in the CT feed-ins.
        # Gp = inv(sI - Ac)*(zI - A)/z*Bc1
        n, m = Bc1.shape
        Gp = np.empty_like(np.zeros((n, m)), dtype=object)
        # !!Make this like stf: an array of zpk objects
        ztf = np.empty_like(Bc1, dtype=object)
        # Compute the z-domain portions of the filters
        ABc1 = np.dot(A, Bc1)
        for h in range(m):
            for i in range(n):
                if Bc1[i, h] == 0:
                    ztf[i, h] = (np.array([]), np.array([0.]), -ABc1[i, h]
                                 )  # dt=1
                else:
                    ztf[i, h] = (np.atleast_1d(ABc1[i, h] / Bc1[i, h]),
                                 np.array([0.]), Bc1[i, h])  # dt = 1
        # Compute the s-domain portions of each of the filters
        stf = np.empty_like(np.zeros((n, n)),
                            dtype=object)  # stf[out, in] = zpk
        for oi in range(n):
            for ii in range(n):
                # Doesn't do pole-zero cancellation
                stf[oi, ii] = ss2zpk(Ac,
                                     np.eye(n),
                                     np.eye(n)[oi, :],
                                     np.zeros((1, n)),
                                     input=ii)
                # scipy as of v 0.13 has no support for LTI MIMO systems
                # only 'MISO', therefore you can't write:
                # stf = ss2zpk(Ac, eye(n), eye(n), np.zeros(n, n)))
        for h in range(m):
            for i in range(n):
                # k = 1 unneded, see below
                for j in range(n):
                    # check the k values for a non-zero term
                    if stf[i, j][2] != 0 and ztf[j, h][2] != 0:
                        if Gp[i, h] is None:
                            Gp[i, h] = {}
                            Gp[i, h].update({'Hs': [list(stf[i, j])]})
                            Gp[i, h].update({'Hz': [list(ztf[j, h])]})
                        else:
                            Gp[i, h].update(
                                {'Hs': Gp[i, h]['Hs'] + [list(stf[i, j])]})
                            Gp[i, h].update(
                                {'Hz': Gp[i, h]['Hz'] + [list(ztf[j, h])]})
                        # the MATLAB-like cell code for the above statements would have
                        # been:
                        #Gp[i, h](k).Hs = stf[i, j]
                        #Gp[i, h](k).Hz = ztf[j, h]
                        #k = k + 1
        if f0 != 0:  # Need to correct the gain terms calculated by c2d
            # B1 = gains of Gp @f0;
            for h in range(m):
                for i in range(n):
                    B1ih = np.real_if_close(evalMixedTF(Gp[i, h], f0))
                    # abs() used because ss() whines if B has complex entries...
                    # This is clearly incorrect.
                    # I've fudged the complex stuff by including a sign....
                    B1[i, h] = np.abs(B1ih) * np.sign(np.real(B1ih))
                    if np.abs(B1[i, h]) < 1e-09:
                        B1[i,
                           h] = 1e-09  # This prevents NaN in "line 174" below
        # Adjust the gains of the pre-filters
        for h in range(m):
            for i in range(n):
                for j in range(max(len(Gp[i, h]['Hs']), len(Gp[i, h]['Hz']))):
                    # The next is "line 174"
                    Gp[i, h]['Hs'][j][2] = Gp[i, h]['Hs'][j][2] / B1[i, h]
        sys = (
            sys[0],  # Ap
            np.hstack((padb(B1, npp), sys[1])),  # new B
            sys[2],  # Cp
            np.hstack((D1, sys[3])))  # new D
    return sys, Gp
def realizeNTF_ct(ntf,
                  form='FB',
                  tdac=(0, 1),
                  ordering=None,
                  bp=None,
                  ABCDc=None,
                  method='LOOP'):
    """Realize an NTF with a continuous-time loop filter.

    **Parameters:**

    ntf : object
        A noise transfer function (NTF).

    form : str, optional
        A string specifying the topology of the loop filter.

         * 'FB': Feedback form,
         * 'FF': Feedforward form

        For the FB structure, the elements of ``Bc`` are calculated
        so that the sampled pulse response matches the L1 impulse
        response.  For the FF structure, ``Cc`` is calculated.

    tdac : sequence, optional
        The timing for the feedback DAC(s). If ``tdac[0] >= 1``,
        direct feedback terms are added to the quantizer.

        Multiple timings (one or more per integrator) for the FB
        topology can be specified by making tdac a list of lists,
        e.g. ``tdac = [[1, 2], [1, 2], [[0.5, 1], [1, 1.5]], []]``

        In this example, the first two integrators have
        DACs with ``[1, 2]`` timing, the third has a pair of
        DACs, one with ``[0.5, 1]`` timing and the other with
        ``[1, 1.5]`` timing, and there is no direct feedback
        DAC to the quantizer.

    ordering : sequence, optional
        A vector specifying which NTF zero-pair to use in each resonator
        Default is for the zero-pairs to be used in the order specified
        in the NTF.

    bp : sequence, optional
        A vector specifying which resonator sections are bandpass.
        The default (``zeros(...)``) is for all sections to be lowpass.

    ABCDc : ndarray, optional
        The loop filter structure, in state-space form.
        If this argument is omitted, ABCDc is constructed according
        to "form."

    method : str, optional
        The default fitting method is ``'LOOP'``, which means that
        the DT and CT loop responses will be matched.
        Alternatively, it is possible to set the method to ``'NTF'``,
        which will result in the NTF responses to be matched.
        See :ref:`discrete-time-to-continuous-time-mapping` for a
        more in-depth discussion.

    **Returns:**

    ABCDc : ndarray
        A state-space description of the CT loop filter

    tdac2 : ndarray
        A matrix with the DAC timings, including ones
        that were automatically added.

    **Example:**

    Realize the NTF :math:`(1 - z^{-1})^2` with a CT system (cf with the
    example at :func:`mapCtoD`).::

        from deltasigma import *
        ntf = ([1, 1], [0, 0], 1)
        ABCDc, tdac2 = realizeNTF_ct(ntf, 'FB')

    Returns:

    ABCDc::

        [[ 0.          0.          1.         -1.        ]
         [ 1.          0.          0.         -1.49999999]
         [ 0.          1.          0.          0.        ]]

    tdac2::

        [[-1. -1.]
         [ 0.  1.]]

    """
    ntf_z, ntf_p, _ = _get_zpk(ntf)
    ntf_z = carray(ntf_z)
    ntf_p = carray(ntf_p)
    order = max(ntf_p.shape)
    order2 = order // 2
    odd = order - 2 * order2
    # compensate for limited accuracy of zero calculation
    ntf_z[np.abs(ntf_z - 1) < eps**(1. / (1. + order))] = 1.
    method = method.upper()
    if method not in ('LOOP', 'NTF'):
        raise ValueError('Unimplemented matching method %s.' % method)
    # check if multiple timings mode
    if (type(tdac) == list or type(tdac) == tuple) and len(tdac) and \
       (type(tdac[0]) == list or type(tdac[0]) == tuple):
        if len(tdac) != order + 1:
            msg = 'For multi-timing tdac, len(tdac) ' + \
                  ' must be order+1.'
            raise ValueError(msg)
        if form != 'FB':
            msg = "Currently only supporting form='FB' " + \
                  'for multi-timing tdac'
            raise ValueError(msg)
        multi_timing = True
    else:  # single timing
        tdac = carray(tdac)
        if np.prod(tdac.shape) != 2:
            msg = 'For single-timing tdac, len(tdac) must be 2.'
            raise ValueError(msg)
        tdac.reshape((2, ))
        multi_timing = False
    if ordering is None:
        ordering = np.arange(order2)
    if bp is None:
        bp = np.zeros((order2, ))
    if not multi_timing:
        # Need direct terms for every interval of memory in the DAC
        n_direct = int(np.ceil(tdac[1])) - 1
        if tdac[0] > 0 and tdac[0] < 1 and tdac[1] > 1 and tdac[1] < 2:
            n_extra = n_direct - 1  #  tdac pulse spans a sample point
        else:
            n_extra = n_direct
        tdac2 = np.vstack((np.array((-1, -1)), np.array(tdac).reshape(
            (1, 2)), 0.5 * np.dot(np.ones((n_extra, 1)), np.array([[-1, 1]])) +
                           np.cumsum(np.ones(
                               (n_extra, 2)), 0) + (n_direct - n_extra)))
    else:
        n_direct = 0
        n_extra = 0
    if ABCDc is None:
        ABCDc = np.zeros((order + 1, order + 2))
        # Stuff the A portion
        if odd:
            ABCDc[0, 0] = np.real(np.log(ntf_z[0]))
            ABCDc[1, 0] = 1
        dline = np.array([0, 1, 2])
        for i in range(order2):
            n = bp[i]
            i1 = 2 * i + odd
            zi = 2 * ordering[i] + odd
            w = np.abs(np.angle(ntf_z[zi]))
            ABCDc[i1 + dline, i1] = np.array([0, 1, n])
            ABCDc[i1 + dline, i1 + 1] = np.array([-w**2, 0, 1 - n])
        ABCDc[0, order] = 1
        # 2006.10.02 Changed to -1 to make FF STF have +ve gain at DC
        ABCDc[0, order + 1] = -1
    Ac = ABCDc[:order, :order]
    if form == 'FB':
        Cc = ABCDc[order, :order].reshape((1, -1))
        if not multi_timing:
            Bc = np.hstack((np.eye(order), np.zeros((order, 1))))
            Dc = np.hstack((np.zeros((1, order)), np.array([[1]])))
            tp = np.tile(np.array(tdac).reshape((1, 2)), (order + 1, 1))
        else:  #Assemble tdac2, Bc and Dc
            tdac2 = np.array([[-1, -1]])
            Bc = None
            Dc = None
            Bci = np.hstack((np.eye(order), np.zeros((order, 1))))
            Dci = np.hstack((np.zeros((1, order)), np.array([[1]])))
            for i in range(len(tdac)):
                tdi = tdac[i]
                if (type(tdi) in (tuple, list)) and len(tdi) and \
                   (type(tdi[0]) in (list, tuple)):
                    for j in range(len(tdi)):
                        tdj = tdi[j]
                        tdac2 = np.vstack((tdac2, np.array(tdj).reshape(1,
                                                                        -1)))
                        if Bc is not None:
                            Bc = np.hstack((Bc, Bci[:, i].reshape((-1, 1))))
                        else:
                            Bc = Bci[:, i].reshape((-1, 1))
                        if Dc is not None:
                            Dc = np.hstack((Dc, Dci[:, i].reshape((-1, 1))))
                        else:
                            Dc = Dci[:, i].reshape((-1, 1))
                elif len(
                        tdi):  # we got tdac[i] = [a, b] where a, b are scalars
                    tdac2 = np.vstack((tdac2, np.array(tdi).reshape(1, -1)))
                    if Bc is not None:
                        Bc = np.hstack((Bc, Bci[:, i].reshape((-1, 1))))
                    else:
                        Bc = Bci[:, i].reshape((-1, 1))
                    if Dc is not None:
                        Dc = np.hstack((Dc, Dci[:, i].reshape((-1, 1))))
                    else:
                        Dc = Dci[:, i].reshape((-1, 1))
            tp = tdac2[1:, :]
    elif form == 'FF':
        Cc = np.vstack((np.eye(order), np.zeros((1, order))))
        Bc = np.vstack((np.array([[-1]]), np.zeros((order - 1, 1))))
        Dc = np.vstack((np.zeros((order, 1)), np.array([[1]])))
        tp = tdac  #  2008-03-24 fix from Ayman Shabra
    else:
        raise ValueError('Sorry, no code for form "%s".', form)

    n_imp = int(np.ceil(2 * order + np.max(tdac2[:, 1]) + 1))
    if method == 'LOOP':
        # Sample the L1 impulse response
        y = impL1(ntf, n_imp)
    else:
        # Sample the NTF impulse response
        y = dimpulse((ntf_z, ntf_p, 1., 1.), t=np.arange(n_imp + 1))[1][0]
        y = np.atleast_1d(y.squeeze())
    sys_c = []
    for i in range(Bc.shape[1]):  # number of inputs
        sys_tmp = []
        for j in range(Cc.shape[0]):  # number of outputs
            sys_tmp.append(ss2zpk(Ac, Bc, Cc[j, :], Dc[j, :], input=i))
        sys_c.append(sys_tmp)
    yy = pulse(sys_c, tp, 1, n_imp, 1)
    yy = np.squeeze(yy)
    # Endow yy with n_extra extra impulses.
    # These will need to be implemented with n_extra extra DACs.
    # !! Note: if t1=int, matlab says pulse(sys) @t1 ~=0
    # !! This code corrects this problem.
    if n_extra > 0:
        y_right = padb(np.vstack((np.zeros((1, n_direct)), np.eye(n_direct))),
                       n_imp + 1)
        # Replace the last column in yy with an ordered set of impulses
        if (n_direct > n_extra):
            yy = np.hstack((yy, y_right[:, 1:]))
        else:
            yy = np.hstack((yy[:, :-1], y_right))

    if method == 'NTF':
        # convolve CT loop response and NTF response
        yynew = None
        for i in range(yy.shape[1]):
            yytmp = np.convolve(yy[:, i], y)[:-n_imp]
            if yynew is None:
                yynew = yytmp.reshape((-1, 1))
            else:
                yynew = np.hstack((yynew, yytmp.reshape((-1, 1))))
        yy = yynew
        e1 = np.zeros(y.shape)
        e1[0] = 1.
        y = y - e1
    # Solve for the coefficients
    x = linalg.lstsq(yy, y)[0]
    if linalg.norm(np.dot(yy, x) - y) > 0.0001:
        warn('Pulse response fit is poor.')
    if form == 'FB':
        if not multi_timing:
            Bc2 = np.hstack((x[:order].reshape(
                (-1, 1)), np.zeros((order, n_extra))))
            if n_extra > 0:
                Dc2 = np.hstack((np.array([[0]]), x[order:].reshape((-1, 1))))
            else:
                Dc2 = x[order:].reshape((-1, 1))
        else:
            BcDc = np.vstack((Bc, Dc))
            i = np.nonzero(BcDc)
            BcDc[i] = x
            Bc2 = BcDc[:-1, :]
            Dc2 = BcDc[-1, :]
    elif form == 'FF':
        Bc2 = np.hstack((Bc, np.zeros((order, n_extra))))
        Cc = x[:order].reshape((1, -1))
        if n_extra > 0:
            Dc2 = np.hstack((np.array([[0]]), x[order:].T))
        else:
            Dc2 = x[order:].T

    Dc1 = np.zeros((1, 1))
    Dc = np.hstack((Dc1, np.atleast_2d(Dc2)))
    Bc1 = np.vstack((np.ones((1, 1)), np.zeros((order - 1, 1))))
    Bc = np.hstack((Bc1, Bc2))
    # Scale Bc1 for unity STF magnitude at f0
    fz = np.angle(ntf_z) / (2 * np.pi)
    f1 = fz[0]
    ibz = np.abs(fz - f1) <= np.abs(fz + f1)
    fz = fz[ibz]
    f0 = np.mean(fz)
    if np.min(np.abs(fz)) < 3 * np.min(np.abs(fz - f0)):
        f0 = 0
    L0c = ss2zpk(Ac, Bc1, Cc, Dc1)
    G0 = evalTFP(L0c, ntf, f0)
    if f0 == 0:
        Bc[:, 0] = np.dot(
            Bc[:, 0],
            np.abs(
                np.dot(Bc[0, 1:], (tdac2[1:, 1] - tdac2[1:, 0])) / Bc[0, 0]))
    else:
        Bc[:, 0] = Bc[:, 0] / np.abs(G0)
    ABCDc = np.vstack((np.hstack((Ac, Bc)), np.hstack((Cc, Dc))))
    #ABCDc = np.dot(ABCDc, np.abs(ABCDc) > eps**(1./2.))
    ABCDc[np.nonzero(np.abs(ABCDc) < eps**(1. / 2))] = 0.
    return ABCDc, tdac2
Esempio n. 10
0
def calculateTF(ABCD, k=1.):
    """Calculate the NTF and STF of a delta-sigma modulator.

    The calculation is performed for a given loop filter
    ABCD matrix, assuming a quantizer gain of ``k``.

    **Parameters:**

    ABCD : array_like,
        The ABCD matrix that describes the system.

    k : float or ndarray-like, optional
        The quantizer gains. If only one quantizer is present, it may be set
        to a float, corresponding to the quantizer gain. If multiple quantizers
        are present, a list should be used, with quantizer gains ordered
        according to the order in which the quantizer inputs appear in the
        ``C`` and ``D`` submatrices. If not specified, a default of one quantizer
        with gain ``1.`` is assumed.

    **Returns:**

    (NTF, STF) : a tuple of two LTI objects (or of two lists of LTI objects).
                 If a version of the ``scipy`` library equal to 0.16.x or 
                 greater is in use, the objects will be ``ZeroPolesGain``
                 objects, a subclass of ``scipy.signal.lti``.

    If the system has multiple quantizers, multiple STFs and NTFs will be
    returned.

    In that case:

    * ``STF[i]`` is the STF from ``u`` to output number ``i``.
    * ``NTF[i, j]`` is the NTF from the quantization noise of the quantizer
      number ``j`` to output number ``i``.

    **Note:**

    Setting ``k`` to a list is unsupported in the MATLAB code (last checked
    Nov. 2014).

    **Example:**

    Realize a fifth-order modulator with the cascade-of-resonators structure,
    feedback form. Calculate the ABCD matrix of the loop filter and verify
    that the NTF and STF are correct.

    .. code-block:: python

        from deltasigma import *
        H = synthesizeNTF(5, 32, 1)
        a, g, b, c = realizeNTF(H)
        ABCD = stuffABCD(a,g,b,c)
        ntf, stf = calculateTF(ABCD)

    From which we get:

    ``H``::

             (z -1) (z^2 -1.997z +1) (z^2 -1.992z +0.9999)
        --------------------------------------------------------
         (z -0.7778) (z^2 -1.796z +0.8549) (z^2 -1.613z +0.665)

    coefficients::

        a: 0.0007, 0.0084, 0.055, 0.2443, 0.5579
        g: 0.0028, 0.0079
        b: 0.0007, 0.0084, 0.055, 0.2443, 0.5579, 1.0
        c: 1.0, 1.0, 1.0, 1.0, 1.0

    ABCD matrix::

        [[  1.00000000e+00   0.00000000e+00   0.00000000e+00   0.00000000e+00
            0.00000000e+00   6.75559806e-04  -6.75559806e-04]
         [  1.00000000e+00   1.00000000e+00  -2.79396240e-03   0.00000000e+00
            0.00000000e+00   8.37752565e-03  -8.37752565e-03]
         [  1.00000000e+00   1.00000000e+00   9.97206038e-01   0.00000000e+00
            0.00000000e+00   6.33294166e-02  -6.33294166e-02]
         [  0.00000000e+00   0.00000000e+00   1.00000000e+00   1.00000000e+00
           -7.90937431e-03   2.44344030e-01  -2.44344030e-01]
         [  0.00000000e+00   0.00000000e+00   1.00000000e+00   1.00000000e+00
            9.92090626e-01   8.02273699e-01  -8.02273699e-01]
         [  0.00000000e+00   0.00000000e+00   0.00000000e+00   0.00000000e+00
            1.00000000e+00   1.00000000e+00   0.00000000e+00]]

    NTF::

             (z -1) (z^2 -1.997z +1) (z^2 -1.992z +0.9999)
        --------------------------------------------------------
         (z -0.7778) (z^2 -1.796z +0.8549) (z^2 -1.613z +0.665)

    STF::

        1

    """

    nq = len(k) if type(k) in (tuple, list) else 1
    A, B, C, D = partitionABCD(ABCD, m=nq + 1, r=nq)
    k = carray(k)
    diagk = np.atleast_2d(np.diag(k))

    B1 = B[:, 0]
    B2 = B[:, 1:]
    B1 = B1.reshape((B1.shape[0], 1)) if len(B1.shape) == 1 else B1
    B2 = B2.reshape((B2.shape[0], 1)) if len(B2.shape) == 1 else B2

    # In a single-quantizer system, D2 should be all zeros, as any
    # non-zero term in D2 would imply we got a delay-free loop.
    # The original MATLAB code implies so.
    # The trouble arises when we adapt and extend the code to calculate
    # the transfer functions for multiple-quantizer systems. There the
    # off-diagonal terms of D2 may very well be non-zero, therefore
    # in the following we consider D2.
    # this means that if you supply an ABCD matrix, with one quantizer
    # and an (erroneously) zero-delay loop, the MATLAB toolbox will
    # disregard D2 and pretend it's zero and the loop is correctly
    # delayed, spitting out the corresponding TF.
    # Instead here we print out a warning and process the ABCD matrix
    # with the user-supplied, non-zero D2 matrix.
    # The resulting TFs will obviously be different.

    D1 = D[:, 0]
    D2 = D[:, 1:]
    D1 = D1.reshape((D1.shape[0], 1)) if len(D1.shape) == 1 else D1
    D2 = D2.reshape((D2.shape[0], 1)) if len(D2.shape) == 1 else D2

    # WARN DELAY FREE LOOPS
    if np.diag(D2).any():
        warn("Delay free loop detected! D2 diag: %s", str(np.diag(D2)))

    # Find the noise transfer function by forming the closed-loop
    # system (sys_cl) in state-space form.
    Ct = np.linalg.inv(np.eye(nq) - D2 * diagk)
    Acl = A + np.dot(B2, np.dot(Ct, np.dot(diagk, C)))
    Bcl = np.hstack(
        (B1 + np.dot(B2, np.dot(Ct, np.dot(diagk, D1))), np.dot(B2, Ct)))
    Ccl = np.dot(Ct, np.dot(diagk, C))
    Dcl = np.dot(Ct, np.hstack((np.dot(diagk, D1), np.eye(nq))))
    tol = min(1e-3, max(1e-6, eps**(1 / ABCD.shape[0])))
    ntfs = np.empty((nq, Dcl.shape[0]), dtype=np.object_)
    stfs = np.empty((Dcl.shape[0], ), dtype=np.object_)

    # sweep the outputs 'cause scipy is silly but we love it anyhow.
    for i in range(Dcl.shape[0]):
        # input #0 is the signal
        # inputs #1,... are quantization noise
        stf_z, stf_p, stf_k = ss2zpk(Acl, Bcl, Ccl[i, :], Dcl[i, :], input=0)
        stf = lti(stf_z, stf_p, stf_k)
        for j in range(nq):
            ntf_z, ntf_p, ntf_k = ss2zpk(Acl,
                                         Bcl,
                                         Ccl[i, :],
                                         Dcl[i, :],
                                         input=j + 1)
            ntf = lti(ntf_z, ntf_p, ntf_k)
            stf_min, ntf_min = minreal((stf, ntf), tol)
            ntfs[i, j] = ntf_min
        stfs[i] = stf_min

    # if we have one stf and one ntf, then just return those in a list
    if ntfs.shape == (1, 1):
        return [ntfs[0, 0], stfs[0]]
    return ntfs, stfs
Esempio n. 11
0
def calculateTF(ABCD, k=1.):
    """Calculate the NTF and STF of a delta-sigma modulator.

    The calculation is performed for a given loop filter
    ABCD matrix, assuming a quantizer gain of ``k``.

    **Parameters:**

    ABCD : array_like,
        The ABCD matrix that describes the system.

    k : float, optional
        The quantizer gain. If not specified, a default value of 1 is used.

    **Returns:**

    (NTF, STF) : a tuple of two LTI objects.

    **Example:**

    Realize a fifth-order modulator with the cascade-of-resonators structure,
    feedback form. Calculate the ABCD matrix of the loop filter and verify
    that the NTF and STF are correct.

    .. code-block:: python

        from deltasigma import *
        H = synthesizeNTF(5, 32, 1)
        a, g, b, c = realizeNTF(H)
        ABCD = stuffABCD(a,g,b,c)
        ntf, stf = calculateTF(ABCD)

    From which we get:

    ``H``::

             (z -1) (z^2 -1.997z +1) (z^2 -1.992z +0.9999)
        --------------------------------------------------------
         (z -0.7778) (z^2 -1.796z +0.8549) (z^2 -1.613z +0.665)

    coefficients::

        a: 0.0007, 0.0084, 0.055, 0.2443, 0.5579
        g: 0.0028, 0.0079
        b: 0.0007, 0.0084, 0.055, 0.2443, 0.5579, 1.0
        c: 1.0, 1.0, 1.0, 1.0, 1.0

    ABCD matrix::

        [[  1.00000000e+00   0.00000000e+00   0.00000000e+00   0.00000000e+00
            0.00000000e+00   6.75559806e-04  -6.75559806e-04]
         [  1.00000000e+00   1.00000000e+00  -2.79396240e-03   0.00000000e+00
            0.00000000e+00   8.37752565e-03  -8.37752565e-03]
         [  1.00000000e+00   1.00000000e+00   9.97206038e-01   0.00000000e+00
            0.00000000e+00   6.33294166e-02  -6.33294166e-02]
         [  0.00000000e+00   0.00000000e+00   1.00000000e+00   1.00000000e+00
           -7.90937431e-03   2.44344030e-01  -2.44344030e-01]
         [  0.00000000e+00   0.00000000e+00   1.00000000e+00   1.00000000e+00
            9.92090626e-01   8.02273699e-01  -8.02273699e-01]
         [  0.00000000e+00   0.00000000e+00   0.00000000e+00   0.00000000e+00
            1.00000000e+00   1.00000000e+00   0.00000000e+00]]

    NTF::

             (z -1) (z^2 -1.997z +1) (z^2 -1.992z +0.9999)
        --------------------------------------------------------
         (z -0.7778) (z^2 -1.796z +0.8549) (z^2 -1.613z +0.665)

    STF::

        1

    """
    A, B, C, D = partitionABCD(ABCD)
    if B.shape[1] > 1:
        B1 = B[:, 0]
        B2 = B[:, 1]
        B1 = B1.reshape((B1.shape[0], 1)) if len(B1.shape) == 1 else B1
        B2 = B2.reshape((B2.shape[0], 1)) if len(B2.shape) == 1 else B2
    else:
        B1 = B
        B2 = B

    # Find the noise transfer function by forming the closed-loop
    # system (sys_cl) in state-space form.
    Acl = A + k * np.dot(B2, C)
    Bcl = np.hstack((B1 + k*B2*D[0, 0], B2))
    Ccl = k*C
    Dcl = np.array((k*D[0, 0], 1.))
    Dcl = Dcl.reshape((1, Dcl.shape[0])) if len(Dcl.shape) == 1 else Dcl
    tol = min(1e-3, max(1e-6, eps**(1/ABCD.shape[0])))
    # input #0 is the signal
    # input #1 is the quantization noise
    stf_p, stf_z, stf_k  = ss2zpk(Acl, Bcl, Ccl, Dcl, input=0)
    ntf_p, ntf_z, ntf_k = ss2zpk(Acl, Bcl, Ccl, Dcl, input=1)
    stf = lti(stf_p, stf_z, stf_k)
    ntf = lti(ntf_p, ntf_z, ntf_k)
    stf_min, ntf_min = minreal((stf, ntf), tol)
    return ntf_min, stf_min
Esempio n. 12
0
_sys2ss = {
    _LSYS: lambda sys: _ss(sys.ss),
    _LFILT: lambda sys: _ss(tf2ss(sys.num, sys.den)),
    _NUM: lambda sys: _ss(tf2ss(sys, 1)),
    _TF: lambda sys: _ss(tf2ss(*sys)),
    _ZPK: lambda sys: _ss(zpk2ss(*sys)),
    _SS: lambda sys: _ss(sys),
}

_sys2zpk = {
    _LSYS: lambda sys: sys.zpk,
    _LFILT: lambda sys: tf2zpk(sys.num, sys.den),
    _NUM: lambda sys: tf2zpk(sys, 1),
    _TF: lambda sys: tf2zpk(*sys),
    _ZPK: lambda sys: sys,
    _SS: lambda sys: ss2zpk(*sys),
}

_sys2tf = {
    _LSYS: lambda sys: sys.tf,
    _LFILT: lambda sys: _tf(sys.num, sys.den),
    _NUM: lambda sys: _tf(sys, 1),
    _TF: lambda sys: _tf(*sys),
    _ZPK: lambda sys: _tf(*zpk2tf(*sys)),
    _SS: lambda sys: _tf(*_ss2tf(*sys)),
}


def sys2ss(sys):
    """Converts an LTI system in any form to state-space."""
    return _sys2ss[_sys2form(sys)](sys)
Esempio n. 13
0
def realizeNTF_ct(ntf, form='FB', tdac=(0, 1), ordering=None, bp=None,
                  ABCDc=None):
    """Realize an NTF with a continuous-time loop filter.
    
    **Parameters:**

    ntf : object
        A noise transfer function (NTF).
    
    form : str, optional
        A string specifying the topology of the loop filter.

         * 'FB': Feedback form,
         * 'FF': Feedforward form

        For the FB structure, the elements of ``Bc`` are calculated
        so that the sampled pulse response matches the L1 impulse
        response.  For the FF structure, ``Cc`` is calculated.
    
    tdac : sequence, optional
        The timing for the feedback DAC(s). If ``tdac[0] >= 1``,
        direct feedback terms are added to the quantizer.

        Multiple timings (one or more per integrator) for the FB 
        topology can be specified by making tdac a list of lists,
        e.g. ``tdac = [[1, 2], [1, 2], [[0.5, 1], [1, 1.5]], []]``

        In this example, the first two integrators have
        DACs with ``[1, 2]`` timing, the third has a pair of
        DACs, one with ``[0.5, 1]`` timing and the other with
        ``[1, 1.5]`` timing, and there is no direct feedback
        DAC to the quantizer.
    
    ordering : sequence, optional
        A vector specifying which NTF zero-pair to use in each resonator
        Default is for the zero-pairs to be used in the order specified 
        in the NTF.

    bp : sequence, optional
        A vector specifying which resonator sections are bandpass.
        The default (``zeros(...)``) is for all sections to be lowpass.

    ABCDc : ndarray, optional
        The loop filter structure, in state-space form.
        If this argument is omitted, ABCDc is constructed according 
        to "form."

    **Returns:**

    ABCDc : ndarray
        A state-space description of the CT loop filter

    tdac2 : ndarray
        A matrix with the DAC timings, including ones
        that were automatically added.

    **Example:**

    Realize the NTF :math:`(1 - z^{-1})^2` with a CT system (cf with the
    example at :func:`mapCtoD`).::

        from deltasigma import *
        ntf = ([1, 1], [0, 0], 1)
        ABCDc, tdac2 = realizeNTF_ct(ntf, 'FB') 

    Returns:

    ABCDc::

        [[ 0.          0.          1.         -1.        ]
         [ 1.          0.          0.         -1.49999999]
         [ 0.          1.          0.          0.        ]]

    tdac2::

        [[-1. -1.]
         [ 0.  1.]]

    """
    ntf_z, ntf_p, _ = _get_zpk(ntf)
    ntf_z = carray(ntf_z)
    ntf_p = carray(ntf_p)
    order = max(ntf_p.shape)
    order2 = int(np.floor(order/2.))
    odd = order - 2*order2
    # compensate for limited accuracy of zero calculation
    ntf_z[np.abs(ntf_z - 1) < eps**(1./(1. + order))] = 1.
    # check if multiple timings mode
    if (type(tdac) == list or type(tdac) == tuple) and len(tdac) and \
       (type(tdac[0]) == list or type(tdac[0]) == tuple):
        if len(tdac) != order + 1:
            msg = 'For multi-timing tdac, len(tdac) ' + \
                  ' must be order+1.'
            raise ValueError(msg)
        if form != 'FB':
            msg = "Currently only supporting form='FB' " + \
                  'for multi-timing tdac'
            raise ValueError(msg)
        multi_timing = True
    else: # single timing
        tdac = carray(tdac)
        if np.prod(tdac.shape) != 2:
            msg = 'For single-timing tdac, len(tdac) must be 2.'
            raise ValueError(msg)
        tdac.reshape((2,))
        multi_timing = False
    if ordering is None:
        ordering = np.arange(order2)
    if bp is None:
        bp = np.zeros((order2,))
    if not multi_timing:
        # Need direct terms for every interval of memory in the DAC
        n_direct = np.ceil(tdac[1]) - 1
        if tdac[0] > 0 and tdac[0] < 1 and tdac[1] > 1 and tdac[1] < 2:
            n_extra = n_direct - 1 #  tdac pulse spans a sample point
        else:
            n_extra = n_direct
        tdac2 = np.vstack(
                 (np.array((-1, -1)), 
                  np.array(tdac).reshape((1, 2)), 
                  0.5*np.dot(np.ones((n_extra, 1)), np.array([[-1, 1]])) 
                  + np.cumsum(np.ones((n_extra, 2)), 0) + (n_direct - n_extra)
                 ))
    else:
        n_direct = 0
        n_extra = 0
    if ABCDc is None:
        ABCDc = np.zeros((order + 1, order + 2))
        # Stuff the A portion
        if odd:
            ABCDc[0, 0] = np.real(np.log(ntf_z[0]))
            ABCDc[1, 0] = 1
        dline = np.array([0, 1, 2])
        for i in range(order2):
            n = bp[i]
            i1 = 2*i + odd
            zi = 2*ordering[i] + odd
            w = np.abs(np.angle(ntf_z[zi]))
            ABCDc[i1 + dline, i1] = np.array([0, 1, n])
            ABCDc[i1 + dline, i1 + 1] = np.array([-w**2, 0, 1 - n])
        ABCDc[0, order] = 1
        # 2006.10.02 Changed to -1 to make FF STF have +ve gain at DC
        ABCDc[0, order + 1] = -1
    Ac = ABCDc[:order, :order]
    if form == 'FB':
        Cc = ABCDc[order, :order].reshape((1, -1))
        if not multi_timing:
            Bc = np.hstack((np.eye(order), np.zeros((order, 1))))
            Dc = np.hstack((np.zeros((1, order)), np.array([[1]])))
            tp = np.tile(np.array(tdac).reshape((1, 2)), (order + 1, 1))
        else: #Assemble tdac2, Bc and Dc
            tdac2 = np.array([[-1, -1]])
            Bc = None
            Dc = None
            Bci = np.hstack((np.eye(order), np.zeros((order, 1))))
            Dci = np.hstack((np.zeros((1, order)), np.array([[1]])))
            for i in range(len(tdac)):
                tdi = tdac[i]
                if (type(tdi) in (tuple, list)) and len(tdi) and \
                   (type(tdi[0]) in (list, tuple)):
                    for j in range(len(tdi)):
                        tdj = tdi[j]
                        tdac2 = np.vstack((tdac2, 
                                           np.array(tdj).reshape(1,-1)))
                        if Bc is not None:
                            Bc = np.hstack((Bc, Bci[:, i].reshape((-1, 1))))
                        else:
                            Bc = Bci[:, i].reshape((-1, 1))
                        if Dc is not None:
                            Dc = np.hstack((Dc, Dci[:, i].reshape((-1, 1))))
                        else:
                            Dc = Dci[:, i].reshape((-1, 1))
                elif len(tdi): # we got tdac[i] = [a, b] where a, b are scalars
                    tdac2 = np.vstack((tdac2,
                                       np.array(tdi).reshape(1,-1)))
                    if Bc is not None:
                        Bc = np.hstack((Bc, Bci[:, i].reshape((-1, 1))))
                    else:
                        Bc = Bci[:, i].reshape((-1, 1))
                    if Dc is not None:
                        Dc = np.hstack((Dc, Dci[:, i].reshape((-1, 1))))
                    else:
                        Dc = Dci[:, i].reshape((-1, 1))
            tp = tdac2[1:, :]
    elif form == 'FF':
        Cc = np.vstack((np.eye(order), np.zeros((1, order))))
        Bc = np.vstack((np.array([[-1]]), np.zeros((order-1, 1))))
        Dc = np.vstack((np.zeros((order, 1)), np.array([[1]])))
        tp = tdac #  2008-03-24 fix from Ayman Shabra
    else:
        raise ValueError('Sorry, no code for form "%s".', form)

    # Sample the L1 impulse response
    n_imp = np.ceil(2*order + np.max(tdac2[:, 1]) + 1)
    y = impL1(ntf, n_imp)
    sys_c = []
    for i in range(Bc.shape[1]): # number of inputs
        sys_tmp = []
        for j in range(Cc.shape[0]): # number of outputs
            sys_tmp.append(ss2zpk(Ac, Bc, Cc[j, :], Dc[j, :], input=i))
        sys_c.append(sys_tmp)
    yy = pulse(sys_c, tp, 1, n_imp, 1)
    yy = np.squeeze(yy)
    # Endow yy with n_extra extra impulses.
    # These will need to be implemented with n_extra extra DACs.
    # !! Note: if t1=int, matlab says pulse(sys) @t1 ~=0
    # !! This code corrects this problem.
    if n_extra > 0:
        y_right = padb(np.vstack((np.zeros((1, n_direct)), 
                                  np.eye(n_direct))),
                       n_imp + 1)
        # Replace the last column in yy with an ordered set of impulses
        if (n_direct > n_extra):
            yy = np.hstack((yy, y_right[:, 1:]))
        else:
            yy = np.hstack((yy[:, :-1], y_right))

    # Solve for the coefficients
    x = linalg.lstsq(yy, y)[0]
    if linalg.norm(np.dot(yy, x) - y) > 0.0001:
        warn('Pulse response fit is poor.')
    if form == 'FB':
        if not multi_timing:
            Bc2 = np.hstack((x[:order].reshape((-1, 1)), 
                             np.zeros((order, n_extra))))
            if n_extra > 0:
                Dc2 = np.hstack((np.array([[0]]), 
                                 x[order:].reshape((-1, 1))))
            else:
                Dc2 = x[order:].reshape((-1, 1))
        else:
            BcDc = np.vstack((Bc, Dc))
            i = np.nonzero(BcDc)
            BcDc[i] = x
            Bc2 = BcDc[:-1, :]
            Dc2 = BcDc[-1, :]
    elif form == 'FF':
        Bc2 = np.hstack((Bc, np.zeros((order, n_extra))))
        Cc = x[:order].reshape((1, -1))
        if n_extra > 0:
            Dc2 = np.hstack((np.array([[0]]), x[order:].T))
        else:
            Dc2 = x[order:].T

    Dc1 = np.zeros((1, 1))
    Dc = np.hstack((Dc1, np.atleast_2d(Dc2)))
    Bc1 = np.vstack((np.ones((1, 1)), np.zeros((order - 1, 1))))
    Bc = np.hstack((Bc1, Bc2))
    # Scale Bc1 for unity STF magnitude at f0
    fz = np.angle(ntf_z)/(2*np.pi)
    f1 = fz[0]
    ibz = np.abs(fz - f1) <= np.abs(fz + f1)
    fz = fz[ibz]
    f0 = np.mean(fz)
    if np.min(np.abs(fz)) < 3*np.min(np.abs(fz - f0)):
        f0 = 0
    L0c = ss2zpk(Ac, Bc1, Cc, Dc1)
    G0 = evalTFP(L0c, ntf, f0)
    if f0 == 0:
        Bc[:, 0] = np.dot(Bc[:, 0], 
                          np.abs(np.dot(Bc[0, 1:], 
                                        (tdac2[1:, 1] - tdac2[1:, 0]))
                                 /Bc[0, 0]))
    else:
        Bc[:, 0] = Bc[:, 0]/np.abs(G0)
    ABCDc = np.vstack((
                      np.hstack((Ac, Bc)),
                      np.hstack((Cc, Dc))
                     ))
    #ABCDc = np.dot(ABCDc, np.abs(ABCDc) > eps**(1./2.))
    ABCDc[np.nonzero(np.abs(ABCDc) < eps**(1./2))] = 0.
    return ABCDc, tdac2
def calculateTF(ABCD, k=1.):
    """Calculate the NTF and STF of a delta-sigma modulator.

    The calculation is performed for a given loop filter
    ABCD matrix, assuming a quantizer gain of ``k``.

    **Parameters:**

    ABCD : array_like,
        The ABCD matrix that describes the system.

    k : float or ndarray-like, optional
        The quantizer gains. If only one quantizer is present, it may be set
        to a float, corresponding to the quantizer gain. If multiple quantizers
        are present, a list should be used, with quantizer gains ordered
        according to the order in which the quantizer inputs appear in the
        ``C`` and ``D`` submatrices. If not specified, a default of one quantizer
        with gain ``1.`` is assumed.

    **Returns:**

    (NTF, STF) : a tuple of two LTI objects (or of two lists of LTI objects).
                 If a version of the ``scipy`` library equal to 0.16.x or 
                 greater is in use, the objects will be ``ZeroPolesGain``
                 objects, a subclass of ``scipy.signal.lti``.

    If the system has multiple quantizers, multiple STFs and NTFs will be
    returned.

    In that case:

    * ``STF[i]`` is the STF from ``u`` to output number ``i``.
    * ``NTF[i, j]`` is the NTF from the quantization noise of the quantizer
      number ``j`` to output number ``i``.

    **Note:**

    Setting ``k`` to a list is unsupported in the MATLAB code (last checked
    Nov. 2014).

    **Example:**

    Realize a fifth-order modulator with the cascade-of-resonators structure,
    feedback form. Calculate the ABCD matrix of the loop filter and verify
    that the NTF and STF are correct.

    .. code-block:: python

        from deltasigma import *
        H = synthesizeNTF(5, 32, 1)
        a, g, b, c = realizeNTF(H)
        ABCD = stuffABCD(a,g,b,c)
        ntf, stf = calculateTF(ABCD)

    From which we get:

    ``H``::

             (z -1) (z^2 -1.997z +1) (z^2 -1.992z +0.9999)
        --------------------------------------------------------
         (z -0.7778) (z^2 -1.796z +0.8549) (z^2 -1.613z +0.665)

    coefficients::

        a: 0.0007, 0.0084, 0.055, 0.2443, 0.5579
        g: 0.0028, 0.0079
        b: 0.0007, 0.0084, 0.055, 0.2443, 0.5579, 1.0
        c: 1.0, 1.0, 1.0, 1.0, 1.0

    ABCD matrix::

        [[  1.00000000e+00   0.00000000e+00   0.00000000e+00   0.00000000e+00
            0.00000000e+00   6.75559806e-04  -6.75559806e-04]
         [  1.00000000e+00   1.00000000e+00  -2.79396240e-03   0.00000000e+00
            0.00000000e+00   8.37752565e-03  -8.37752565e-03]
         [  1.00000000e+00   1.00000000e+00   9.97206038e-01   0.00000000e+00
            0.00000000e+00   6.33294166e-02  -6.33294166e-02]
         [  0.00000000e+00   0.00000000e+00   1.00000000e+00   1.00000000e+00
           -7.90937431e-03   2.44344030e-01  -2.44344030e-01]
         [  0.00000000e+00   0.00000000e+00   1.00000000e+00   1.00000000e+00
            9.92090626e-01   8.02273699e-01  -8.02273699e-01]
         [  0.00000000e+00   0.00000000e+00   0.00000000e+00   0.00000000e+00
            1.00000000e+00   1.00000000e+00   0.00000000e+00]]

    NTF::

             (z -1) (z^2 -1.997z +1) (z^2 -1.992z +0.9999)
        --------------------------------------------------------
         (z -0.7778) (z^2 -1.796z +0.8549) (z^2 -1.613z +0.665)

    STF::

        1

    """

    nq = len(k) if type(k) in (tuple, list) else 1
    A, B, C, D = partitionABCD(ABCD, m=nq+1, r=nq)
    k = carray(k)
    diagk = np.atleast_2d(np.diag(k))

    B1 = B[:, 0]
    B2 = B[:, 1:]
    B1 = B1.reshape((B1.shape[0], 1)) if len(B1.shape) == 1 else B1
    B2 = B2.reshape((B2.shape[0], 1)) if len(B2.shape) == 1 else B2

    # In a single-quantizer system, D2 should be all zeros, as any
    # non-zero term in D2 would imply we got a delay-free loop.
    # The original MATLAB code implies so.
    # The trouble arises when we adapt and extend the code to calculate
    # the transfer functions for multiple-quantizer systems. There the
    # off-diagonal terms of D2 may very well be non-zero, therefore
    # in the following we consider D2.
    # this means that if you supply an ABCD matrix, with one quantizer
    # and an (erroneously) zero-delay loop, the MATLAB toolbox will
    # disregard D2 and pretend it's zero and the loop is correctly
    # delayed, spitting out the corresponding TF.
    # Instead here we print out a warning and process the ABCD matrix
    # with the user-supplied, non-zero D2 matrix.
    # The resulting TFs will obviously be different.

    D1 = D[:, 0]
    D2 = D[:, 1:]
    D1 = D1.reshape((D1.shape[0], 1)) if len(D1.shape) == 1 else D1
    D2 = D2.reshape((D2.shape[0], 1)) if len(D2.shape) == 1 else D2

    # WARN DELAY FREE LOOPS
    if np.diag(D2).any():
        warn("Delay free loop detected! D2 diag: %s", str(np.diag(D2)))

    # Find the noise transfer function by forming the closed-loop
    # system (sys_cl) in state-space form.
    Ct = np.linalg.inv(np.eye(nq) - D2*diagk)
    Acl = A + np.dot(B2, np.dot(Ct, np.dot(diagk, C)))
    Bcl = np.hstack((B1 + np.dot(B2, np.dot(Ct, np.dot(diagk, D1))),
                     np.dot(B2, Ct)))
    Ccl = np.dot(Ct, np.dot(diagk, C))
    Dcl = np.dot(Ct, np.hstack((np.dot(diagk, D1), np.eye(nq))))
    tol = min(1e-3, max(1e-6, eps**(1/ABCD.shape[0])))
    ntfs = np.empty((nq, Dcl.shape[0]), dtype=np.object_)
    stfs = np.empty((Dcl.shape[0],), dtype=np.object_)

    # sweep the outputs 'cause scipy is silly but we love it anyhow.
    for i in range(Dcl.shape[0]):
        # input #0 is the signal
        # inputs #1,... are quantization noise
        stf_z, stf_p, stf_k  = ss2zpk(Acl, Bcl, Ccl[i, :], Dcl[i, :], input=0)
        stf = lti(stf_z, stf_p, stf_k)
        for j in range(nq):
            ntf_z, ntf_p, ntf_k = ss2zpk(Acl, Bcl, Ccl[i, :], Dcl[i, :], input=j+1)
            ntf = lti(ntf_z, ntf_p, ntf_k)
            stf_min, ntf_min = minreal((stf, ntf), tol)
            ntfs[i, j] = ntf_min
        stfs[i] = stf_min

    # if we have one stf and one ntf, then just return those in a list
    if ntfs.shape == (1, 1):
        return [ntfs[0, 0], stfs[0]]
    return ntfs, stfs
Esempio n. 15
0
def calculateTF(ABCD, k=1.):
    """Calculate the NTF and STF of a delta-sigma modulator.

    The calculation is performed for a given loop filter
    ABCD matrix, assuming a quantizer gain of ``k``.

    **Parameters:**

    ABCD : array_like,
        The ABCD matrix that describes the system.

    k : float, optional
        The quantizer gain. If not specified, a default value of 1 is used.

    **Returns:**

    (NTF, STF) : a tuple of two LTI objects.

    **Example:**

    Realize a fifth-order modulator with the cascade-of-resonators structure,
    feedback form. Calculate the ABCD matrix of the loop filter and verify
    that the NTF and STF are correct.

    .. code-block:: python

        from deltasigma import *
        H = synthesizeNTF(5, 32, 1)
        a, g, b, c = realizeNTF(H)
        ABCD = stuffABCD(a,g,b,c)
        ntf, stf = calculateTF(ABCD)

    From which we get:

    ``H``::

             (z -1) (z^2 -1.997z +1) (z^2 -1.992z +0.9999)
        --------------------------------------------------------
         (z -0.7778) (z^2 -1.796z +0.8549) (z^2 -1.613z +0.665)

    coefficients::

        a: 0.0007, 0.0084, 0.055, 0.2443, 0.5579
        g: 0.0028, 0.0079
        b: 0.0007, 0.0084, 0.055, 0.2443, 0.5579, 1.0
        c: 1.0, 1.0, 1.0, 1.0, 1.0

    ABCD matrix::

        [[  1.00000000e+00   0.00000000e+00   0.00000000e+00   0.00000000e+00
            0.00000000e+00   6.75559806e-04  -6.75559806e-04]
         [  1.00000000e+00   1.00000000e+00  -2.79396240e-03   0.00000000e+00
            0.00000000e+00   8.37752565e-03  -8.37752565e-03]
         [  1.00000000e+00   1.00000000e+00   9.97206038e-01   0.00000000e+00
            0.00000000e+00   6.33294166e-02  -6.33294166e-02]
         [  0.00000000e+00   0.00000000e+00   1.00000000e+00   1.00000000e+00
           -7.90937431e-03   2.44344030e-01  -2.44344030e-01]
         [  0.00000000e+00   0.00000000e+00   1.00000000e+00   1.00000000e+00
            9.92090626e-01   8.02273699e-01  -8.02273699e-01]
         [  0.00000000e+00   0.00000000e+00   0.00000000e+00   0.00000000e+00
            1.00000000e+00   1.00000000e+00   0.00000000e+00]]

    NTF::

             (z -1) (z^2 -1.997z +1) (z^2 -1.992z +0.9999)
        --------------------------------------------------------
         (z -0.7778) (z^2 -1.796z +0.8549) (z^2 -1.613z +0.665)

    STF::

        1

    """
    A, B, C, D = partitionABCD(ABCD)
    if B.shape[1] > 1:
        B1 = B[:, 0]
        B2 = B[:, 1]
        B1 = B1.reshape((B1.shape[0], 1)) if len(B1.shape) == 1 else B1
        B2 = B2.reshape((B2.shape[0], 1)) if len(B2.shape) == 1 else B2
    else:
        B1 = B
        B2 = B

    # Find the noise transfer function by forming the closed-loop
    # system (sys_cl) in state-space form.
    Acl = A + k * np.dot(B2, C)
    Bcl = np.hstack((B1 + k * B2 * D[0, 0], B2))
    Ccl = k * C
    Dcl = np.array((k * D[0, 0], 1.))
    Dcl = Dcl.reshape((1, Dcl.shape[0])) if len(Dcl.shape) == 1 else Dcl
    tol = min(1e-3, max(1e-6, eps**(1 / ABCD.shape[0])))
    # input #0 is the signal
    # input #1 is the quantization noise
    stf_p, stf_z, stf_k = ss2zpk(Acl, Bcl, Ccl, Dcl, input=0)
    ntf_p, ntf_z, ntf_k = ss2zpk(Acl, Bcl, Ccl, Dcl, input=1)
    stf = lti(stf_p, stf_z, stf_k)
    ntf = lti(ntf_p, ntf_z, ntf_k)
    stf_min, ntf_min = minreal((stf, ntf), tol)
    return ntf_min, stf_min
Esempio n. 16
0
_sys2ss = {
    _LSYS: lambda sys: sys.ss,
    _LFILT: lambda sys: tf2ss(sys.num, sys.den),
    _NUM: lambda sys: tf2ss(sys, 1),
    _TF: lambda sys: tf2ss(*sys),
    _ZPK: lambda sys: zpk2ss(*sys),
    _SS: lambda sys: sys,
}

_sys2zpk = {
    _LSYS: lambda sys: sys.zpk,
    _LFILT: lambda sys: tf2zpk(sys.num, sys.den),
    _NUM: lambda sys: tf2zpk(sys, 1),
    _TF: lambda sys: tf2zpk(*sys),
    _ZPK: lambda sys: sys,
    _SS: lambda sys: ss2zpk(*sys),
}

_sys2tf = {
    _LSYS: lambda sys: sys.tf,
    _LFILT: lambda sys: _tf(sys.num, sys.den),
    _NUM: lambda sys: _tf(sys, 1),
    _TF: lambda sys: _tf(*sys),
    _ZPK: lambda sys: _tf(*zpk2tf(*sys)),
    _SS: lambda sys: _tf(*_ss2tf(*sys)),
}


def sys2ss(sys):
    """Converts an LTI system in any form to state-space."""
    return _sys2ss[_sys2form(sys)](sys)