Source code for deltasigma._clans

# -*- coding: utf-8 -*-
# _clans.py
# Closed-Loop Analysis of Noise-Shapers module
# Copyright 2013 Giuseppe Venturini
# 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 clans() optimization function.
"""

from __future__ import division, print_function

import numpy as np
from scipy.optimize import minimize
from scipy.signal import dimpulse

from ._dsclansNTF import dsclansNTF
from ._evalTF import evalTF
from ._synthesizeNTF import synthesizeNTF
from ._utils import cplxpair


# We ported the version that was originally designed for the MATLAB
# Optimization Toolbox version >= 6

[docs]def clans(order=4, OSR=64, Q=5, rmax=0.95, opt=0): """Optimal NTF design for a multi-bit modulator. Synthesize a noise transfer function (NTF) for a lowpass delta-sigma modulator using the CLANS methodology. CLANS stands for Closed-Loop Analysis of Noise-Shapers, and it was originally developed by J.G. Kenney and L.R. Carley [1]_. .. [1] J. G. Kenney and L. R. Carley, “Design of multibit noise-shaping data converters,” Analog Integrated Circuits Signal Processing Journal, vol. 3, pp. 259-272, 1993. **Parameters:** order : int The order of the NTF. OSR : int The oversampling ratio. Q : int The maximum number of quantization levels used by the fed-back quantization noise. (Mathematically, :math:`Q = \\|h\\|_1 - 1`, i.e. the sum of the absolute values of the impulse response samples minus one is the maximum instantaneous noise gain.) rmax : float The maximum radius for the NTF poles. opt : int A flag used to request optimized NTF zeros. * `opt=0` puts all NTF zeros at band center (DC for lowpass modulators). * `opt=1` optimizes the NTF zeros. * For even-order modulators, `opt=2` puts two zeros at band-center, but optimizes the rest. **Returns** ntf : tuple The modulator NTF, given in ZPK (zero-pole-gain) form. **Example:** Fifth-order lowpass modulator; (time-domain) noise gain of 5, zeros optimized for OSR = 32.:: H = clans(5, 32, 5, .95, 1) pretty_lti(H) Returns:: (z -1) (z^2 -1.997z +1) (z^2 -1.992z +0.9999) --------------------------------------------------------- (z -0.4184) (z^2 -1.305z +0.5713) (z^2 -0.978z +0.2686) ``H`` can be plotted through :func:`DocumentNTF`: .. plot:: from deltasigma import DocumentNTF, clans # Fifth-order lowpass modulator; (time-domain) noise gain of 5, # zeros optimized for OSR = 32. H = clans(5, 32, 5, .95, 1) DocumentNTF(H) """ # Create the initial guess Hz, poles, _ = synthesizeNTF(order, OSR, opt, 1 + Q, 0) x = np.zeros((order, )) odd = order % 2 poles = cplxpair(poles) poles = poles[::-1] if odd == 1: z = poles[0]/rmax if (np.abs(z) > 1).any(): #project poles outside rmax onto the circle z = z/np.abs(z) s = (z - 1)/(z + 1) x[0] = np.real_if_close(np.sqrt(-s)) for i in range(odd, order, 2): z = poles[i:i + 2]/rmax if np.any(np.abs(z) > 1): #project poles outside rmax onto the circle z = z/np.abs(z) s = (z - 1)/(z + 1) coeffs = np.poly(s) wn = np.sqrt(coeffs[2]) zeta = coeffs[1]/(2 * wn) x[i] = np.real_if_close(np.sqrt(zeta)) x[i + 1] = np.real_if_close(np.sqrt(wn)) #options=optimset('TolX',1e-06,'TolFun',1e-06,'TolCon',1e-06,'MaxIter',1000) #fobj = lambda x: dsclansObj6a(x, order, OSR, Q, rmax, Hz) #fconstr = lambda x: dsclansObj6b(x, order, OSR, Q, rmax, Hz) res = minimize(dsclansObja, x, args=(order, OSR, Q, rmax, Hz), method='slsqp', constraints={'type':'ineq', 'fun':dsclansObjb, 'args':(order, OSR, Q, rmax, Hz)}) NTF = dsclansNTF(res['x'], order, rmax, Hz) return NTF
def dsclansObja(x, order, OSR, Q, rmax, Hz): """Objective function for clans; Optimization Toolbox version >= 6 f is the magnitude of H at the band-edge """ H = dsclansNTF(x, order, rmax, Hz) f = np.abs(evalTF(H, np.exp(1j*np.pi/OSR))) return f def dsclansObjb(x, order, OSR, Q, rmax, Hz): """Constraint function for clans; g =||h||_1 - Q """ H = dsclansNTF(x, order, rmax, Hz) H = (H[0], H[1], H[2], 1.) # dimpulse(H, n=100)[y is 0][output 0] g = np.sum(np.abs(dimpulse(H, t=np.arange(100))[1][0])) - 1 - Q return -g