# Source code for deltasigma._calculateQTF

# -*- coding: utf-8 -*-
# _calculateQTF.py
# Module providing the calculateQTF function
# This file is part of python-deltasigma.
#
# python-deltasigma is a 1:1 Python replacement of Richard Schreier's
# MATLAB delta sigma toolbox (aka "delsigma"), upon which it is heavily based.
# The delta sigma toolbox is (c) 2009, Richard Schreier.
#
# python-deltasigma is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# LICENSE file for the licensing terms.

"""Module providing the calculateQTF() function
"""

from __future__ import division

import numpy as np

from scipy.signal import ss2tf, tf2zpk

from ._cancelPZ import cancelPZ
from ._partitionABCD import partitionABCD

[docs]def calculateQTF(ABCDr):
"""Calculate noise and signal transfer functions for a quadrature modulator

**Parameters:**

ABCDr : ndarray
The ABCD matrix, in real form. You may call :func:mapQtoR to convert
an imaginary (quadrature) ABCD matrix to a real one.

**Returns:**

ntf, stf, intf, istf : tuple of zpk tuples
The quadrature noise and signal transfer functions.

:raises RuntimeError: if the supplied ABCD matrix results in denominator mismatches.
"""
A, B, C, D = partitionABCD(ABCDr, 4)

#Construct an ABCD description of the closed-loop system
# sys is a tuple in A, B, C, D form
Acl = A + np.dot(B[:, 2:4], C)
Bcl = B
Ccl = C
Dcl = np.hstack((D[:, 0:2], np.eye(2)))
#sys = (A + np.dot(B[:, 2:4], C), B, C,
#       np.hstack((D[:, 0:2], np.eye(2))))

#Calculate the 2x4 matrix of transfer functions
tfs = np.empty((2, 4), dtype=object)
# Each tf is a tuple in num, den form
# tf[i, j] corresponds to the TF from input j to output i
for i in range(2):
for j in range(4):
tfs[i, j] = ss2tf(Acl, Bcl, Ccl[i, :], Dcl[i, :], input=j)

#Reduce these to NTF, STF, INTF and ISTF
if any(tfs[0, 2][1] != tfs[1, 3][1]):
raise RuntimeError('TF Denominator mismatch. Location 1')

ntf_x = (0.5 * (tfs[0, 2][0] + tfs[1, 3][0]), tfs[0, 2][1])
intf_x = (0.5 * (tfs[0, 2][0] - tfs[1, 3][0]), tfs[0, 2][1])

if any(tfs[0, 3][1] != tfs[1, 2][1]):
raise RuntimeError('TF Denominator mismatch. Location 2')

ntf_y = (0.5 * (tfs[1, 2][0] - tfs[0, 3][0]), tfs[1, 2][1])
intf_y = (0.5 * (tfs[1, 2][0] + tfs[0, 3][0]), tfs[1, 2][1])

if any(ntf_x[1] != ntf_y[1]):
raise RuntimeError('TF Denominator mismatch. Location 3')
if any(tfs[0, 0][1] != tfs[1, 1][1]):
raise RuntimeError('TF Denominator mismatch. Location 4')

stf_x = (0.5 * (tfs[0, 0][0] + tfs[1, 1][0]), tfs[0, 0][1])
istf_x = (0.5 * (tfs[0, 0][0] - tfs[1, 1][0]), tfs[0, 0][1])

if any(tfs[0, 1][1] != tfs[1, 0][1]):
raise RuntimeError('TF Denominator mismatch. Location 5')

stf_y = (0.5 * (tfs[1, 0][0] - tfs[0, 1][0]), tfs[1, 0][1])
istf_y = (0.5 * (tfs[1, 0][0] + tfs[0, 1][0]), tfs[1, 0][1])

if any(stf_x[1] != stf_y[1]):
raise RuntimeError('TF Denominator mismatch. Location 6')

# suppress warnings about complex TFs
#warning('off')
ntf = cancelPZ(tf2zpk(ntf_x[0] + 1j*ntf_y[0], ntf_x[1]))
intf = cancelPZ(tf2zpk(intf_x[0] + 1j*intf_y[0], intf_x[1]))
stf = cancelPZ(tf2zpk(stf_x[0] + 1j*stf_y[0], ntf_x[1]))
istf = cancelPZ(tf2zpk(istf_x[0] + 1j*istf_y[0], intf_x[1]))
#warning('on')

return ntf, stf, intf, istf