Source code for diamondback.transforms.ZTransform

""" **Description**
        A z transform converts a continuous s-domain differential equation to a
        discrete z-domain difference equation as a function of a recursive
        coefficient array and a forward coefficient array of a specified order.
        A normalized frequency and bilinear condition are specified.

        Singleton.

        .. math::

            y_{n} = \\sum_{i = 1}^{N} a_{i} y_{n-i} + \\sum_{i = 0}^{N} b_{i} x_{n-i} = \\sum_{i = 1}^{N} (\\ a_{i} b_{0} + b_{i}\\ ) s_{i,n} + b_{0} x_{n}\\qquad a_{0} = 0

        A frequency response is expressed as a function of a recursive coefficient
        array and a forward coefficient array, in s-domain and z-domain.

        .. math::

            H_{s,n} = \\frac{\\sum_{i = 0}^{N} v_{i} s^{N-i}}{{\\sum_{i = 0}^{N} u_{i} s^{N-i}}}

        .. math::

            H_{z,n} = \\frac{\\sum_{i = 0}^{N} b_{i} z^{-i}}{{1 - \\sum_{i = 1}^{N} a_{i} z^{-i}}}

    **Example**

        .. code-block:: python

            from diamondback import ZTransform
            import math
            import numpy

            frequency, order, ripple = 0.1, 2, 0.125
            u = numpy.array( [ numpy.exp( 1j * math.pi * x / ( 2.0 * order ) ) for x in range( 1, 2 * order, 2 ) ] )
            v = math.asinh( 1.0 / ( ( 10.0 ** ( 0.1 * ripple ) - 1.0 ) ** 0.5 ) ) / order
            a = ( numpy.poly( ( -math.sinh( v ) * u.imag + 1j * math.cosh( v ) * u.real ) * 2.0 * math.pi ) ).real
            a /= a[ -1 ]

            # Transform z-domain coefficients with s-domain coefficients, frequency, and bilinear.

            a, b = ZTransform.transform( a = a, b = [ 1.0 ], frequency = frequency, bilinear = True )

            # Define zeros and normalize gain.

            b = numpy.poly( -numpy.ones( order ) )
            b = b * ( 1.0 - sum( a ) ) / sum( b )


    **License**
        `BSD-3C.  <https://github.com/larryturner/diamondback/blob/master/license>`_
        © 2018 - 2024 Larry Turner, Schneider Electric Industries SAS. All rights reserved.

    **Author**
        Larry Turner, Schneider Electric, AI Hub, 2018-01-26.
"""

from typing import Tuple, Union
import math
import numpy
import scipy.signal

[docs] class ZTransform( object ) : """ Z transform realizes continuous s-domain to discrete z-domain transformation, through application of bilinear or impulse invariant methods. """
[docs] @staticmethod def transform( u : Union[ list, numpy.ndarray ], v : Union[ list, numpy.ndarray ], frequency : float, bilinear : bool = True ) -> Tuple[ numpy.ndarray, numpy.ndarray ] : """ Transforms continuous s-domain coefficient arrays and produces discrete z-domain coefficient arrays. Arguments : u : Union[ list, numpy.ndarray ] - recursive coefficient, s-domain. v : Union[ list, numpy.ndarray ] - forward coefficient, s-domain. frequency : float - frequency in ( 0.0, inf ). bilinear : bool. Returns : a : numpy.ndarray - recursive coefficient, z-domain. b : numpy.ndarray - forward coefficient, z-domain. """ if ( not isinstance( u, numpy.ndarray ) ) : u = numpy.array( list( u ) ) if ( ( not len( u ) ) or ( not u.any( ) ) ) : raise ValueError( f'U = {u}' ) if ( not isinstance( v, numpy.ndarray ) ) : v = numpy.array( list( v ) ) if ( ( not len( v ) ) or ( not v.any( ) ) ) : raise ValueError( f'V = {v}' ) if ( frequency <= 0.0 ) : raise ValueError( f'Frequency = {frequency} Expected Frequency in ( 0.0, inf )' ) while ( not u[ 0 ] ) : u = numpy.delete( u, 0 ) while ( not v[ 0 ] ) : v = numpy.delete( v, 0 ) v /= u[ -1 ] u /= u[ -1 ] t = 1.0 / ( math.pi * frequency ) if ( bilinear ) : p = numpy.roots( u ) p = ( 1.0 + p / ( 2.0 * t ) ) / ( 1.0 - p / ( 2.0 * t ) ) z = numpy.roots( v ) z = ( 1.0 + z / ( 2.0 * t ) ) / ( 1.0 - z / ( 2.0 * t ) ) if ( len( z ) < len( p ) ) : z = numpy.concatenate( ( z, numpy.zeros( len( p ) - len( z ) ) ) ) a, b = numpy.poly( p ).real, numpy.poly( z ).real else : r, p, k = scipy.signal.residue( v, u ) a, b = numpy.ones( 1 ) + 0j, 0j # type: ignore for ii in range( 0, len( r ) ) : a = numpy.convolve( a, numpy.array( [ 1.0, -numpy.exp( p[ ii ] / t ) ] ) ) q = numpy.ones( 1 ) for jj in range( 0, len( r ) ) : if ( jj != ii ) : q = numpy.convolve( q, numpy.array( [ 1.0, -numpy.exp( p[ jj ] / t ) ] ) ) b += r[ ii ] * q if ( len( b ) < len( a ) ) : b = numpy.concatenate( ( b, numpy.zeros( len( a ) - len( b ) ) ) ) a, b = a.real, b.real a /= -a[ 0 ] a[ 0 ] = 0.0 b *= ( ( 1.0 - sum( a ) ) / sum( b ) ) return a, b