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

Code before:
    >>> AA, BB, CC, DD = abcd_normalize(A=[[1, 2], [3, 4]], B=[[-1], [5]],
    ...                                 C=[[4, 5]], D=2.5)
    >>> AA.shape, BB.shape, CC.shape, DD.shape
    ((2, 2), (2, 1), (1, 2), (1, 1))

    In the following, the missing parameter C is assumed to be an array of zeros
    with shape (1, 2):

    >>> from scipy.signal import abcd_normalize
    >>> AA, BB, CC, DD = abcd_normalize(A=[[1, 2], [3, 4]], B=[[-1], [5]], D=2.5)
    >>> AA.shape, BB.shape, CC.shape, DD.shape
    ((2, 2), (2, 1), (1, 2), (1, 1))
    >>> CC
    array([[0., 0.]])

    The following snippet shows the effect of the `dtype` parameter:

    >>> import numpy as np
    >>> from scipy.signal import abcd_normalize
    >>> A, D = [[1, 2], [3, 4]], 2.5
    ...
    >>> AA, BB, CC, DD = abcd_normalize(A=A, D=D)  # default type casting
    >>> print(f" AA: {AA.dtype}, BB: {BB.dtype}\n CC: {CC.dtype}, DD: {DD.dtype}")
     AA: float64, BB: float64
     CC: float64, DD: float64
    >>> AA, BB, CC, DD = abcd_normalize(A=A, D=D, dtype=np.int32)  # Explicit dtype
    >>> print(f" AA: {AA.dtype}, BB: {BB.dtype}\n CC: {CC.dtype}, DD: {DD.dtype}")
     AA: int32, BB: int32
     CC: int32, DD: int32
    """
    if A is None and B is None and C is None:
        raise ValueError("Dimension n is undefined for parameters A = B = C = None!")
    if B is None and D is None:
        raise ValueError("Dimension p is undefined for parameters B = D = None!")
    if C is None and D is None:
        raise ValueError("Dimension q is undefined for parameters C = D = None!")

    xp = xp or array_namespace(A, B, C, D)

    # convert inputs into 2d arrays (zero-size 2d array if None):
    A, B, C, D = (xpx.atleast_nd(xp.asarray(M_, device=device), ndim=2, xp=xp)
                  if M_ is not None else xp.zeros((0, 0), device=device)
                  for M_ in (A, B, C, D))

    if dtype is None:
        to_comp = any(xp.isdtype(M_.dtype, 'complex floating') for M_ in (A, B, C, D))
        dtype = xp.complex128 if to_comp else xp.float64
    else:
        try:
            is_numeric = xp.isdtype(dtype, 'numeric')
        except Exception as dtype_error:

Review comment (suggestion):
Nit, minor, optional: are integer state space matrices useful anywhere? If not, consider using `float32` or complex

Code after:
    ((2, 2), (2, 1), (1, 2), (1, 1))
    >>> CC
    array([[0., 0.]])

    The following snippet shows the effect of the `dtype` parameter:

    >>> import numpy as np
    >>> from scipy.signal import abcd_normalize
    >>> A, D = [[1, 2], [3, 4]], 2.5
    ...
    >>> AA, BB, CC, DD = abcd_normalize(A=A, D=D)  # default type casting
    >>> print(f" AA: {AA.dtype}, BB: {BB.dtype}\n CC: {CC.dtype}, DD: {DD.dtype}")
     AA: float64, BB: float64
     CC: float64, DD: float64
    >>> AA, BB, CC, DD = abcd_normalize(A=A, D=D, dtype=np.float32)  # Explicit dtype
    >>> print(f" AA: {AA.dtype}, BB: {BB.dtype}\n CC: {CC.dtype}, DD: {DD.dtype}")
     AA: float32, BB: float32
     CC: float32, DD: float32
    """
    if A is None and B is None and C is None:
        raise ValueError("Dimension n is undefined for parameters A = B = C = None!")
    if B is None and D is None:
        raise ValueError("Dimension p is undefined for parameters B = D = None!")
    if C is None and D is None:
        raise ValueError("Dimension q is undefined for parameters C = D = None!")

    xp = array_namespace(A, B, C, D)

    # convert inputs into 2d arrays (zero-size 2d array if None):
    A, B, C, D = (xpx.atleast_nd(xp.asarray(M_), ndim=2, xp=xp)
                  if M_ is not None else xp.zeros((0, 0)) for M_ in (A, B, C, D))

    if dtype is None:
        to_comp = any(xp.isdtype(M_.dtype, 'complex floating') for M_ in (A, B, C, D))
        dtype = xp.complex128 if to_comp else xp.float64
    else:
        try:
            is_numeric = xp.isdtype(dtype, 'numeric')
        except Exception as dtype_error:
            err_msg = f"Parameter {dtype=} must be None or a numeric dtype!"
            raise ValueError(err_msg) from dtype_error
        if not is_numeric:
            raise ValueError(f"Parameter {dtype=} is not a numeric dtype!")

    n = A.shape[0] or B.shape[0] or C.shape[1] # try finding non-zero dimensions
    p = B.shape[1] or D.shape[1]
    q = C.shape[0] or D.shape[0]

    # Create zero matrices as needed:
    A = xp.zeros((n, n)) if xp_size(A) == 0 else A
    B = xp.zeros((n, p)) if xp_size(B) == 0 else B