Beispiel #1
0
def impinvar(b_in, a_in, fs=1, tol=0.0001):
    """
    # Copyright (c) 2007 R.G.H. Eschauzier <*****@*****.**>
    # Copyright (c) 2011 Carnë Draug <*****@*****.**>
    # Copyright (c) 2011 Juan Pablo Carbajal <*****@*****.**>
    # Copyright (c) 2016 Sascha Eichstädt <*****@*****.**>
    #
    # This program is free software; you can redistribute it and/or modify it under
    # the terms of the GNU General Public License as published by the Free Software
    # Foundation; either version 3 of the License, or (at your option) any later
    # version.
    #
    # This program 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 GNU General Public License for more
    # details.
    #
    # You should have received a copy of the GNU General Public License along with
    # this program; if not, see <https://www.gnu.org/licenses/>.
    """

    ts = 1 / fs  # we should be using sampling frequencies to be compatible with Matlab

    [r_in, p_in, k_in] = dsp.residue(b_in, a_in)  # partial fraction expansion

    n = len(r_in)  # Number of poles/residues

    if len(k_in
           ) > 0:  # Greater than zero means we cannot do impulse invariance
        if abs(k_in) > 0:
            raise ValueError("Order numerator >= order denominator")

    r_out = np.zeros(n, dtype=complex)  # Residues of H(z)
    p_out = np.zeros(n, dtype=complex)  # Poles of H(z)
    k_out = np.zeros(1)  # Constant term of H(z)

    i = 0
    while i < n:
        m = 0
        first_pole = p_in[i]  # Pole in the s-domain
        while (i < (n - 1) and np.abs(first_pole - p_in[i + 1]) < tol
               ):  # Multiple poles at p(i)
            i += 1  # Next residue
            m += 1  # Next multiplicity
        [r, p, k] = z_res(r_in[i - m:i + 1], first_pole,
                          ts)  # Find z-domain residues
        k_out += k  # Add direct term to output
        p_out[i - m:i + 1] = p  # Copy z-domain pole(s) to output
        r_out[i - m:i + 1] = r  # Copy z-domain residue(s) to output

        i += 1  # Next s-domain residue/pole

    [b_out, a_out] = dsp.invres(r_out, p_out, k_out, tol=tol)
    a_out = to_real(a_out)  # Get rid of spurious imaginary part
    b_out = to_real(b_out)

    # Shift results right to account for calculating in z instead of z^-1
    b_out = b_out[:-1]

    return b_out, a_out
Beispiel #2
0
    def __init__(self,Model,ref_beta=1,
                 nodes=[(1,0)],Nj=None,\
                 output=passthrough):

        if not Nj: Nj = Model.Nj
        self.Nj = Nj
        self.nodes = nodes
        self.Model = Model
        self.set_ref_signal(ref_beta)

        ds, ws = list(zip(*nodes))
        ws_grid = numpy.array(ws).reshape(
            (len(ws),
             1))  #last dimension is to broadcast over all `Nterms` equally
        ds = numpy.array(ds)

        # ---Acquire the necessary pole and residue values--- #
        Ps = Model.evaluate_poles(ds, Nj)
        Rs = Model.evaluate_residues(ds, Nj)

        #`rs` and `ps` can safely remain as arrays for `invres`
        ps = Ps.flatten()
        rs = (Rs * ws_grid).flatten()
        k0 = numpy.sum(rs / ps).tolist()

        # ---Rescale units to center dynamic range of `rs` and `ps` around 1e0--- #
        rscaling=numpy.exp(-(numpy.log(numpy.abs(rs).max())+\
                            numpy.log(numpy.abs(rs).min()))/2.)
        pscaling=numpy.exp(-(numpy.log(numpy.abs(ps).max())+\
                             numpy.log(numpy.abs(ps).min()))/2.)

        self.rscaling = rscaling
        self.pscaling = pscaling

        ps *= pscaling
        rs *= rscaling
        k0 *= rscaling / pscaling

        self.ps = ps
        self.rs = rs
        self.k0 = k0

        # ---Inverse partial fraction expansion of Eigenfield Laurent series--- #
        #highest order first in `Ps` and `Qs`
        #VERY SLOW - about 100ms on practical inversions (~60 terms)
        #therefore, do it only once and store the polynomial coefficients `As` and `Bs`
        As, Bs = invres(
            rs, ps, k=[k0], tol=1e-16, rtype='avg'
        )  #tol=1e-16 is the smallest allowable to `unique_roots`..

        #Double precision offers noticeable protection against overflow
        dtype = numpy.complex128
        self.As = numpy.array(As, dtype=dtype)
        self.Bs = numpy.array(Bs, dtype=dtype)
        self.output = output

        # ---Initialize a counter so inverse function can have self-awareness of
        # beta continuity etc. for any series of inversions--- #
        self.i = 0
        self.betas = []
Beispiel #3
0
def make_ctle(rx_bw, peak_freq, peak_mag, w, dc_offset=0):
    """
    Generate the frequency response of a continuous time linear
    equalizer (CTLE), given the:

    - signal path bandwidth,
    - peaking specification, and
    - list of frequencies of interest.

    We use the 'invres()' function from scipy.signal, as it suggests
    itself as a natural approach, given our chosen use model of having
    the user provide the peaking frequency and degree of peaking.

    That is, we define our desired frequency response using one zero
    and two poles, where:

    - The pole locations are equal to:
       - the signal path natural bandwidth, and
       - the user specified peaking frequency.

    - The zero location is chosen, so as to provide the desired degree
      of peaking.

    Inputs:

      - rx_bw        The natural (or, unequalized) signal path bandwidth (Hz).

      - peak_freq    The location of the desired peak in the frequency
                     response (Hz).

      - peak_mag     The desired relative magnitude of the peak (dB). (mag(H(0)) = 1)

      - w            The list of frequencies of interest (rads./s).

      - dc_offset    The d.c. offset of the CTLE gain curve (dB).

    Outputs:

      - w, H         The resultant complex frequency response, at the
                     given frequencies.

    """

    p2   = -2. * pi * rx_bw
    p1   = -2. * pi * peak_freq
    z    = p1 / pow(10., peak_mag / 20.)
    if(p2 != p1):
        r1   = (z - p1) / (p2 - p1)
        r2   = 1 - r1
    else:
        r1   = -1.
        r2   = z - p1
    b, a = invres([r1, r2], [p1, p2], [])

    w, H = freqs(b, a, w)
    H   *= pow(10., dc_offset / 20.) / abs(H[0])  # Enforce d.c. offset.

    return (w, H)
Beispiel #4
0
def impinvar(b_in, a_in, fs = 1, tol = 0.0001):
	"""

	## Copyright (c) 2007 R.G.H. Eschauzier <*****@*****.**>
	## Copyright (c) 2011 Carnë Draug <*****@*****.**>
	## Copyright (c) 2011 Juan Pablo Carbajal <*****@*****.**>
	## Copyright (c) 2016 Sascha Eichstaedt <*****@*****.**>
	##
	## This program is free software; you can redistribute it and/or modify it under
	## the terms of the GNU General Public License as published by the Free Software
	## Foundation; either version 3 of the License, or (at your option) any later
	## version.
	##
	## This program 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 GNU General Public License for more
	## details.
	##
	## You should have received a copy of the GNU General Public License along with
	## this program; if not, see <http://www.gnu.org/licenses/>.
	"""

	ts = 1 / fs  # we should be using sampling frequencies to be compatible with Matlab

	[r_in, p_in, k_in] = dsp.residue(b_in, a_in)  # partial fraction expansion

	n = len(r_in)  # Number of poles/residues

	if (len(k_in) > 0):  # Greater than zero means we cannot do impulse invariance
		if abs(k_in)>0:
			raise ValueError("Order numerator >= order denominator")

	r_out = np.zeros(n, dtype = complex)  # Residues of H(z)
	p_out = np.zeros(n, dtype = complex)  # Poles of H(z)
	k_out = np.zeros(n)  # Constant term of H(z)

	i = 0
	while (i < n):
		m = 0
		first_pole = p_in[i]  # Pole in the s-domain
		while (i < (n-1) and np.abs(first_pole - p_in[i + 1]) < tol):  # Multiple poles at p(i)
			i += 1 # Next residue
			m += 1  # Next multiplicity
		[r, p, k] = z_res(r_in[i - m:i+1], first_pole, ts) # Find z-domain residues
		k_out += k  # Add direct term to output
		p_out[i - m:i+1]   = p  # Copy z-domain pole(s) to output
		r_out[i - m:i+1]   = r  # Copy z-domain residue(s) to output

		i += 1  # Next s-domain residue/pole

	[b_out, a_out] = dsp.invres(r_out, p_out, k_out, tol=tol)
	a_out = to_real(a_out)  # Get rid of spurious imaginary part
	b_out = to_real(b_out)

	## Shift results right to account for calculating in z instead of z^-1
	b_out = b_out[:-1]

	return b_out, a_out
Beispiel #5
0
def make_ctle(rx_bw, peak_freq, peak_mag, w, dc_offset=0):
    """
    Generate the frequency response of a continuous time linear
    equalizer (CTLE), given the:

    - signal path bandwidth,
    - peaking specification, and
    - list of frequencies of interest.

    We use the 'invres()' function from scipy.signal, as it suggests
    itself as a natural approach, given our chosen use model of having
    the user provide the peaking frequency and degree of peaking.

    That is, we define our desired frequency response using one zero
    and two poles, where:

    - The pole locations are equal to:
       - the signal path natural bandwidth, and
       - the user specified peaking frequency.

    - The zero location is chosen, so as to provide the desired degree
      of peaking.

    Inputs:

      - rx_bw        The natural (or, unequalized) signal path bandwidth (Hz).

      - peak_freq    The location of the desired peak in the frequency
                     response (Hz).

      - peak_mag     The desired relative magnitude of the peak (dB). (mag(H(0)) = 1)

      - w            The list of frequencies of interest (rads./s).

      - dc_offset    The d.c. offset of the CTLE gain curve (dB).

    Outputs:

      - w, H         The resultant complex frequency response, at the
                     given frequencies.

    """

    p2 = -2. * pi * rx_bw
    p1 = -2. * pi * peak_freq
    z = p1 / pow(10., peak_mag / 20.)
    if (p2 != p1):
        r1 = (z - p1) / (p2 - p1)
        r2 = 1 - r1
    else:
        r1 = -1.
        r2 = z - p1
    b, a = invres([r1, r2], [p1, p2], [])

    w, H = freqs(b, a, w)
    H *= pow(10., dc_offset / 20.) / abs(H[0])  # Enforce d.c. offset.

    return (w, H)
Beispiel #6
0
def vectFitSingle(ss, yy, p0, asymptote='none'):
    _, _, sigma_res = getResidues(ss, yy, p0, asymptote)
    ones_col = np.transpose(np.matrix(np.ones(len(sigma_res))))
    sigma_res_row = np.matrix(sigma_res)
    Hmat = np.matrix(np.diag(p0)) - ones_col * sigma_res_row
    yy_poles, _ = np.linalg.eig(Hmat)
    yy_res, asympArr, _ = getResidues(ss, yy, yy_poles, asymptote)

    yy_num, yy_den = sig.invres(yy_res, yy_poles, np.real(asympArr))
    return sig.lti(yy_num, yy_den)
Beispiel #7
0
 def test_invres_repeated_roots(self):
     r = [3 / 20, -7 / 36, -1 / 6, 2 / 45]
     p = [0, -2, -2, -5]
     k = []
     a_expected = [1, 3]
     b_expected = [1, 9, 24, 20, 0]
     rtypes = ('avg', 'mean', 'min', 'minimum', 'max', 'maximum')
     for rtype in rtypes:
         a_observed, b_observed = invres(r, p, k, rtype=rtype)
         assert_allclose(a_observed, a_expected)
         assert_allclose(b_observed, b_expected)
Beispiel #8
0
 def test_invres_repeated_roots(self):
     r = [3/20, -7/36, -1/6, 2/45]
     p = [0, -2, -2, -5]
     k = []
     a_expected = [1, 3]
     b_expected = [1, 9, 24, 20, 0]
     rtypes = ('avg', 'mean', 'min', 'minimum', 'max', 'maximum')
     for rtype in rtypes:
         a_observed, b_observed = invres(r, p, k, rtype=rtype)
         assert_allclose(a_observed, a_expected)
         assert_allclose(b_observed, b_expected)
Beispiel #9
0
    def test_invres_distinct_roots(self):
        # This test was inspired by github issue 2496.
        r = [3 / 10, -1 / 6, -2 / 15]
        p = [0, -2, -5]
        k = []
        a_expected = [1, 3]
        b_expected = [1, 7, 10, 0]
        a_observed, b_observed = invres(r, p, k)
        assert_allclose(a_observed, a_expected)
        assert_allclose(b_observed, b_expected)
        rtypes = ('avg', 'mean', 'min', 'minimum', 'max', 'maximum')

        # With the default tolerance, the rtype does not matter
        # for this example.
        for rtype in rtypes:
            a_observed, b_observed = invres(r, p, k, rtype=rtype)
            assert_allclose(a_observed, a_expected)
            assert_allclose(b_observed, b_expected)

        # With unrealistically large tolerances, repeated roots may be inferred
        # and the rtype comes into play.
        ridiculous_tolerance = 1e10
        for rtype in rtypes:
            a, b = invres(r, p, k, tol=ridiculous_tolerance, rtype=rtype)
Beispiel #10
0
    def test_invres_distinct_roots(self):
        # This test was inspired by github issue 2496.
        r = [3/10, -1/6, -2/15]
        p = [0, -2, -5]
        k = []
        a_expected = [1, 3]
        b_expected = [1, 7, 10, 0]
        a_observed, b_observed = invres(r, p, k)
        assert_allclose(a_observed, a_expected)
        assert_allclose(b_observed, b_expected)
        rtypes = ('avg', 'mean', 'min', 'minimum', 'max', 'maximum')

        # With the default tolerance, the rtype does not matter
        # for this example.
        for rtype in rtypes:
            a_observed, b_observed = invres(r, p, k, rtype=rtype)
            assert_allclose(a_observed, a_expected)
            assert_allclose(b_observed, b_expected)

        # With unrealistically large tolerances, repeated roots may be inferred
        # and the rtype comes into play.
        ridiculous_tolerance = 1e10
        for rtype in rtypes:
            a, b = invres(r, p, k, tol=ridiculous_tolerance, rtype=rtype)
Beispiel #11
0
def vectFit(ss, yy, p0=[], numIter=10, asymptote='none', makePlot=True):
    """Performs vector fitting of complex data and returns a scipy.signal.lti model.
    
    Parameters
    ----------
    ss : array of s-parameters (usually 2*np.pi*1j*ff, where ff is the non-angular Fourier frequency)
    yy : array of complex values to be fitted
    p0 : array of guesses for poles
    numIter : number of times to iterate the vector fitting algorithm
    asymptote : 'none', 'constant', or 'linear'. If 'none', the underlying function is assumed to
        be given entirely in terms of residues and poles. If 'constant', a complex constant d will
        be added. If 'linear', a complex linear term d + ss*e will be added.
    makePlot : generate a Bode plot (with residuals) of the data and the fit.

    Returns
    -------
    yyLti : scipy.signal.lti representation of the vector-fitted data
    figHandle : if makePlot is true, vectFit also returns a handle to the Bode plot figure.

    """
    thisGuess = p0
    for ii in range(numIter):
        _, _, sigma_res = getResidues(ss, yy, thisGuess, asymptote)
        ones_col = np.transpose(np.matrix(np.ones(len(sigma_res))))
        sigma_res_row = np.matrix(sigma_res)
        Hmat = np.matrix(np.diag(thisGuess)) - ones_col * sigma_res_row
        yy_poles, _ = np.linalg.eig(Hmat)
        yy_res, asympArr, _ = getResidues(ss, yy, yy_poles, asymptote)
        print(asympArr)
        yy_num, yy_den = sig.invres(yy_res, yy_poles, asympArr)
        yyLti = sig.lti(yy_num, yy_den)
        # The following loop flips poles as necessary to keep them
        # in the left-hand plane
        for poleIndex, pole in enumerate(yyLti.poles):
            if np.real(pole) > 0:
                yyLti.poles[poleIndex] = -np.conjugate(pole)
        thisGuess = yyLti.poles
    if makePlot == True:
        figHandle = bodePlot(ss, yy, yyLti)
        return yyLti, figHandle
    return yyLti
Beispiel #12
0
 def leastsquare_complexexponential(self, h, t, N):
     ''' Model parameter estimation from impulse response
     using least-squares complex exponential method 
     h: impulse response measurement
     t: time range vector (in sec); must be uniformly-spaced
     N: degree of freedom
 '''
     no_sample = h.size
     if diff(t).max() > diff(t).max():  # check for uniform time steps
         spline_fn = interpolate.InterpolatedUnivariateSpline(t, h)
         t = linspace(t[0], t[-1], no_sample)
         h = spline_fn(t)
     h = h.reshape(no_sample, 1)
     t = t.reshape(no_sample, 1)
     M = no_sample - N  # no of equations
     dT = t[1] - t[0]
     # least-squares estimation of eigenvalues (modes)
     R = matrix(toeplitz(h[N - 1::-1], h[N - 1:no_sample - 1]))
     A = -1 * matrix(pinv(R.transpose())) * matrix(
         h[N + arange(0, M, dtype=int)])
     A0 = vstack((ones(A.shape[1]), A)).getA1()
     P = matrix(log(roots(A0)) / dT).transpose()
     # least-squares estimation of eigenvectors (modal coef)
     Q = exp(P * t.transpose())
     Z = pinv(matrix(Q.transpose())) * matrix(h)
     # return values
     num, den = invres(Z.getA1(),
                       P.getA1(),
                       zeros(size(P)),
                       tol=1e-4,
                       rtype='avg')
     print num, den
     num = num.real
     den = den.real
     h_estimated = Q.transpose() * Z
     h_estimated = h_estimated.real.getA1()
     return dict(h_impulse=h, h_estimated=h_estimated, num=num, den=den)
Beispiel #13
0
def make_ctle(rx_bw, peak_freq, peak_mag, w, mode="Passive", dc_offset=0):
    """
    Generate the frequency response of a continuous time linear
    equalizer (CTLE), given the:

    - signal path bandwidth,
    - peaking specification
    - list of frequencies of interest, and
    - operational mode/offset.

    We use the 'invres()' function from scipy.signal, as it suggests
    itself as a natural approach, given our chosen use model of having
    the user provide the peaking frequency and degree of peaking.

    That is, we define our desired frequency response using one zero
    and two poles, where:

    - The pole locations are equal to:
       - the signal path natural bandwidth, and
       - the user specified peaking frequency.

    - The zero location is chosen, so as to provide the desired degree
      of peaking.

    Inputs:

      - rx_bw        The natural (or, unequalized) signal path bandwidth (Hz).

      - peak_freq    The location of the desired peak in the frequency
                     response (Hz).

      - peak_mag     The desired relative magnitude of the peak (dB). (mag(H(0)) = 1)

      - w            The list of frequencies of interest (rads./s).

      - mode         The operational mode; must be one of:
                       - 'Off'    : CTLE is disengaged.
                       - 'Passive': Maximum frequency response has magnitude one.
                       - 'AGC'    : Automatic gain control. (Handled by calling routine.)
                       - 'Manual' : D.C. offset is set manually.

      - dc_offset    The d.c. offset of the CTLE gain curve (dB).
                     (Only valid, when 'mode' = 'Manual'.)

    Outputs:

      - w, H         The resultant complex frequency response, at the
                     given frequencies.

    """

    if mode == "Off":
        return (w, ones(len(w)))

    p2 = -2.0 * pi * rx_bw
    p1 = -2.0 * pi * peak_freq
    z = p1 / pow(10.0, peak_mag / 20.0)
    if p2 != p1:
        r1 = (z - p1) / (p2 - p1)
        r2 = 1 - r1
    else:
        r1 = -1.0
        r2 = z - p1
    b, a = invres([r1, r2], [p1, p2], [])
    w, H = freqs(b, a, w)

    if mode == "Passive":
        H /= max(abs(H))
    elif mode in ("Manual", "AGC"):
        H *= pow(10.0, dc_offset / 20.0) / abs(H[0])  # Enforce d.c. offset.
    else:
        raise RuntimeError(
            "pybert_util.make_ctle(): Unrecognized value for 'mode' parameter: {}."
            .format(mode))

    return (w, H)
Beispiel #14
0
def make_ctle(rx_bw, peak_freq, peak_mag, w, mode='Passive', dc_offset=0):
    """
    Generate the frequency response of a continuous time linear
    equalizer (CTLE), given the:

    - signal path bandwidth,
    - peaking specification
    - list of frequencies of interest, and
    - operational mode/offset.

    We use the 'invres()' function from scipy.signal, as it suggests
    itself as a natural approach, given our chosen use model of having
    the user provide the peaking frequency and degree of peaking.

    That is, we define our desired frequency response using one zero
    and two poles, where:

    - The pole locations are equal to:
       - the signal path natural bandwidth, and
       - the user specified peaking frequency.

    - The zero location is chosen, so as to provide the desired degree
      of peaking.

    Inputs:

      - rx_bw        The natural (or, unequalized) signal path bandwidth (Hz).

      - peak_freq    The location of the desired peak in the frequency
                     response (Hz).

      - peak_mag     The desired relative magnitude of the peak (dB). (mag(H(0)) = 1)

      - w            The list of frequencies of interest (rads./s).

      - mode         The operational mode; must be one of:
                       - 'Off'    : CTLE is disengaged.
                       - 'Passive': Maximum frequency response has magnitude one.
                       - 'AGC'    : Automatic gain control. (Handled by calling routine.)
                       - 'Manual' : D.C. offset is set manually.

      - dc_offset    The d.c. offset of the CTLE gain curve (dB).
                     (Only valid, when 'mode' = 'Manual'.)

    Outputs:

      - w, H         The resultant complex frequency response, at the
                     given frequencies.

    """

    if(mode == 'Off'):
        return (w, ones(len(w)))

    p2   = -2. * pi * rx_bw
    p1   = -2. * pi * peak_freq
    z    = p1 / pow(10., peak_mag / 20.)
    if(p2 != p1):
        r1   = (z - p1) / (p2 - p1)
        r2   = 1 - r1
    else:
        r1   = -1.
        r2   = z - p1
    b, a = invres([r1, r2], [p1, p2], [])
    w, H = freqs(b, a, w)

    if(mode == 'Passive'):
        H /= max(abs(H))
    elif(mode == 'Manual' or mode == 'AGC'):
        H *= pow(10., dc_offset / 20.) / abs(H[0])  # Enforce d.c. offset.
    else:
        raise RunTimeException("pybert_util.make_ctle(): Unrecognized value for 'mode' parameter: {}.".format(mode))

    return (w, H)
 def invert_signal(self,signals,nodes=[(1,0)],Nterms=10,\
                   interpolation='linear',\
                   select_by='continuity',\
                   closest_pole=0,\
                   scaling=10):
     """The inversion is not unique, consequently the selected solution
     will probably be wrong if signal values correspond with 
     "beta" values that are too large (`|beta|~>min{|Poles|}`).
     This can be expected to break at around `|beta|>2`."""
     #Default is to invert signal in contact
     #~10 terms seem required to converge on e.g. SiO2 spectrum,
     #especially on the Re(beta)<0 (low signal) side of phonons
     
     global roots,poly,root_scaling
     
     #global betas,all_roots,pmin,rs,ps,As,Bs,roots,to_minimize
     if self.verbose:
         Logger.write('Inverting `signals` based on the provided `nodes` to obtain consistent beta values...')
     
     if not hasattr(signals,'__len__'): signals=[signals]
     if not isinstance(signals,AWA): signals=AWA(signals)
     
     zs,ws=list(zip(*nodes))
     ws_grid=numpy.array(ws).reshape((len(ws),1)) #last dimension is to broadcast over all `Nterms` equally
     zs=numpy.array(zs)
     
     Rs=self.Rs[:,:Nterms].interpolate_axis(zs,axis=0,kind=interpolation,
                                                    bounds_error=False,extrapolate=True)
     Ps=self.Ps[:,:Nterms].interpolate_axis(zs,axis=0,kind=interpolation,
                                                    bounds_error=False,extrapolate=True)
     
     #`rs` and `ps` can safely remain as arrays for `invres`
     rs=(Rs*ws_grid).flatten()
     ps=Ps.flatten()
     
     k0=numpy.sum(rs/ps).tolist()
     
     #Rescale units so their order of magnitude centers around 1
     rscaling=numpy.exp(-(numpy.log(numpy.abs(rs).max())+\
                         numpy.log(numpy.abs(rs).min()))/2.)
     pscaling=numpy.exp(-(numpy.log(numpy.abs(ps).max())+\
                          numpy.log(numpy.abs(ps).min()))/2.)
     root_scaling=1/pscaling
     #rscaling=1
     #pscaling=1
     if self.verbose:
         Logger.write('\tScaling residues by a factor %1.2e to reduce floating point overflow...'%rscaling)
         Logger.write('\tScaling poles by a factor %1.2e to reduce floating point overflow...'%pscaling)
     rs*=rscaling; ps*=pscaling
     k0*=rscaling/pscaling
     signals=signals*rscaling/pscaling
     
     #highest order first in `Ps` and `Qs`
     #VERY SLOW - about 100ms on practical inversions (~60 terms)
     As,Bs=invres(rs, ps, k=[k0], tol=1e-16, rtype='avg') #tol=1e-16 is the smallest allowable to `unique_roots`..
     
     dtype=numpy.complex128 #Double precision offers noticeable protection against overflow
     As=numpy.array(As,dtype=dtype)
     Bs=numpy.array(Bs,dtype=dtype)
     signals=signals.astype(dtype)
     
     #import time
     
     betas=[]
     for i,signal in enumerate(signals):
         #t1=time.time()
         
         #Root finding `roots` seems to give noisy results when `Bs` has degree >84, with dynamic range ~1e+/-30 in coefficients...
         #Pretty fast - 5-9 ms on practical inversions with rank ~60 companion matrices, <1 ms with ~36 terms
         #@TODO: Root finding chokes on `Nterms=9` (number of eigenfields) and `Nts=12` (number of nodes),
         #       necessary for truly converged S3 on resonant phonons, probably due to
         #       floating point overflow - leading term increases exponentially with
         #       number of terms, leading to huge dynamic range.
         #       Perhaps limited by the double precision of DGEEV.
         #       So, replace with faster / more reliable root finder?
         #       We need 1) speed, 2) ALL roots (or at least the first ~10 smallest)
         poly=As-signal*Bs
         roots=find_roots(poly,scaling=scaling)
         roots=roots[roots.imag>0]
         roots*=root_scaling #since all beta units scaled by `pscaling`, undo that here
         
         #print time.time()-t1
         
         #How should we select the most likely beta among the multiple solutions?
         #1. Avoids large changes in value of beta
         if select_by=='difference' and i>=1:
             if i==1 and self.verbose:
                 Logger.write('\tSelecting remaining roots by minimizing differences with prior...')
             to_minimize=numpy.abs(roots-betas[i-1])
             
         #2. Avoids large changes in slope of beta (best for spectroscopy)
         #Nearly guarantees good beta spectrum, with exception of very loosely sampled SiC spectrum
         #Loosely samples SiO2-magnitude phonons still perfectly fine
         elif select_by=='continuity' and i>=2:
             if i==2 and self.verbose:
                 Logger.write('\tSelecting remaining roots by ensuring continuity with prior...')
             earlier_diff=betas[i-1]-betas[i-2]
             current_diffs=roots-betas[i-1]
             to_minimize=numpy.abs(current_diffs-earlier_diff)
             
         #3. Select specifically which pole we want |beta| to be closest to
         else:
             if i==0 and self.verbose:
                 Logger.write('\tSeeding inversion closest to pole %i...'%closest_pole)
             reordering=numpy.argsort(numpy.abs(roots)) #Order the roots towards increasing beta
             roots=roots[reordering]
             to_minimize=numpy.abs(closest_pole-numpy.arange(len(roots)))
             
         beta=roots[to_minimize==to_minimize.min()].squeeze()
         betas.append(beta)
         if not i%5 and self.verbose:
             Logger.write('\tProgress: %1.2f%%  -  Inverted %i signals of %i.'%\
                                  (((i+1)/numpy.float(len(signals))*100),\
                                   (i+1),len(signals)))
     
     betas=AWA(betas); betas.adopt_axes(signals)
     betas=betas.squeeze()
     if not betas.ndim: betas=betas.tolist()
     
     return betas