Repository: scipy/scipy
File: scipy/signal/_filter_design.py (Python)

Code before:

    >>> from scipy.signal import zpk2tf
    >>> z   = [2,   6]
    >>> p   = [1,   8]
    >>> k   = 5
    >>> zpk2tf(z, p, k)
    (   array([  5., -40.,  60.]), array([ 1., -9.,  8.]))
    """
    xp = array_namespace(z, p)
    z, p = map(xp.asarray, (z, p))
    k = xp.asarray(k, dtype=xp.result_type(xp.real(z), xp.real(p), k))
    if xp.isdtype(k.dtype, "integral"):
        k = xp.astype(k, xp.float64)

    z = xpx.atleast_nd(z, ndim=1, xp=xp)
    k = xpx.atleast_nd(k, ndim=1, xp=xp)

    if z.ndim > 1:
        temp = _pu.poly(z[0, ...], xp=xp)
        # Determine output dtype considering both polynomial and gain types.
        # If temp is integral, use k.dtype directly (k is already float/complex).
        # Otherwise use result_type to handle float/complex promotion.
        if xp.isdtype(temp.dtype, "integral"):
            result_dtype = k.dtype
        else:
            result_dtype = xp.result_type(temp.dtype, k.dtype)
        b = xp.empty((z.shape[0], z.shape[1] + 1), dtype=result_dtype)
        if k.shape[0] == 1:
            k = [k[0]] * z.shape[0]
        for i in range(z.shape[0]):
            k_i = xp.asarray(k[i], dtype=b.dtype)
            b_i = k_i * _pu.poly(z[i, ...], xp=xp)
            b = xpx.at(b)[i, ...].set(b_i)
    else:
        # Use xp.multiply to work around torch type promotion
        # non-compliance for operations between 0d and higher
        # dimensional arrays.
        b = xp.multiply(k, _pu.poly(z, xp=xp))

    a = _pu.poly(p, xp=xp)
    a = xpx.atleast_nd(xp.asarray(a), ndim=1, xp=xp)

    return b, a


def tf2sos(b, a, pairing=None, *, analog=False):
    r"""
    Return second-order sections from transfer function representation

    Parameters
    ----------

Review comment (suggestion):
You may want to use https://github.com/scipy/scipy/blob/b9f97fedd9fde02616bd9c74f6cf6c360b86e0ab/scipy/_lib/_array_api.py#L471 instead with `force_floating=True` and then you don't have to worry about special casing non floating dtypes

Code after:
        { (s - 1)(s - 8) }

    >>> from scipy.signal import zpk2tf
    >>> z   = [2,   6]
    >>> p   = [1,   8]
    >>> k   = 5
    >>> zpk2tf(z, p, k)
    (   array([  5., -40.,  60.]), array([ 1., -9.,  8.]))
    """
    xp = array_namespace(z, p)
    z, p = map(xp.asarray, (z, p))
    k = xp.asarray(k, dtype=xp.result_type(xp.real(z), xp.real(p), k))
    if xp.isdtype(k.dtype, "integral"):
        k = xp.astype(k, xp.float64)

    z = xpx.atleast_nd(z, ndim=1, xp=xp)
    k = xpx.atleast_nd(k, ndim=1, xp=xp)

    if z.ndim > 1:
        temp = _pu.poly(z[0, ...], xp=xp)
        result_dtype = xp_result_type(temp, k, force_floating=True, xp=xp)
        b = xp.empty((z.shape[0], z.shape[1] + 1), dtype=result_dtype)
        if k.shape[0] == 1:
            k = [k[0]] * z.shape[0]
        for i in range(z.shape[0]):
            k_i = xp.asarray(k[i], dtype=result_dtype)
            poly_i = xp.asarray(_pu.poly(z[i, ...], xp=xp), dtype=result_dtype)
            b_i = k_i * poly_i
            b = xpx.at(b)[i, ...].set(b_i)
    else:
        # Use xp.multiply to work around torch type promotion
        # non-compliance for operations between 0d and higher
        # dimensional arrays.
        b = xp.multiply(k, _pu.poly(z, xp=xp))

    a = _pu.poly(p, xp=xp)
    a = xpx.atleast_nd(xp.asarray(a), ndim=1, xp=xp)

    return b, a


def tf2sos(b, a, pairing=None, *, analog=False):
    r"""
    Return second-order sections from transfer function representation

    Parameters
    ----------
    b : array_like
        Numerator polynomial coefficients.
    a : array_like
        Denominator polynomial coefficients.