Module dadi.PhiManip

Manipulating population frequency spectra phi. e.g. population splittings and admixture

Expand source code
"""
Manipulating population frequency spectra phi. e.g. population splittings and
admixture
"""
import numpy
from numpy import newaxis as nuax
import scipy.integrate

from dadi import Numerics

def phi_1D(xx, nu=1.0, theta0=1.0, gamma=0, h=0.5, theta=None, beta=1):
    """
    One-dimensional phi for a constant-sized population with genic selection.

    xx: one-dimensional grid of frequencies upon which phi is defined
    nu: size of this population, relative to the reference population size Nref.
    theta0: scaled mutation rate, equal to 4*Nref * u, where u is the mutation 
            event rate per generation for the simulated locus and Nref is the 
            reference population size.
    gamma: scaled selection coefficient, equal to 2*Nref * s, where s is the
           selective advantage.
    h: Dominance coefficient. If A is the selected allele, the aa has fitness 1,
       aA has fitness 1+2sh and AA has fitness 1+2s. h = 0.5 corresonds to
       genic selection.
    theta: deprecated in favor of distinct nu and theta0 arguments, for 
           consistency with Integration functions.

    Returns a new phi array.
    """

    if theta is not None:
        raise ValueError('The parameter theta has been deprecated in favor of '
                         'parameters nu and theta0, for consistency with the '
                         'Integration functions.')

    if h == 0.5:
        return phi_1D_genic(xx, nu, theta0, gamma, beta=beta)

    # Eqn 1 from Williamson, Fledel-Alon, Bustamante _Genetics_ 168:463 (2004).

    # Modified to incorporate fact that for beta != 1, we get a term of 
    # 4*beta/(beta+1)^2 in V. This can be implemented by rescaling gamma
    # and rescaling the final phi.
    gamma = gamma * 4.*beta/(beta+1.)**2

    # Our final result is of the form 
    # exp(Q) * int_0_x exp(-Q) / int_0_1 exp(-Q)

    # For large negative gamma, exp(-Q) becomes numerically infinite.
    # To work around this, we can adjust Q in both the top and bottom
    # integrals by the same factor. We choose to make that factor the 
    # maximum of -Q, which is -2*gamma.
    Qadjust = 0
    # For negative gamma, the maximum of -Q is -2*gamma.
    if gamma < 0 and numpy.isinf(numpy.exp(-2*gamma)):
        Qadjust = -2*gamma

    # For large positive gamma, the prefactor exp(Q) becomes numerically
    # infinite, while the numerator becomes very small. To work around this,
    # we'll can pull the prefactor into the numerator integral.

    # Evaluate the denominator integral.
    integrand = lambda xi: numpy.exp(-4*gamma*h*xi - 2*gamma*(1-2*h)*xi**2
                                     - Qadjust)
    int0, eps = scipy.integrate.quad(integrand, 0, 1, epsabs=0,
                                     points=numpy.linspace(0,1,41))

    ints = numpy.empty(len(xx))
    # Evaluate the numerator integrals
    if gamma < 0:
        # In this case, the prefactor is not divergent, so we can evaluate
        # the numerator as before, using the Qadjust if necessary.
        for ii,q in enumerate(xx):
            val, eps = scipy.integrate.quad(integrand, q, 1, epsabs=0,
                                            points=numpy.linspace(q,1,41))
            ints[ii] = val
        phi = numpy.exp(4*gamma*h*xx + 2*gamma*(1-2*h)*xx**2)*ints/int0
    else:
        # In this case, the prefactor may be divergent, so we do the integral
        # with the prefactor pulled inside
        integrand = lambda xi, q: numpy.exp(-4*gamma*h*(xi-q) -
                                            2*gamma*(1-2*h)*(xi**2-q**2))
        for ii,q in enumerate(xx):
            val, eps = scipy.integrate.quad(integrand, q, 1, args=(q,))
            ints[ii] = val
        phi = ints/int0

    # Protect from division by zero errors
    phi[1:-1] *= 1./(xx[1:-1]*(1-xx[1:-1]))
    # Technically, phi diverges at 0. This kludge lets us do numerics
    # sensibly.
    phi[0] = phi[1]
    # I used Mathematica to calculate the proper limit for x goes to 1.
    # But if we've adjusted the denominator integrand, then that limit doesn't
    # hold. We only need to do that in cases of strong negative selection,
    # when phi near 1 should be almost zero anyways. So we'll just ensure
    # that it is at least monotonically decreasing.
    if Qadjust == 0:
        phi[-1] = 1./int0
    else:
        phi[-1] = min(phi[-1], phi[-2])

    return phi * nu*theta0 * 4.*beta/(beta+1.)**2

def phi_1D_genic(xx, nu=1.0, theta0=1.0, gamma=0, theta=None, beta=1):
    """
    One-dimensional phi for a constant-sized population with genic selection.

    xx: one-dimensional grid of frequencies upon which phi is defined
    nu: size of this population, relative to the reference population size Nref.
    theta0: scaled mutation rate, equal to 4*Nref * u, where u is the mutation 
            event rate per generation for the simulated locus and Nref is the 
            reference population size.
    gamma: scaled selection coefficient, equal to 2*Nref * s, where s is the
           selective advantage.
    theta: deprecated in favor of distinct nu and theta0 arguments, for 
           consistency with Integration functions.

    Returns a new phi array.
    """

    if theta is not None:
        raise ValueError('The parameter theta has been deprecated in favor of '
                         'parameters nu and theta0, for consistency with the '
                         'Integration functions.')
    if gamma == 0:
        return phi_1D_snm(xx, nu, theta0, beta=beta)

    # Beta effectively re-scales gamma.
    gamma = gamma * 4.*beta/(beta+1.)**2

    exp = numpy.exp
    # Protect from warnings on division by zero
    if xx[0] == 0 and xx[-1] == 1:
        phi = 0*xx
        if gamma > -300:
            phi[1:-1] = 1./(xx[1:-1]*(1-xx[1:-1]))\
                    * (1-exp(-2*gamma*(1-xx[1:-1])))/(1-exp(-2*gamma))
        else:
            # Avoid overflow issues for very negative gammas
            phi[1:-1] = 1./(xx[1:-1]*(1-xx[1:-1])) * exp(2*gamma*xx[1:-1])
    else:
        if gamma > -300:
            phi = 1./(xx*(1-xx)) * (1-exp(-2*gamma*(1-xx)))/(1-exp(-2*gamma))
        else:
            phi = 1./(xx*(1-xx)) * exp(2*gamma*xx)

    if xx[0] == 0:
        phi[0] = phi[1]
    if xx[-1] == 1:
        if gamma < 300:
            limit = 2*gamma * exp(2*gamma)/(exp(2*gamma)-1)
        else:
            limit = 2*gamma
        phi[-1] = limit
    return phi * nu*theta0 * 4.*beta/(beta+1.)**2

def phi_1D_snm(xx, nu=1.0, theta0=1.0, theta=None, beta=1):
    """
    Standard neutral one-dimensional probability density.

    xx: one-dimensional grid of frequencies upon which phi is defined
    nu: size of this population, relative to the reference population size Nref.
    theta0: scaled mutation rate, equal to 4*Nref * u, where u is the mutation 
            event rate per generation for the simulated locus and Nref is the 
            reference population size.
    theta: deprecated in favor of distinct nu and theta0 arguments, for 
           consistency with Integration functions.

    Returns a new phi array.
    """

    if theta is not None:
        raise ValueError('The parameter theta has been deprecated in favor of '
                         'parameters nu and theta0, for consistency with the '
                         'Integration functions.')
    # Protect from division by zero errors
    if xx[0] == 0:
        phi = 0*xx
        phi[1:] = nu*theta0/xx[1:]
        phi[0] = phi[1]
    else:
        phi = nu*theta0/xx
    return phi * 4.*beta/(beta+1.)**2

def check_xx(xx):
    """
    Check whether xx is monotonically increasing from 0 to 1. 
    """
    if not xx[0] == 0 and xx[1] == 1:
        raise ValueError('Input xx argument does not run from 0 to 1.'
                         'Have you passed in an incorrect argument?')
    if not numpy.all(numpy.diff(xx) >= 0):
        raise ValueError('Input xx argument is not monotonically increasing. '
                         'Have you passed in an incorrect argument?')

def phi_1D_to_2D(xx, phi_1D):
    """
    Implement a one-to-two population split.

    xx: one-dimensional grid of frequencies upon which phi is defined
    phi1D: initial probability density

    Returns a new two-dimensional phi array.
    """
    check_xx(xx)

    pts = len(xx)
    phi_2D = numpy.zeros((pts, pts))
    for ii in range(1, pts-1):
        phi_2D[ii,ii] = phi_1D[ii] * 2/(xx[ii+1]-xx[ii-1])
    return phi_2D

def phi_2D_to_3D_split_2(xx, phi_2D):
    """
    Split population 2 into populations 2 and 3.

    xx: one-dimensional grid of frequencies upon which phi is defined
    phi2D: initial probability density

    Returns a new three-dimensional phi array.
    """
    check_xx(xx)

    return phi_2D_to_3D_admix(phi_2D,0,xx,xx,xx)

def phi_2D_to_3D_split_1(xx, phi_2D):
    """
    Split population 1 into populations 1 and 3.

    xx: one-dimensional grid of frequencies upon which phi is defined
    phi2D: initial probability density

    Returns a new three-dimensional phi array.
    """
    check_xx(xx)

    return phi_2D_to_3D_admix(phi_2D,1,xx,xx,xx)

def _admixture_intermediates(phi, ad_z, zz):
    # Find where those z values map to in the zz array.
    # Note that zz[upper_z[ii,jj]] >= ad_z[ii,jj]
    upper_z_index = numpy.searchsorted(zz, ad_z)
    # I occasionally seem to get values > 1 (floating pt error) in ad_z. This
    # corrects them.
    upper_z_index = numpy.minimum(upper_z_index, len(zz)-1)
    # Also ensure that upper_z_index >= 1, so lower_z_index is never < 0.
    upper_z_index = numpy.maximum(upper_z_index, 1)
    lower_z_index = upper_z_index - 1
    upper_z = zz[upper_z_index]
    lower_z = zz[lower_z_index]
    
    # These are the spacings between points. To special-case the endpoints, we
    # use where to set the appropriate spacings to zero. This turns out to be
    # what we want to do for proper normalization via the trapezoid rule.
    delz0 = zz[lower_z_index] - zz[lower_z_index-1]
    delz0 = numpy.where(lower_z_index == 0, 0, delz0)
    
    delz1 = zz[upper_z_index] - zz[lower_z_index]
    delz1 = numpy.where(upper_z_index == 0, 0, delz1)
    
    delz2 = zz[(upper_z_index+1)%len(zz)] - zz[upper_z_index]
    delz2 = numpy.where(upper_z_index == len(zz)-1, 0, delz2)
    
    # frac_lower is the fraction that gets assigned to the lower z index
    frac_lower = (upper_z - ad_z)/(upper_z - lower_z)
    # frac_upper is the fraction that gets assigned to the upper z index
    frac_upper = (ad_z-lower_z)/(upper_z - lower_z)
    # This is the overall normalization for the entry
    norm = 2*phi/(frac_lower*delz0 + delz1 + frac_upper*delz2)

    return lower_z_index, upper_z_index, frac_lower, frac_upper, norm

def _two_pop_admixture_intermediates(phi_2D, f, xx,yy,zz):
    """
    Intermediate results used when splitting a new population out from a 2D phi.
    """
    # For each point x,y in phi, this is the corresponding frequency z that SNPs
    # with frequency x and y in populations 1 and 2 would map to.
    ad_z = f*xx[:,nuax] + (1-f)*yy[nuax,:]

    lower_z_index, upper_z_index, frac_lower, frac_upper, norm \
            = _admixture_intermediates(phi_2D, ad_z, zz)

    return lower_z_index, upper_z_index, frac_lower, frac_upper, norm

def _three_pop_admixture_intermediates(phi_3D, f1,f2, xx,yy,zz,ww):
    """
    Intermediate results used when splitting a new population out from a 3D phi.

    ww: frequency mapping for the new population.
    """
    # For each point x,y,z in phi, this is the corresponding frequency w that
    # SNPs with frequency x,y,z in populations 1,2,3 would map to.
    if f1 + f2 > 1:
        raise ValueError('Admixture proportions (f1=%f, f2 = %f) are '
                         'non-sensible.' % (f1, f2))
    ad_w = f1*xx[:,nuax,nuax] + f2*yy[nuax,:,nuax] + (1-f1-f2)*zz[nuax,nuax,:]

    lower_w_index, upper_w_index, frac_lower, frac_upper, norm \
            = _admixture_intermediates(phi_3D, ad_w, ww)

    return lower_w_index, upper_w_index, frac_lower, frac_upper, norm

def _four_pop_admixture_intermediates(phi_4D, f1,f2,f3, xx,yy,zz,aa,bb):
    """
    Intermediate results used when splitting a new population out from a 4D phi.

    ww: frequency mapping for the new population.
    """
    # For each point x,y,z,a in phi, this is the corresponding frequency b that
    # SNPs with frequency x,y,z,a in populations 1,2,3,4 would map to.
    if f1 + f2 + f3> 1:
        raise ValueError('Admixture proportions (f1=%f, f2 = %f, f3=%f) are '
                         'non-sensible.' % (f1, f2, f3))
    ad_w = f1*xx[:,nuax,nuax,nuax] + f2*yy[nuax,:,nuax,nuax] + f3*zz[nuax,nuax,:,nuax]\
        + (1-f1-f2-f3)*aa[nuax,nuax,nuax,:]

    lower_w_index, upper_w_index, frac_lower, frac_upper, norm \
            = _admixture_intermediates(phi_4D, ad_w, bb)

    return lower_w_index, upper_w_index, frac_lower, frac_upper, norm

def _five_pop_admixture_intermediates(phi_5D, f1,f2,f3,f4, xx,yy,zz,aa,bb,cc):
    """
    Intermediate results used when splitting a new population out from a 4D phi.

    ww: frequency mapping for the new population.
    """
    # For each point x,y,z,a,b in phi, this is the corresponding frequency c that
    # SNPs with frequency x,y,z,a,b in populations 1,2,3,4,5 would map to.
    if f1 + f2 + f3 + f4 > 1:
        raise ValueError('Admixture proportions (f1=%f, f2 = %f, f3=%f, f4=%f) are '
                         'non-sensible.' % (f1, f2, f3,  f4))
    ad_w = f1*xx[:,nuax,nuax,nuax,nuax] + f2*yy[nuax,:,nuax,nuax,nuax] + f3*zz[nuax,nuax,:,nuax,nuax]\
        + f4*aa[nuax,nuax,nuax,:,nuax] + (1-f1-f2-f3-f4)*bb[nuax,nuax,nuax,nuax,:]

    lower_w_index, upper_w_index, frac_lower, frac_upper, norm \
            = _admixture_intermediates(phi_5D, ad_w, cc)

    return lower_w_index, upper_w_index, frac_lower, frac_upper, norm


def phi_2D_to_3D_admix(phi, f1, xx,yy,zz):
    """
    Create population 3 admixed from populations 1 and 2.

    Returns a 3D sfs of shape (len(xx),len(yy),len(zz))

    phi:   phi corresponding to original 2 populations
    f1:     Fraction of population 3 derived from population 1. (A fraction 1-f1
             will be derived from population 2.)
    xx,yy: Mapping of points in phi to frequencies in populations 1 and 2.
    zz:    Frequency mapping that will be used along population 3 axis.
    """
    lower_z_index, upper_z_index, frac_lower, frac_upper, norm \
            = _two_pop_admixture_intermediates(phi, f1, xx,yy,zz)


    # Assemble our result.
    # This uses numpy's fancy indexing. It is much, much faster than an
    # explicit loop.
    # See the numpy-discussion post "Numpy Advanced Indexing Question" by
    # Robert Kern on July 16, 2008
    # http://projects.scipy.org/pipermail/numpy-discussion/2008-July/035776.html
    idx_i = numpy.arange(len(xx))[:,numpy.newaxis]
    idx_j = numpy.arange(len(yy))[numpy.newaxis,:]

    phi_3D = numpy.zeros((len(xx), len(yy), len(zz)))
    phi_3D[idx_i, idx_j, lower_z_index] = frac_lower*norm
    phi_3D[idx_i, idx_j, upper_z_index] += frac_upper*norm

    return phi_3D
phi_2D_to_3D = phi_2D_to_3D_admix

def phi_3D_to_4D(phi, f1,f2, xx,yy,zz,aa):
    """
    Create population 4 from populations 1, 2, and 3.

    Returns a 4D sfs of shape (len(xx),len(yy),len(zz),len(aa))

    phi:   phi corresponding to original 3 populations
    f1:    Fraction of population 4 derived from population 1.
    f2:    Fraction of population 4 derived from population 2.
           (A fraction 1-f1-f2 will be derived from population 3.)
    xx,yy,zz: Mapping of points in phi to frequencies in populations 1, 2, and 3.
    aa:    Frequency mapping that will be used along population 4 axis.
    """
    lower_z_index, upper_z_index, frac_lower, frac_upper, norm \
            = _three_pop_admixture_intermediates(phi, f1, f2, xx,yy,zz, aa)

    # Assemble our result.
    # This uses numpy's fancy indexing. It is much, much faster than an
    # explicit loop.
    # See the numpy-discussion post "Numpy Advanced Indexing Question" by
    # Robert Kern on July 16, 2008
    # http://projects.scipy.org/pipermail/numpy-discussion/2008-July/035776.html
    idx_i = numpy.arange(len(xx))[:,nuax,nuax]
    idx_j = numpy.arange(len(yy))[nuax,:,nuax]
    idx_k = numpy.arange(len(zz))[nuax,nuax,:]

    phi_4D = numpy.zeros((len(xx), len(yy), len(zz), len(aa)))
    phi_4D[idx_i, idx_j, idx_k, lower_z_index] = frac_lower*norm
    phi_4D[idx_i, idx_j, idx_k, upper_z_index] += frac_upper*norm

    return phi_4D

def phi_4D_to_5D(phi, f1,f2,f3, xx,yy,zz,aa,bb):
    """
    Create population 5 from populations 1, 2, 3, and 4.

    Returns a 5D sfs of shape (len(xx),len(yy),len(zz),len(aa),len(bb))

    phi:   phi corresponding to original 3 populations
    f1:    Fraction of population 4 derived from population 1.
    f2:    Fraction of population 4 derived from population 2.
    f4:    Fraction of population 4 derived from population 3.
           (A fraction 1-f1-f2-f3 will be derived from population 4.)
    xx,yy,zz,aa: Mapping of points in phi to frequencies in populations 1, 2, 3, 4.
    bb:    Frequency mapping that will be used along population bb axis.
    """
    lower_z_index, upper_z_index, frac_lower, frac_upper, norm \
            = _four_pop_admixture_intermediates(phi, f1,f2,f3, xx,yy,zz,aa, bb)

    # Assemble our result.
    # This uses numpy's fancy indexing. It is much, much faster than an
    # explicit loop.
    # See the numpy-discussion post "Numpy Advanced Indexing Question" by
    # Robert Kern on July 16, 2008
    # http://projects.scipy.org/pipermail/numpy-discussion/2008-July/035776.html
    idx_i = numpy.arange(len(xx))[:,nuax,nuax,nuax]
    idx_j = numpy.arange(len(yy))[nuax,:,nuax,nuax]
    idx_k = numpy.arange(len(zz))[nuax,nuax,:,nuax]
    idx_l = numpy.arange(len(aa))[nuax,nuax,nuax,:]

    phi_5D = numpy.zeros((len(xx), len(yy), len(zz), len(aa), len(bb)))
    phi_5D[idx_i, idx_j, idx_k, idx_l, lower_z_index] = frac_lower*norm
    phi_5D[idx_i, idx_j, idx_k, idx_l, upper_z_index] += frac_upper*norm

    return phi_5D

def phi_2D_admix_1_into_2(phi, f, xx,yy):
    """
    Admix population 1 into population 2.

    Alters phi in place and returns the new version.

    phi:   phi corresponding to original 2 populations
    f:     Fraction of updated population 2 to be derived from population 1. 
             (A fraction 1-f will be derived from the original population 2.)
    xx,yy: Mapping of points in phi to frequencies in populations 1 and 2.
    """
    # This is just like the the split_admix situation, but we're splitting into
    # a population with zz=yy. We could do this by creating a xx by yy by yy
    # array, then integrating out the second population. That's a big waste of
    # memory, however.
    lower_z_index, upper_z_index, frac_lower, frac_upper, norm \
            = _two_pop_admixture_intermediates(phi, f, xx,yy,yy)

    # Basically, we're splitting into a third zz population, then integrating
    # over yy to be left with the two populations we care about.
    lower_cont = frac_lower*norm
    upper_cont = frac_upper*norm
    idx_j = numpy.arange(phi.shape[1])
    for ii in range(phi.shape[0]):
        phi_int = numpy.zeros((phi.shape[1], phi.shape[1]))
        # Use fancy indexing to avoid the commented out loop.
        #for jj in range(len(yy)):
        #    phi_int[jj, upper_z_index[ii,jj]] += frac_upper[ii,jj]*norm[ii,jj]
        #    phi_int[jj, lower_z_index[ii,jj]] += frac_lower[ii,jj]*norm[ii,jj]
        phi_int[idx_j, lower_z_index[ii]] = lower_cont[ii]
        phi_int[idx_j, upper_z_index[ii]] += upper_cont[ii]
        phi[ii] = Numerics.trapz(phi_int, yy, axis=0)

    return phi

def phi_2D_admix_2_into_1(phi, f, xx,yy):
    """
    Admix population 2 into population 1.

    Alters phi in place and returns the new version.

    phi:   phi corresponding to original 2 populations
    f:     Fraction of updated population 1 to be derived from population 2. 
             (A fraction 1-f will be derived from the original population 1.)
    xx,yy: Mapping of points in phi to frequencies in populations 1 and 2.
    """
    # Note that it's 1-f here since f now denotes the fraction coming from
    # population 2.
    lower_z_index, upper_z_index, frac_lower, frac_upper, norm \
            = _two_pop_admixture_intermediates(phi, 1-f, xx,yy,xx)

    idx_i = numpy.arange(phi.shape[0])
    lower_cont = frac_lower*norm
    upper_cont = frac_upper*norm
    for jj in range(len(yy)):
        phi_int = numpy.zeros((len(xx), len(xx)))
        phi_int[idx_i, lower_z_index[:,jj]] = lower_cont[:,jj]
        phi_int[idx_i, upper_z_index[:,jj]] = upper_cont[:,jj]
        phi[:,jj] = Numerics.trapz(phi_int, xx, axis=0)

    return phi

def phi_3D_admix_1_and_2_into_3(phi, f1,f2, xx,yy,zz):
    """
    Admix populations 1 and 2 into population 3.

    Alters phi in place and returns the new version.

    phi:      phi corresponding to original 3 populations.
    f1:       Fraction of updated population 3 to be derived from population 1. 
    f2:       Fraction of updated population 3 to be derived from population 2. 
              A fraction (1-f1-f2) will be derived from the original pop 3.
    xx,yy,zz: Mapping of points in phi to frequencies in populations 1,2 and 3.
    """
    lower_w_index, upper_w_index, frac_lower, frac_upper, norm \
            = _three_pop_admixture_intermediates(phi, f1,f2, xx,yy,zz, zz)

    lower_cont = frac_lower * norm
    upper_cont = frac_upper * norm

    # Basically, we're splitting into a fourth ww population, then integrating
    # over zz to be left with the two populations we care about.
    idx_k = numpy.arange(phi.shape[2])
    for ii in range(phi.shape[0]):
        for jj in range(phi.shape[1]):
            phi_int = numpy.zeros((phi.shape[2], phi.shape[2]))
            phi_int[idx_k, lower_w_index[ii,jj]] = lower_cont[ii,jj]
            phi_int[idx_k, upper_w_index[ii,jj]] = upper_cont[ii,jj]
            phi[ii,jj] = Numerics.trapz(phi_int, zz, axis=0)

    return phi

def phi_3D_admix_1_and_3_into_2(phi, f1,f3, xx,yy,zz):
    """
    Admix populations 1 and 3 into population 2.

    Alters phi in place and returns the new version.

    phi:      phi corresponding to original 3 populations.
    f1:       Fraction of updated population 2 to be derived from population 1. 
    f3:       Fraction of updated population 2 to be derived from population 3. 
              A fraction (1-f1-f3) will be derived from the original pop 2.
    xx,yy,zz: Mapping of points in phi to frequencies in populations 1,2 and 3.
    """
    lower_w_index, upper_w_index, frac_lower, frac_upper, norm \
            = _three_pop_admixture_intermediates(phi, f1,1-f1-f3, xx,yy,zz, yy)

    lower_cont = frac_lower * norm
    upper_cont = frac_upper * norm

    # Basically, we're splitting into a fourth ww population, then integrating
    # over yy to be left with the two populations we care about.
    idx_j = numpy.arange(phi.shape[1])
    for ii in range(phi.shape[0]):
        for kk in range(phi.shape[2]):
            phi_int = numpy.zeros((phi.shape[1], phi.shape[1]))
            phi_int[idx_j, lower_w_index[ii,:,kk]] = lower_cont[ii,:,kk]
            phi_int[idx_j, upper_w_index[ii,:,kk]] = upper_cont[ii,:,kk]
            phi[ii,:,kk] = Numerics.trapz(phi_int, yy, axis=0)

    return phi

def phi_3D_admix_2_and_3_into_1(phi, f2,f3, xx,yy,zz):
    """
    Admix populations 2 and 3 into population 1.

    Alters phi in place and returns the new version.

    phi:      phi corresponding to original 3 populations.
    f2:       Fraction of updated population 1 to be derived from population 2. 
    f3:       Fraction of updated population 1 to be derived from population 3. 
              A fraction (1-f2-f3) will be derived from the original pop 1.
    xx,yy,zz: Mapping of points in phi to frequencies in populations 1,2 and 3.
    """
    lower_w_index, upper_w_index, frac_lower, frac_upper, norm \
            = _three_pop_admixture_intermediates(phi, 1-f2-f3,f2, xx,yy,zz, xx)

    lower_cont = frac_lower * norm
    upper_cont = frac_upper * norm

    # Basically, we're splitting into a fourth ww population, then integrating
    # over yy to be left with the two populations we care about.
    idx_i = numpy.arange(phi.shape[0])
    for jj in range(phi.shape[1]):
        for kk in range(phi.shape[2]):
            phi_int = numpy.zeros((phi.shape[0], phi.shape[0]))
            phi_int[idx_i, lower_w_index[:,jj,kk]] = lower_cont[:,jj,kk]
            phi_int[idx_i, upper_w_index[:,jj,kk]] = upper_cont[:,jj,kk]
            phi[:,jj,kk] = Numerics.trapz(phi_int, xx, axis=0)

    return phi

def phi_4D_admix_into_1(phi, f2,f3,f4, xx,yy,zz,aa):
    """
    Admix populations 2, 3, and 4 into population 1.

    Alters phi in place and returns the new version.

    phi:      phi corresponding to original 4 populations.
    f2:       Fraction of updated population 1 to be derived from population 2. 
    f3:       Fraction of updated population 1 to be derived from population 3. 
    f4:       Fraction of updated population 1 to be derived from population 4. 
              A fraction (1-f2-f3-f4) will be derived from the original pop 1.
    xx,yy,zz,aa: Mapping of points in phi to frequencies in populations 1,2,3, and 4.
    """
    lower_w_index, upper_w_index, frac_lower, frac_upper, norm \
            = _four_pop_admixture_intermediates(phi, 1-f2-f3-f4,f2,f3, xx,yy,zz,aa, xx)

    lower_cont = frac_lower * norm
    upper_cont = frac_upper * norm

    # Basically, we're splitting into a fifth bb population, then integrating
    # over xx to be left with the two populations we care about.
    idx_i = numpy.arange(phi.shape[0])
    for jj in range(phi.shape[1]):
        for kk in range(phi.shape[2]):
            for ll in range(phi.shape[3]):
                phi_int = numpy.zeros((phi.shape[0], phi.shape[0]))
                phi_int[idx_i, lower_w_index[:,jj,kk,ll]] = lower_cont[:,jj,kk,ll]
                phi_int[idx_i, upper_w_index[:,jj,kk,ll]] = upper_cont[:,jj,kk,ll]
                phi[:,jj,kk,ll] = Numerics.trapz(phi_int, xx, axis=0)

    return phi

def phi_4D_admix_into_4(phi, f1,f2,f3, xx,yy,zz,aa):
    """
    Admix populations 1,2, and 3 into population 4.

    Alters phi in place and returns the new version.

    phi:      phi corresponding to original 4 populations.
    f1:       Fraction of updated population 4 to be derived from population 1. 
    f2:       Fraction of updated population 4 to be derived from population 2. 
    f3:       Fraction of updated population 4 to be derived from population 3. 
              A fraction (1-f1-f2-f3) will be derived from the original pop 4.
    xx,yy,zz,aa: Mapping of points in phi to frequencies in populations 1,2,3 and 4.
    """
    lower_w_index, upper_w_index, frac_lower, frac_upper, norm \
            = _four_pop_admixture_intermediates(phi, f1,f2,f3, xx,yy,zz,aa, yy)

    lower_cont = frac_lower * norm
    upper_cont = frac_upper * norm

    idx_l = numpy.arange(phi.shape[3])
    for ii in range(phi.shape[0]):
        for jj in range(phi.shape[1]):
            for kk in range(phi.shape[2]):
                phi_int = numpy.zeros((phi.shape[3], phi.shape[3]))
                phi_int[idx_l, lower_w_index[ii,jj,kk,:]] = lower_cont[ii,jj,kk,:]
                phi_int[idx_l, upper_w_index[ii,jj,kk,:]] = upper_cont[ii,jj,kk,:]
                phi[ii,jj,kk,:] = Numerics.trapz(phi_int, aa, axis=0)

    return phi

def phi_4D_admix_into_3(phi, f1,f2,f4, xx,yy,zz,aa):
    """
    Admix populations 1,2, and 4 into population 3.

    Alters phi in place and returns the new version.

    phi:      phi corresponding to original 4 populations.
    f1:       Fraction of updated population 3 to be derived from population 1. 
    f2:       Fraction of updated population 3 to be derived from population 2. 
    f4:       Fraction of updated population 3 to be derived from population 4. 
              A fraction (1-f1-f2-f4) will be derived from the original pop 3.
    xx,yy,zz,aa: Mapping of points in phi to frequencies in populations 1,2,3 and 4.
    """
    lower_w_index, upper_w_index, frac_lower, frac_upper, norm \
            = _four_pop_admixture_intermediates(phi, f1,f2,1-f1-f2-f4, xx,yy,zz,aa, yy)

    lower_cont = frac_lower * norm
    upper_cont = frac_upper * norm

    idx_k = numpy.arange(phi.shape[2])
    for ii in range(phi.shape[0]):
        for jj in range(phi.shape[1]):
            for ll in range(phi.shape[3]):
                phi_int = numpy.zeros((phi.shape[2], phi.shape[2]))
                phi_int[idx_k, lower_w_index[ii,jj,:,ll]] = lower_cont[ii,jj,:,ll]
                phi_int[idx_k, upper_w_index[ii,jj,:,ll]] = upper_cont[ii,jj,:,ll]
                phi[ii,jj,:,ll] = Numerics.trapz(phi_int, zz, axis=0)

    return phi

def phi_4D_admix_into_2(phi, f1,f3,f4, xx,yy,zz,aa):
    """
    Admix populations 1,3, and 4 into population 2.

    Alters phi in place and returns the new version.

    phi:      phi corresponding to original 4 populations.
    f1:       Fraction of updated population 2 to be derived from population 1. 
    f3:       Fraction of updated population 2 to be derived from population 3. 
    f4:       Fraction of updated population 2 to be derived from population 4. 
              A fraction (1-f1-f3-f4) will be derived from the original pop 2.
    xx,yy,zz,aa: Mapping of points in phi to frequencies in populations 1,2,3 and 4.
    """
    lower_w_index, upper_w_index, frac_lower, frac_upper, norm \
            = _four_pop_admixture_intermediates(phi, f1,1-f1-f3-f4,f3, xx,yy,zz,aa, yy)

    lower_cont = frac_lower * norm
    upper_cont = frac_upper * norm

    idx_j = numpy.arange(phi.shape[1])
    for ii in range(phi.shape[0]):
        for kk in range(phi.shape[2]):
            for ll in range(phi.shape[3]):
                phi_int = numpy.zeros((phi.shape[1], phi.shape[1]))
                phi_int[idx_j, lower_w_index[ii,:,kk,ll]] = lower_cont[ii,:,kk,ll]
                phi_int[idx_j, upper_w_index[ii,:,kk,ll]] = upper_cont[ii,:,kk,ll]
                phi[ii,:,kk,ll] = Numerics.trapz(phi_int, yy, axis=0)

    return phi

def phi_5D_admix_into_1(phi, f2,f3,f4,f5, xx,yy,zz,aa,bb):
    """
    Admix populations 2, 3, 4, and 5 into population 1.

    Alters phi in place and returns the new version.

    phi:      phi corresponding to original 5 populations.
    f2:       Fraction of updated population 1 to be derived from population 2. 
    f3:       Fraction of updated population 1 to be derived from population 3. 
    f4:       Fraction of updated population 1 to be derived from population 4. 
    f5:       Fraction of updated population 1 to be derived from population 5. 
              A fraction (1-f2-f3-f4-f5) will be derived from the original pop 1.
    xx,yy,zz,aa,bb: Mapping of points in phi to frequencies in populations 1,2,3,4, and 5.
    """
    lower_w_index, upper_w_index, frac_lower, frac_upper, norm \
            = _five_pop_admixture_intermediates(phi, 1-f2-f3-f4-f5,f2,f3,f4, xx,yy,zz,aa,bb, xx)

    lower_cont = frac_lower * norm
    upper_cont = frac_upper * norm

    idx_i = numpy.arange(phi.shape[0])
    for jj in range(phi.shape[1]):
        for kk in range(phi.shape[2]):
            for ll in range(phi.shape[3]):
                for mm in range(phi.shape[4]):
                    phi_int = numpy.zeros((phi.shape[0], phi.shape[0]))
                    phi_int[idx_i, lower_w_index[:,jj,kk,ll,mm]] = lower_cont[:,jj,kk,ll,mm]
                    phi_int[idx_i, upper_w_index[:,jj,kk,ll,mm]] = upper_cont[:,jj,kk,ll,mm]
                    phi[:,jj,kk,ll,mm] = Numerics.trapz(phi_int, xx, axis=0)

    return phi

def phi_5D_admix_into_2(phi, f1,f3,f4,f5, xx,yy,zz,aa,bb):
    """
    Admix populations 1, 3, 4, and 5 into population 2.

    Alters phi in place and returns the new version.

    phi:      phi corresponding to original 5 populations.
    f1:       Fraction of updated population 2 to be derived from population 1. 
    f3:       Fraction of updated population 2 to be derived from population 3. 
    f4:       Fraction of updated population 2 to be derived from population 4. 
    f5:       Fraction of updated population 2 to be derived from population 5. 
              A fraction (1-f1-f3-f4-f5) will be derived from the original pop 2.
    xx,yy,zz,aa,bb: Mapping of points in phi to frequencies in populations 1,2,3,4, and 5.
    """
    lower_w_index, upper_w_index, frac_lower, frac_upper, norm \
            = _five_pop_admixture_intermediates(phi, f1, 1-f1-f3-f4-f5,f3,f4, xx,yy,zz,aa,bb, xx)

    lower_cont = frac_lower * norm
    upper_cont = frac_upper * norm

    idx_j = numpy.arange(phi.shape[1])
    for ii in range(phi.shape[0]):
        for kk in range(phi.shape[2]):
            for ll in range(phi.shape[3]):
                for mm in range(phi.shape[4]):
                    phi_int = numpy.zeros((phi.shape[1], phi.shape[1]))
                    phi_int[idx_j, lower_w_index[ii,:,kk,ll,mm]] = lower_cont[ii,:,kk,ll,mm]
                    phi_int[idx_j, upper_w_index[ii,:,kk,ll,mm]] = upper_cont[ii,:,kk,ll,mm]
                    phi[ii,:,kk,ll,mm] = Numerics.trapz(phi_int, xx, axis=0)

    return phi

def phi_5D_admix_into_3(phi, f1,f2,f4,f5, xx,yy,zz,aa,bb):
    """
    Admix populations 1, 2, 4, and 5 into population 3.

    Alters phi in place and returns the new version.

    phi:      phi corresponding to original 5 populations.
    f1:       Fraction of updated population 3 to be derived from population 1. 
    f2:       Fraction of updated population 3 to be derived from population 2. 
    f4:       Fraction of updated population 3 to be derived from population 4. 
    f5:       Fraction of updated population 3 to be derived from population 5. 
              A fraction (1-f1-f2-f4-f5) will be derived from the original pop 3.
    xx,yy,zz,aa,bb: Mapping of points in phi to frequencies in populations 1,2,3,4, and 5.
    """
    lower_w_index, upper_w_index, frac_lower, frac_upper, norm \
            = _five_pop_admixture_intermediates(phi, f1, f2, 1-f1-f2-f4-f5,f4, xx,yy,zz,aa,bb, xx)

    lower_cont = frac_lower * norm
    upper_cont = frac_upper * norm

    idx_k = numpy.arange(phi.shape[2])
    for ii in range(phi.shape[0]):
        for jj in range(phi.shape[1]):
            for ll in range(phi.shape[3]):
                for mm in range(phi.shape[4]):
                    phi_int = numpy.zeros((phi.shape[2], phi.shape[2]))
                    phi_int[idx_k, lower_w_index[ii,jj,:,ll,mm]] = lower_cont[ii,jj,:,ll,mm]
                    phi_int[idx_k, upper_w_index[ii,jj,:,ll,mm]] = upper_cont[ii,jj,:,ll,mm]
                    phi[ii,jj,:,ll,mm] = Numerics.trapz(phi_int, xx, axis=0)

    return phi

def phi_5D_admix_into_4(phi, f1,f2,f3,f5, xx,yy,zz,aa,bb):
    """
    Admix populations 1, 2, 3, and 5 into population 4.

    Alters phi in place and returns the new version.

    phi:      phi corresponding to original 5 populations.
    f1:       Fraction of updated population 4 to be derived from population 1. 
    f2:       Fraction of updated population 4 to be derived from population 2. 
    f3:       Fraction of updated population 4 to be derived from population 3. 
    f5:       Fraction of updated population 4 to be derived from population 5. 
              A fraction (1-f1-f2-f3-f5) will be derived from the original pop 4.
    xx,yy,zz,aa,bb: Mapping of points in phi to frequencies in populations 1,2,3,4, and 5.
    """
    lower_w_index, upper_w_index, frac_lower, frac_upper, norm \
            = _five_pop_admixture_intermediates(phi, f1, f2, f3, 1-f1-f2-f3-f5, xx,yy,zz,aa,bb, xx)

    lower_cont = frac_lower * norm
    upper_cont = frac_upper * norm

    idx_l = numpy.arange(phi.shape[3])
    for ii in range(phi.shape[0]):
        for jj in range(phi.shape[1]):
            for kk in range(phi.shape[2]):
                for mm in range(phi.shape[4]):
                    phi_int = numpy.zeros((phi.shape[3], phi.shape[3]))
                    phi_int[idx_l, lower_w_index[ii,jj,kk,:,mm]] = lower_cont[ii,jj,kk,:,mm]
                    phi_int[idx_l, upper_w_index[ii,jj,kk,:,mm]] = upper_cont[ii,jj,kk,:,mm]
                    phi[ii,jj,kk,:,mm] = Numerics.trapz(phi_int, xx, axis=0)

    return phi

def phi_5D_admix_into_5(phi, f1,f2,f3,f4, xx,yy,zz,aa,bb):
    """
    Admix populations 1, 2, 3, and 4 into population 5.

    Alters phi in place and returns the new version.

    phi:      phi corresponding to original 5 populations.
    f1:       Fraction of updated population 5 to be derived from population 1. 
    f2:       Fraction of updated population 5 to be derived from population 2. 
    f3:       Fraction of updated population 5 to be derived from population 3. 
    f4:       Fraction of updated population 5 to be derived from population 3. 
              A fraction (1-f1-f2-f3-f4) will be derived from the original pop 5.
    xx,yy,zz,aa,bb: Mapping of points in phi to frequencies in populations 1,2,3,4, and 5.
    """
    lower_w_index, upper_w_index, frac_lower, frac_upper, norm \
            = _five_pop_admixture_intermediates(phi, f1, f2, f3, f4, xx,yy,zz,aa,bb, xx)

    lower_cont = frac_lower * norm
    upper_cont = frac_upper * norm

    idx_m = numpy.arange(phi.shape[4])
    for ii in range(phi.shape[0]):
        for jj in range(phi.shape[1]):
            for kk in range(phi.shape[2]):
                for ll in range(phi.shape[3]):
                    phi_int = numpy.zeros((phi.shape[4], phi.shape[4]))
                    phi_int[idx_m, lower_w_index[ii,jj,kk,ll,:]] = lower_cont[ii,jj,kk,ll,:]
                    phi_int[idx_m, upper_w_index[ii,jj,kk,ll,:]] = upper_cont[ii,jj,kk,ll,:]
                    phi[ii,jj,kk,ll,:] = Numerics.trapz(phi_int, xx, axis=0)

    return phi

def remove_pop(phi, xx, popnum):
    """
    Remove a population from phi.

    Returns new phi with one fewer population.

    phi: phi corresponding to original populations
    xx: Mapping of points in phi to frequencies in population to be removed
    popnum: Population number to remove, numbering from 1.
    """
    return Numerics.trapz(phi, xx, axis=popnum-1)

def filter_pops(phi, xx, tokeep):
    """
    Filter phi to keep only certain populations.

    Returns new phi with len(tokeep) populations.

    phi: phi corresponding to original populations
    xx: Mapping of points in phi to frequencies in population to be removed
    tokeep: List of population numbers to keep, numbering from 1.
    """
    toremove = list(range(1, phi.ndim+1))
    for pop_ii in tokeep:
        toremove.remove(pop_ii)
    for pop_ii in sorted(toremove)[::-1]:
        phi = remove_pop(phi, xx, pop_ii)
    return phi

def phi_1D_X(xx, nu=1.0, theta0=1.0, gamma=0, h=0.5, beta=1, alpha=1):
    """
    One-dimensional phi for a constant-sized population with genic selection.

    xx: one-dimensional grid of frequencies upon which phi is defined
    nu: size of this population, relative to the reference population size Nref.
    theta0: scaled mutation rate, equal to 4*Nref * u, where u is the mutation 
            event rate per generation for the simulated locus and Nref is the 
            reference population size.
    gamma: scaled selection coefficient, equal to 2*Nref * s, where s is the
           selective advantage.
    h: Dominance coefficient. If A is the selected allele, the aa has fitness 1,
       aA has fitness 1+2sh and AA has fitness 1+2s. Male carriers have fitness
       1+2s. h = 0.5 corresonds to genic selection.

    Returns a new phi array.
    """
    Kv = (2.*beta+4.)*(beta+1.)/(9.*beta)
    Km1 = 4./3. * gamma*(0.5+h)
    Km2 = 4./3.*gamma*(1.-2.*h)
    g1 = Km1/Kv
    g2 = Km2/Kv

    # First we evaluate the relevant integrals.
    ints = numpy.empty(len(xx))
    integrand = lambda xi: numpy.exp(-2*g1*xi - g2*xi**2)
    val, eps = scipy.integrate.quad(integrand, 0, 1)
    int0 = val
    for ii,q in enumerate(xx):
        val, eps = scipy.integrate.quad(integrand, q, 1)
        ints[ii] = val

    phi = numpy.exp(2*g1*xx + g2*xx**2)*ints/int0
    # Protect from division by zero errors
    if xx[0] == 0 and xx[-1] == 1:
        phi[1:-1] *= 1./(xx[1:-1]*(1-xx[1:-1]))
    else:
        phi *= 1./(xx*(1-xx))

    if xx[0] == 0:
        # Technically, phi diverges at 0. This fixes lets us do numerics
        # sensibly.
        phi[0] = phi[1]
    if xx[-1] == 1:
        # I used Mathematica to check that this was the proper limit.
        phi[-1] = 1./int0
    return phi * nu*theta0 * 1./Kv * 2./(1.+2.*beta)*(1./(1.+alpha) + beta)


def reorder_pops(phi, neworder):
    """
    Get Spectrum with populations in new order

    Returns new Spectrum with same number of populations, but in a different order

    neworder: Integer list defining new order of populations, indexing the orginal
              populations from 1. Must contain all integers from 1 to number of pops.
    """
    if sorted(neworder) != [_+1 for _ in range(phi.ndim)]:
        raise(ValueError("neworder argument misspecified"))
    newaxes = [_-1 for _ in neworder]
    phi = phi.transpose(newaxes)
    
    return phi

Functions

def check_xx(xx)

Check whether xx is monotonically increasing from 0 to 1.

Expand source code
def check_xx(xx):
    """
    Check whether xx is monotonically increasing from 0 to 1. 
    """
    if not xx[0] == 0 and xx[1] == 1:
        raise ValueError('Input xx argument does not run from 0 to 1.'
                         'Have you passed in an incorrect argument?')
    if not numpy.all(numpy.diff(xx) >= 0):
        raise ValueError('Input xx argument is not monotonically increasing. '
                         'Have you passed in an incorrect argument?')
def filter_pops(phi, xx, tokeep)

Filter phi to keep only certain populations.

Returns new phi with len(tokeep) populations.

phi: phi corresponding to original populations xx: Mapping of points in phi to frequencies in population to be removed tokeep: List of population numbers to keep, numbering from 1.

Expand source code
def filter_pops(phi, xx, tokeep):
    """
    Filter phi to keep only certain populations.

    Returns new phi with len(tokeep) populations.

    phi: phi corresponding to original populations
    xx: Mapping of points in phi to frequencies in population to be removed
    tokeep: List of population numbers to keep, numbering from 1.
    """
    toremove = list(range(1, phi.ndim+1))
    for pop_ii in tokeep:
        toremove.remove(pop_ii)
    for pop_ii in sorted(toremove)[::-1]:
        phi = remove_pop(phi, xx, pop_ii)
    return phi
def phi_1D(xx, nu=1.0, theta0=1.0, gamma=0, h=0.5, theta=None, beta=1)

One-dimensional phi for a constant-sized population with genic selection.

xx: one-dimensional grid of frequencies upon which phi is defined nu: size of this population, relative to the reference population size Nref. theta0: scaled mutation rate, equal to 4Nref * u, where u is the mutation event rate per generation for the simulated locus and Nref is the reference population size. gamma: scaled selection coefficient, equal to 2Nref * s, where s is the selective advantage. h: Dominance coefficient. If A is the selected allele, the aa has fitness 1, aA has fitness 1+2sh and AA has fitness 1+2s. h = 0.5 corresonds to genic selection. theta: deprecated in favor of distinct nu and theta0 arguments, for consistency with Integration functions.

Returns a new phi array.

Expand source code
def phi_1D(xx, nu=1.0, theta0=1.0, gamma=0, h=0.5, theta=None, beta=1):
    """
    One-dimensional phi for a constant-sized population with genic selection.

    xx: one-dimensional grid of frequencies upon which phi is defined
    nu: size of this population, relative to the reference population size Nref.
    theta0: scaled mutation rate, equal to 4*Nref * u, where u is the mutation 
            event rate per generation for the simulated locus and Nref is the 
            reference population size.
    gamma: scaled selection coefficient, equal to 2*Nref * s, where s is the
           selective advantage.
    h: Dominance coefficient. If A is the selected allele, the aa has fitness 1,
       aA has fitness 1+2sh and AA has fitness 1+2s. h = 0.5 corresonds to
       genic selection.
    theta: deprecated in favor of distinct nu and theta0 arguments, for 
           consistency with Integration functions.

    Returns a new phi array.
    """

    if theta is not None:
        raise ValueError('The parameter theta has been deprecated in favor of '
                         'parameters nu and theta0, for consistency with the '
                         'Integration functions.')

    if h == 0.5:
        return phi_1D_genic(xx, nu, theta0, gamma, beta=beta)

    # Eqn 1 from Williamson, Fledel-Alon, Bustamante _Genetics_ 168:463 (2004).

    # Modified to incorporate fact that for beta != 1, we get a term of 
    # 4*beta/(beta+1)^2 in V. This can be implemented by rescaling gamma
    # and rescaling the final phi.
    gamma = gamma * 4.*beta/(beta+1.)**2

    # Our final result is of the form 
    # exp(Q) * int_0_x exp(-Q) / int_0_1 exp(-Q)

    # For large negative gamma, exp(-Q) becomes numerically infinite.
    # To work around this, we can adjust Q in both the top and bottom
    # integrals by the same factor. We choose to make that factor the 
    # maximum of -Q, which is -2*gamma.
    Qadjust = 0
    # For negative gamma, the maximum of -Q is -2*gamma.
    if gamma < 0 and numpy.isinf(numpy.exp(-2*gamma)):
        Qadjust = -2*gamma

    # For large positive gamma, the prefactor exp(Q) becomes numerically
    # infinite, while the numerator becomes very small. To work around this,
    # we'll can pull the prefactor into the numerator integral.

    # Evaluate the denominator integral.
    integrand = lambda xi: numpy.exp(-4*gamma*h*xi - 2*gamma*(1-2*h)*xi**2
                                     - Qadjust)
    int0, eps = scipy.integrate.quad(integrand, 0, 1, epsabs=0,
                                     points=numpy.linspace(0,1,41))

    ints = numpy.empty(len(xx))
    # Evaluate the numerator integrals
    if gamma < 0:
        # In this case, the prefactor is not divergent, so we can evaluate
        # the numerator as before, using the Qadjust if necessary.
        for ii,q in enumerate(xx):
            val, eps = scipy.integrate.quad(integrand, q, 1, epsabs=0,
                                            points=numpy.linspace(q,1,41))
            ints[ii] = val
        phi = numpy.exp(4*gamma*h*xx + 2*gamma*(1-2*h)*xx**2)*ints/int0
    else:
        # In this case, the prefactor may be divergent, so we do the integral
        # with the prefactor pulled inside
        integrand = lambda xi, q: numpy.exp(-4*gamma*h*(xi-q) -
                                            2*gamma*(1-2*h)*(xi**2-q**2))
        for ii,q in enumerate(xx):
            val, eps = scipy.integrate.quad(integrand, q, 1, args=(q,))
            ints[ii] = val
        phi = ints/int0

    # Protect from division by zero errors
    phi[1:-1] *= 1./(xx[1:-1]*(1-xx[1:-1]))
    # Technically, phi diverges at 0. This kludge lets us do numerics
    # sensibly.
    phi[0] = phi[1]
    # I used Mathematica to calculate the proper limit for x goes to 1.
    # But if we've adjusted the denominator integrand, then that limit doesn't
    # hold. We only need to do that in cases of strong negative selection,
    # when phi near 1 should be almost zero anyways. So we'll just ensure
    # that it is at least monotonically decreasing.
    if Qadjust == 0:
        phi[-1] = 1./int0
    else:
        phi[-1] = min(phi[-1], phi[-2])

    return phi * nu*theta0 * 4.*beta/(beta+1.)**2
def phi_1D_X(xx, nu=1.0, theta0=1.0, gamma=0, h=0.5, beta=1, alpha=1)

One-dimensional phi for a constant-sized population with genic selection.

xx: one-dimensional grid of frequencies upon which phi is defined nu: size of this population, relative to the reference population size Nref. theta0: scaled mutation rate, equal to 4Nref * u, where u is the mutation event rate per generation for the simulated locus and Nref is the reference population size. gamma: scaled selection coefficient, equal to 2Nref * s, where s is the selective advantage. h: Dominance coefficient. If A is the selected allele, the aa has fitness 1, aA has fitness 1+2sh and AA has fitness 1+2s. Male carriers have fitness 1+2s. h = 0.5 corresonds to genic selection.

Returns a new phi array.

Expand source code
def phi_1D_X(xx, nu=1.0, theta0=1.0, gamma=0, h=0.5, beta=1, alpha=1):
    """
    One-dimensional phi for a constant-sized population with genic selection.

    xx: one-dimensional grid of frequencies upon which phi is defined
    nu: size of this population, relative to the reference population size Nref.
    theta0: scaled mutation rate, equal to 4*Nref * u, where u is the mutation 
            event rate per generation for the simulated locus and Nref is the 
            reference population size.
    gamma: scaled selection coefficient, equal to 2*Nref * s, where s is the
           selective advantage.
    h: Dominance coefficient. If A is the selected allele, the aa has fitness 1,
       aA has fitness 1+2sh and AA has fitness 1+2s. Male carriers have fitness
       1+2s. h = 0.5 corresonds to genic selection.

    Returns a new phi array.
    """
    Kv = (2.*beta+4.)*(beta+1.)/(9.*beta)
    Km1 = 4./3. * gamma*(0.5+h)
    Km2 = 4./3.*gamma*(1.-2.*h)
    g1 = Km1/Kv
    g2 = Km2/Kv

    # First we evaluate the relevant integrals.
    ints = numpy.empty(len(xx))
    integrand = lambda xi: numpy.exp(-2*g1*xi - g2*xi**2)
    val, eps = scipy.integrate.quad(integrand, 0, 1)
    int0 = val
    for ii,q in enumerate(xx):
        val, eps = scipy.integrate.quad(integrand, q, 1)
        ints[ii] = val

    phi = numpy.exp(2*g1*xx + g2*xx**2)*ints/int0
    # Protect from division by zero errors
    if xx[0] == 0 and xx[-1] == 1:
        phi[1:-1] *= 1./(xx[1:-1]*(1-xx[1:-1]))
    else:
        phi *= 1./(xx*(1-xx))

    if xx[0] == 0:
        # Technically, phi diverges at 0. This fixes lets us do numerics
        # sensibly.
        phi[0] = phi[1]
    if xx[-1] == 1:
        # I used Mathematica to check that this was the proper limit.
        phi[-1] = 1./int0
    return phi * nu*theta0 * 1./Kv * 2./(1.+2.*beta)*(1./(1.+alpha) + beta)
def phi_1D_genic(xx, nu=1.0, theta0=1.0, gamma=0, theta=None, beta=1)

One-dimensional phi for a constant-sized population with genic selection.

xx: one-dimensional grid of frequencies upon which phi is defined nu: size of this population, relative to the reference population size Nref. theta0: scaled mutation rate, equal to 4Nref * u, where u is the mutation event rate per generation for the simulated locus and Nref is the reference population size. gamma: scaled selection coefficient, equal to 2Nref * s, where s is the selective advantage. theta: deprecated in favor of distinct nu and theta0 arguments, for consistency with Integration functions.

Returns a new phi array.

Expand source code
def phi_1D_genic(xx, nu=1.0, theta0=1.0, gamma=0, theta=None, beta=1):
    """
    One-dimensional phi for a constant-sized population with genic selection.

    xx: one-dimensional grid of frequencies upon which phi is defined
    nu: size of this population, relative to the reference population size Nref.
    theta0: scaled mutation rate, equal to 4*Nref * u, where u is the mutation 
            event rate per generation for the simulated locus and Nref is the 
            reference population size.
    gamma: scaled selection coefficient, equal to 2*Nref * s, where s is the
           selective advantage.
    theta: deprecated in favor of distinct nu and theta0 arguments, for 
           consistency with Integration functions.

    Returns a new phi array.
    """

    if theta is not None:
        raise ValueError('The parameter theta has been deprecated in favor of '
                         'parameters nu and theta0, for consistency with the '
                         'Integration functions.')
    if gamma == 0:
        return phi_1D_snm(xx, nu, theta0, beta=beta)

    # Beta effectively re-scales gamma.
    gamma = gamma * 4.*beta/(beta+1.)**2

    exp = numpy.exp
    # Protect from warnings on division by zero
    if xx[0] == 0 and xx[-1] == 1:
        phi = 0*xx
        if gamma > -300:
            phi[1:-1] = 1./(xx[1:-1]*(1-xx[1:-1]))\
                    * (1-exp(-2*gamma*(1-xx[1:-1])))/(1-exp(-2*gamma))
        else:
            # Avoid overflow issues for very negative gammas
            phi[1:-1] = 1./(xx[1:-1]*(1-xx[1:-1])) * exp(2*gamma*xx[1:-1])
    else:
        if gamma > -300:
            phi = 1./(xx*(1-xx)) * (1-exp(-2*gamma*(1-xx)))/(1-exp(-2*gamma))
        else:
            phi = 1./(xx*(1-xx)) * exp(2*gamma*xx)

    if xx[0] == 0:
        phi[0] = phi[1]
    if xx[-1] == 1:
        if gamma < 300:
            limit = 2*gamma * exp(2*gamma)/(exp(2*gamma)-1)
        else:
            limit = 2*gamma
        phi[-1] = limit
    return phi * nu*theta0 * 4.*beta/(beta+1.)**2
def phi_1D_snm(xx, nu=1.0, theta0=1.0, theta=None, beta=1)

Standard neutral one-dimensional probability density.

xx: one-dimensional grid of frequencies upon which phi is defined nu: size of this population, relative to the reference population size Nref. theta0: scaled mutation rate, equal to 4*Nref * u, where u is the mutation event rate per generation for the simulated locus and Nref is the reference population size. theta: deprecated in favor of distinct nu and theta0 arguments, for consistency with Integration functions.

Returns a new phi array.

Expand source code
def phi_1D_snm(xx, nu=1.0, theta0=1.0, theta=None, beta=1):
    """
    Standard neutral one-dimensional probability density.

    xx: one-dimensional grid of frequencies upon which phi is defined
    nu: size of this population, relative to the reference population size Nref.
    theta0: scaled mutation rate, equal to 4*Nref * u, where u is the mutation 
            event rate per generation for the simulated locus and Nref is the 
            reference population size.
    theta: deprecated in favor of distinct nu and theta0 arguments, for 
           consistency with Integration functions.

    Returns a new phi array.
    """

    if theta is not None:
        raise ValueError('The parameter theta has been deprecated in favor of '
                         'parameters nu and theta0, for consistency with the '
                         'Integration functions.')
    # Protect from division by zero errors
    if xx[0] == 0:
        phi = 0*xx
        phi[1:] = nu*theta0/xx[1:]
        phi[0] = phi[1]
    else:
        phi = nu*theta0/xx
    return phi * 4.*beta/(beta+1.)**2
def phi_1D_to_2D(xx, phi_1D)

Implement a one-to-two population split.

xx: one-dimensional grid of frequencies upon which phi is defined phi1D: initial probability density

Returns a new two-dimensional phi array.

Expand source code
def phi_1D_to_2D(xx, phi_1D):
    """
    Implement a one-to-two population split.

    xx: one-dimensional grid of frequencies upon which phi is defined
    phi1D: initial probability density

    Returns a new two-dimensional phi array.
    """
    check_xx(xx)

    pts = len(xx)
    phi_2D = numpy.zeros((pts, pts))
    for ii in range(1, pts-1):
        phi_2D[ii,ii] = phi_1D[ii] * 2/(xx[ii+1]-xx[ii-1])
    return phi_2D
def phi_2D_admix_1_into_2(phi, f, xx, yy)

Admix population 1 into population 2.

Alters phi in place and returns the new version.

phi: phi corresponding to original 2 populations f: Fraction of updated population 2 to be derived from population 1. (A fraction 1-f will be derived from the original population 2.) xx,yy: Mapping of points in phi to frequencies in populations 1 and 2.

Expand source code
def phi_2D_admix_1_into_2(phi, f, xx,yy):
    """
    Admix population 1 into population 2.

    Alters phi in place and returns the new version.

    phi:   phi corresponding to original 2 populations
    f:     Fraction of updated population 2 to be derived from population 1. 
             (A fraction 1-f will be derived from the original population 2.)
    xx,yy: Mapping of points in phi to frequencies in populations 1 and 2.
    """
    # This is just like the the split_admix situation, but we're splitting into
    # a population with zz=yy. We could do this by creating a xx by yy by yy
    # array, then integrating out the second population. That's a big waste of
    # memory, however.
    lower_z_index, upper_z_index, frac_lower, frac_upper, norm \
            = _two_pop_admixture_intermediates(phi, f, xx,yy,yy)

    # Basically, we're splitting into a third zz population, then integrating
    # over yy to be left with the two populations we care about.
    lower_cont = frac_lower*norm
    upper_cont = frac_upper*norm
    idx_j = numpy.arange(phi.shape[1])
    for ii in range(phi.shape[0]):
        phi_int = numpy.zeros((phi.shape[1], phi.shape[1]))
        # Use fancy indexing to avoid the commented out loop.
        #for jj in range(len(yy)):
        #    phi_int[jj, upper_z_index[ii,jj]] += frac_upper[ii,jj]*norm[ii,jj]
        #    phi_int[jj, lower_z_index[ii,jj]] += frac_lower[ii,jj]*norm[ii,jj]
        phi_int[idx_j, lower_z_index[ii]] = lower_cont[ii]
        phi_int[idx_j, upper_z_index[ii]] += upper_cont[ii]
        phi[ii] = Numerics.trapz(phi_int, yy, axis=0)

    return phi
def phi_2D_admix_2_into_1(phi, f, xx, yy)

Admix population 2 into population 1.

Alters phi in place and returns the new version.

phi: phi corresponding to original 2 populations f: Fraction of updated population 1 to be derived from population 2. (A fraction 1-f will be derived from the original population 1.) xx,yy: Mapping of points in phi to frequencies in populations 1 and 2.

Expand source code
def phi_2D_admix_2_into_1(phi, f, xx,yy):
    """
    Admix population 2 into population 1.

    Alters phi in place and returns the new version.

    phi:   phi corresponding to original 2 populations
    f:     Fraction of updated population 1 to be derived from population 2. 
             (A fraction 1-f will be derived from the original population 1.)
    xx,yy: Mapping of points in phi to frequencies in populations 1 and 2.
    """
    # Note that it's 1-f here since f now denotes the fraction coming from
    # population 2.
    lower_z_index, upper_z_index, frac_lower, frac_upper, norm \
            = _two_pop_admixture_intermediates(phi, 1-f, xx,yy,xx)

    idx_i = numpy.arange(phi.shape[0])
    lower_cont = frac_lower*norm
    upper_cont = frac_upper*norm
    for jj in range(len(yy)):
        phi_int = numpy.zeros((len(xx), len(xx)))
        phi_int[idx_i, lower_z_index[:,jj]] = lower_cont[:,jj]
        phi_int[idx_i, upper_z_index[:,jj]] = upper_cont[:,jj]
        phi[:,jj] = Numerics.trapz(phi_int, xx, axis=0)

    return phi
def phi_2D_to_3D(phi, f1, xx, yy, zz)

Create population 3 admixed from populations 1 and 2.

Returns a 3D sfs of shape (len(xx),len(yy),len(zz))

phi: phi corresponding to original 2 populations f1: Fraction of population 3 derived from population 1. (A fraction 1-f1 will be derived from population 2.) xx,yy: Mapping of points in phi to frequencies in populations 1 and 2. zz: Frequency mapping that will be used along population 3 axis.

Expand source code
def phi_2D_to_3D_admix(phi, f1, xx,yy,zz):
    """
    Create population 3 admixed from populations 1 and 2.

    Returns a 3D sfs of shape (len(xx),len(yy),len(zz))

    phi:   phi corresponding to original 2 populations
    f1:     Fraction of population 3 derived from population 1. (A fraction 1-f1
             will be derived from population 2.)
    xx,yy: Mapping of points in phi to frequencies in populations 1 and 2.
    zz:    Frequency mapping that will be used along population 3 axis.
    """
    lower_z_index, upper_z_index, frac_lower, frac_upper, norm \
            = _two_pop_admixture_intermediates(phi, f1, xx,yy,zz)


    # Assemble our result.
    # This uses numpy's fancy indexing. It is much, much faster than an
    # explicit loop.
    # See the numpy-discussion post "Numpy Advanced Indexing Question" by
    # Robert Kern on July 16, 2008
    # http://projects.scipy.org/pipermail/numpy-discussion/2008-July/035776.html
    idx_i = numpy.arange(len(xx))[:,numpy.newaxis]
    idx_j = numpy.arange(len(yy))[numpy.newaxis,:]

    phi_3D = numpy.zeros((len(xx), len(yy), len(zz)))
    phi_3D[idx_i, idx_j, lower_z_index] = frac_lower*norm
    phi_3D[idx_i, idx_j, upper_z_index] += frac_upper*norm

    return phi_3D
def phi_2D_to_3D_admix(phi, f1, xx, yy, zz)

Create population 3 admixed from populations 1 and 2.

Returns a 3D sfs of shape (len(xx),len(yy),len(zz))

phi: phi corresponding to original 2 populations f1: Fraction of population 3 derived from population 1. (A fraction 1-f1 will be derived from population 2.) xx,yy: Mapping of points in phi to frequencies in populations 1 and 2. zz: Frequency mapping that will be used along population 3 axis.

Expand source code
def phi_2D_to_3D_admix(phi, f1, xx,yy,zz):
    """
    Create population 3 admixed from populations 1 and 2.

    Returns a 3D sfs of shape (len(xx),len(yy),len(zz))

    phi:   phi corresponding to original 2 populations
    f1:     Fraction of population 3 derived from population 1. (A fraction 1-f1
             will be derived from population 2.)
    xx,yy: Mapping of points in phi to frequencies in populations 1 and 2.
    zz:    Frequency mapping that will be used along population 3 axis.
    """
    lower_z_index, upper_z_index, frac_lower, frac_upper, norm \
            = _two_pop_admixture_intermediates(phi, f1, xx,yy,zz)


    # Assemble our result.
    # This uses numpy's fancy indexing. It is much, much faster than an
    # explicit loop.
    # See the numpy-discussion post "Numpy Advanced Indexing Question" by
    # Robert Kern on July 16, 2008
    # http://projects.scipy.org/pipermail/numpy-discussion/2008-July/035776.html
    idx_i = numpy.arange(len(xx))[:,numpy.newaxis]
    idx_j = numpy.arange(len(yy))[numpy.newaxis,:]

    phi_3D = numpy.zeros((len(xx), len(yy), len(zz)))
    phi_3D[idx_i, idx_j, lower_z_index] = frac_lower*norm
    phi_3D[idx_i, idx_j, upper_z_index] += frac_upper*norm

    return phi_3D
def phi_2D_to_3D_split_1(xx, phi_2D)

Split population 1 into populations 1 and 3.

xx: one-dimensional grid of frequencies upon which phi is defined phi2D: initial probability density

Returns a new three-dimensional phi array.

Expand source code
def phi_2D_to_3D_split_1(xx, phi_2D):
    """
    Split population 1 into populations 1 and 3.

    xx: one-dimensional grid of frequencies upon which phi is defined
    phi2D: initial probability density

    Returns a new three-dimensional phi array.
    """
    check_xx(xx)

    return phi_2D_to_3D_admix(phi_2D,1,xx,xx,xx)
def phi_2D_to_3D_split_2(xx, phi_2D)

Split population 2 into populations 2 and 3.

xx: one-dimensional grid of frequencies upon which phi is defined phi2D: initial probability density

Returns a new three-dimensional phi array.

Expand source code
def phi_2D_to_3D_split_2(xx, phi_2D):
    """
    Split population 2 into populations 2 and 3.

    xx: one-dimensional grid of frequencies upon which phi is defined
    phi2D: initial probability density

    Returns a new three-dimensional phi array.
    """
    check_xx(xx)

    return phi_2D_to_3D_admix(phi_2D,0,xx,xx,xx)
def phi_3D_admix_1_and_2_into_3(phi, f1, f2, xx, yy, zz)

Admix populations 1 and 2 into population 3.

Alters phi in place and returns the new version.

phi: phi corresponding to original 3 populations. f1: Fraction of updated population 3 to be derived from population 1. f2: Fraction of updated population 3 to be derived from population 2. A fraction (1-f1-f2) will be derived from the original pop 3. xx,yy,zz: Mapping of points in phi to frequencies in populations 1,2 and 3.

Expand source code
def phi_3D_admix_1_and_2_into_3(phi, f1,f2, xx,yy,zz):
    """
    Admix populations 1 and 2 into population 3.

    Alters phi in place and returns the new version.

    phi:      phi corresponding to original 3 populations.
    f1:       Fraction of updated population 3 to be derived from population 1. 
    f2:       Fraction of updated population 3 to be derived from population 2. 
              A fraction (1-f1-f2) will be derived from the original pop 3.
    xx,yy,zz: Mapping of points in phi to frequencies in populations 1,2 and 3.
    """
    lower_w_index, upper_w_index, frac_lower, frac_upper, norm \
            = _three_pop_admixture_intermediates(phi, f1,f2, xx,yy,zz, zz)

    lower_cont = frac_lower * norm
    upper_cont = frac_upper * norm

    # Basically, we're splitting into a fourth ww population, then integrating
    # over zz to be left with the two populations we care about.
    idx_k = numpy.arange(phi.shape[2])
    for ii in range(phi.shape[0]):
        for jj in range(phi.shape[1]):
            phi_int = numpy.zeros((phi.shape[2], phi.shape[2]))
            phi_int[idx_k, lower_w_index[ii,jj]] = lower_cont[ii,jj]
            phi_int[idx_k, upper_w_index[ii,jj]] = upper_cont[ii,jj]
            phi[ii,jj] = Numerics.trapz(phi_int, zz, axis=0)

    return phi
def phi_3D_admix_1_and_3_into_2(phi, f1, f3, xx, yy, zz)

Admix populations 1 and 3 into population 2.

Alters phi in place and returns the new version.

phi: phi corresponding to original 3 populations. f1: Fraction of updated population 2 to be derived from population 1. f3: Fraction of updated population 2 to be derived from population 3. A fraction (1-f1-f3) will be derived from the original pop 2. xx,yy,zz: Mapping of points in phi to frequencies in populations 1,2 and 3.

Expand source code
def phi_3D_admix_1_and_3_into_2(phi, f1,f3, xx,yy,zz):
    """
    Admix populations 1 and 3 into population 2.

    Alters phi in place and returns the new version.

    phi:      phi corresponding to original 3 populations.
    f1:       Fraction of updated population 2 to be derived from population 1. 
    f3:       Fraction of updated population 2 to be derived from population 3. 
              A fraction (1-f1-f3) will be derived from the original pop 2.
    xx,yy,zz: Mapping of points in phi to frequencies in populations 1,2 and 3.
    """
    lower_w_index, upper_w_index, frac_lower, frac_upper, norm \
            = _three_pop_admixture_intermediates(phi, f1,1-f1-f3, xx,yy,zz, yy)

    lower_cont = frac_lower * norm
    upper_cont = frac_upper * norm

    # Basically, we're splitting into a fourth ww population, then integrating
    # over yy to be left with the two populations we care about.
    idx_j = numpy.arange(phi.shape[1])
    for ii in range(phi.shape[0]):
        for kk in range(phi.shape[2]):
            phi_int = numpy.zeros((phi.shape[1], phi.shape[1]))
            phi_int[idx_j, lower_w_index[ii,:,kk]] = lower_cont[ii,:,kk]
            phi_int[idx_j, upper_w_index[ii,:,kk]] = upper_cont[ii,:,kk]
            phi[ii,:,kk] = Numerics.trapz(phi_int, yy, axis=0)

    return phi
def phi_3D_admix_2_and_3_into_1(phi, f2, f3, xx, yy, zz)

Admix populations 2 and 3 into population 1.

Alters phi in place and returns the new version.

phi: phi corresponding to original 3 populations. f2: Fraction of updated population 1 to be derived from population 2. f3: Fraction of updated population 1 to be derived from population 3. A fraction (1-f2-f3) will be derived from the original pop 1. xx,yy,zz: Mapping of points in phi to frequencies in populations 1,2 and 3.

Expand source code
def phi_3D_admix_2_and_3_into_1(phi, f2,f3, xx,yy,zz):
    """
    Admix populations 2 and 3 into population 1.

    Alters phi in place and returns the new version.

    phi:      phi corresponding to original 3 populations.
    f2:       Fraction of updated population 1 to be derived from population 2. 
    f3:       Fraction of updated population 1 to be derived from population 3. 
              A fraction (1-f2-f3) will be derived from the original pop 1.
    xx,yy,zz: Mapping of points in phi to frequencies in populations 1,2 and 3.
    """
    lower_w_index, upper_w_index, frac_lower, frac_upper, norm \
            = _three_pop_admixture_intermediates(phi, 1-f2-f3,f2, xx,yy,zz, xx)

    lower_cont = frac_lower * norm
    upper_cont = frac_upper * norm

    # Basically, we're splitting into a fourth ww population, then integrating
    # over yy to be left with the two populations we care about.
    idx_i = numpy.arange(phi.shape[0])
    for jj in range(phi.shape[1]):
        for kk in range(phi.shape[2]):
            phi_int = numpy.zeros((phi.shape[0], phi.shape[0]))
            phi_int[idx_i, lower_w_index[:,jj,kk]] = lower_cont[:,jj,kk]
            phi_int[idx_i, upper_w_index[:,jj,kk]] = upper_cont[:,jj,kk]
            phi[:,jj,kk] = Numerics.trapz(phi_int, xx, axis=0)

    return phi
def phi_3D_to_4D(phi, f1, f2, xx, yy, zz, aa)

Create population 4 from populations 1, 2, and 3.

Returns a 4D sfs of shape (len(xx),len(yy),len(zz),len(aa))

phi: phi corresponding to original 3 populations f1: Fraction of population 4 derived from population 1. f2: Fraction of population 4 derived from population 2. (A fraction 1-f1-f2 will be derived from population 3.) xx,yy,zz: Mapping of points in phi to frequencies in populations 1, 2, and 3. aa: Frequency mapping that will be used along population 4 axis.

Expand source code
def phi_3D_to_4D(phi, f1,f2, xx,yy,zz,aa):
    """
    Create population 4 from populations 1, 2, and 3.

    Returns a 4D sfs of shape (len(xx),len(yy),len(zz),len(aa))

    phi:   phi corresponding to original 3 populations
    f1:    Fraction of population 4 derived from population 1.
    f2:    Fraction of population 4 derived from population 2.
           (A fraction 1-f1-f2 will be derived from population 3.)
    xx,yy,zz: Mapping of points in phi to frequencies in populations 1, 2, and 3.
    aa:    Frequency mapping that will be used along population 4 axis.
    """
    lower_z_index, upper_z_index, frac_lower, frac_upper, norm \
            = _three_pop_admixture_intermediates(phi, f1, f2, xx,yy,zz, aa)

    # Assemble our result.
    # This uses numpy's fancy indexing. It is much, much faster than an
    # explicit loop.
    # See the numpy-discussion post "Numpy Advanced Indexing Question" by
    # Robert Kern on July 16, 2008
    # http://projects.scipy.org/pipermail/numpy-discussion/2008-July/035776.html
    idx_i = numpy.arange(len(xx))[:,nuax,nuax]
    idx_j = numpy.arange(len(yy))[nuax,:,nuax]
    idx_k = numpy.arange(len(zz))[nuax,nuax,:]

    phi_4D = numpy.zeros((len(xx), len(yy), len(zz), len(aa)))
    phi_4D[idx_i, idx_j, idx_k, lower_z_index] = frac_lower*norm
    phi_4D[idx_i, idx_j, idx_k, upper_z_index] += frac_upper*norm

    return phi_4D
def phi_4D_admix_into_1(phi, f2, f3, f4, xx, yy, zz, aa)

Admix populations 2, 3, and 4 into population 1.

Alters phi in place and returns the new version.

phi: phi corresponding to original 4 populations. f2: Fraction of updated population 1 to be derived from population 2. f3: Fraction of updated population 1 to be derived from population 3. f4: Fraction of updated population 1 to be derived from population 4. A fraction (1-f2-f3-f4) will be derived from the original pop 1. xx,yy,zz,aa: Mapping of points in phi to frequencies in populations 1,2,3, and 4.

Expand source code
def phi_4D_admix_into_1(phi, f2,f3,f4, xx,yy,zz,aa):
    """
    Admix populations 2, 3, and 4 into population 1.

    Alters phi in place and returns the new version.

    phi:      phi corresponding to original 4 populations.
    f2:       Fraction of updated population 1 to be derived from population 2. 
    f3:       Fraction of updated population 1 to be derived from population 3. 
    f4:       Fraction of updated population 1 to be derived from population 4. 
              A fraction (1-f2-f3-f4) will be derived from the original pop 1.
    xx,yy,zz,aa: Mapping of points in phi to frequencies in populations 1,2,3, and 4.
    """
    lower_w_index, upper_w_index, frac_lower, frac_upper, norm \
            = _four_pop_admixture_intermediates(phi, 1-f2-f3-f4,f2,f3, xx,yy,zz,aa, xx)

    lower_cont = frac_lower * norm
    upper_cont = frac_upper * norm

    # Basically, we're splitting into a fifth bb population, then integrating
    # over xx to be left with the two populations we care about.
    idx_i = numpy.arange(phi.shape[0])
    for jj in range(phi.shape[1]):
        for kk in range(phi.shape[2]):
            for ll in range(phi.shape[3]):
                phi_int = numpy.zeros((phi.shape[0], phi.shape[0]))
                phi_int[idx_i, lower_w_index[:,jj,kk,ll]] = lower_cont[:,jj,kk,ll]
                phi_int[idx_i, upper_w_index[:,jj,kk,ll]] = upper_cont[:,jj,kk,ll]
                phi[:,jj,kk,ll] = Numerics.trapz(phi_int, xx, axis=0)

    return phi
def phi_4D_admix_into_2(phi, f1, f3, f4, xx, yy, zz, aa)

Admix populations 1,3, and 4 into population 2.

Alters phi in place and returns the new version.

phi: phi corresponding to original 4 populations. f1: Fraction of updated population 2 to be derived from population 1. f3: Fraction of updated population 2 to be derived from population 3. f4: Fraction of updated population 2 to be derived from population 4. A fraction (1-f1-f3-f4) will be derived from the original pop 2. xx,yy,zz,aa: Mapping of points in phi to frequencies in populations 1,2,3 and 4.

Expand source code
def phi_4D_admix_into_2(phi, f1,f3,f4, xx,yy,zz,aa):
    """
    Admix populations 1,3, and 4 into population 2.

    Alters phi in place and returns the new version.

    phi:      phi corresponding to original 4 populations.
    f1:       Fraction of updated population 2 to be derived from population 1. 
    f3:       Fraction of updated population 2 to be derived from population 3. 
    f4:       Fraction of updated population 2 to be derived from population 4. 
              A fraction (1-f1-f3-f4) will be derived from the original pop 2.
    xx,yy,zz,aa: Mapping of points in phi to frequencies in populations 1,2,3 and 4.
    """
    lower_w_index, upper_w_index, frac_lower, frac_upper, norm \
            = _four_pop_admixture_intermediates(phi, f1,1-f1-f3-f4,f3, xx,yy,zz,aa, yy)

    lower_cont = frac_lower * norm
    upper_cont = frac_upper * norm

    idx_j = numpy.arange(phi.shape[1])
    for ii in range(phi.shape[0]):
        for kk in range(phi.shape[2]):
            for ll in range(phi.shape[3]):
                phi_int = numpy.zeros((phi.shape[1], phi.shape[1]))
                phi_int[idx_j, lower_w_index[ii,:,kk,ll]] = lower_cont[ii,:,kk,ll]
                phi_int[idx_j, upper_w_index[ii,:,kk,ll]] = upper_cont[ii,:,kk,ll]
                phi[ii,:,kk,ll] = Numerics.trapz(phi_int, yy, axis=0)

    return phi
def phi_4D_admix_into_3(phi, f1, f2, f4, xx, yy, zz, aa)

Admix populations 1,2, and 4 into population 3.

Alters phi in place and returns the new version.

phi: phi corresponding to original 4 populations. f1: Fraction of updated population 3 to be derived from population 1. f2: Fraction of updated population 3 to be derived from population 2. f4: Fraction of updated population 3 to be derived from population 4. A fraction (1-f1-f2-f4) will be derived from the original pop 3. xx,yy,zz,aa: Mapping of points in phi to frequencies in populations 1,2,3 and 4.

Expand source code
def phi_4D_admix_into_3(phi, f1,f2,f4, xx,yy,zz,aa):
    """
    Admix populations 1,2, and 4 into population 3.

    Alters phi in place and returns the new version.

    phi:      phi corresponding to original 4 populations.
    f1:       Fraction of updated population 3 to be derived from population 1. 
    f2:       Fraction of updated population 3 to be derived from population 2. 
    f4:       Fraction of updated population 3 to be derived from population 4. 
              A fraction (1-f1-f2-f4) will be derived from the original pop 3.
    xx,yy,zz,aa: Mapping of points in phi to frequencies in populations 1,2,3 and 4.
    """
    lower_w_index, upper_w_index, frac_lower, frac_upper, norm \
            = _four_pop_admixture_intermediates(phi, f1,f2,1-f1-f2-f4, xx,yy,zz,aa, yy)

    lower_cont = frac_lower * norm
    upper_cont = frac_upper * norm

    idx_k = numpy.arange(phi.shape[2])
    for ii in range(phi.shape[0]):
        for jj in range(phi.shape[1]):
            for ll in range(phi.shape[3]):
                phi_int = numpy.zeros((phi.shape[2], phi.shape[2]))
                phi_int[idx_k, lower_w_index[ii,jj,:,ll]] = lower_cont[ii,jj,:,ll]
                phi_int[idx_k, upper_w_index[ii,jj,:,ll]] = upper_cont[ii,jj,:,ll]
                phi[ii,jj,:,ll] = Numerics.trapz(phi_int, zz, axis=0)

    return phi
def phi_4D_admix_into_4(phi, f1, f2, f3, xx, yy, zz, aa)

Admix populations 1,2, and 3 into population 4.

Alters phi in place and returns the new version.

phi: phi corresponding to original 4 populations. f1: Fraction of updated population 4 to be derived from population 1. f2: Fraction of updated population 4 to be derived from population 2. f3: Fraction of updated population 4 to be derived from population 3. A fraction (1-f1-f2-f3) will be derived from the original pop 4. xx,yy,zz,aa: Mapping of points in phi to frequencies in populations 1,2,3 and 4.

Expand source code
def phi_4D_admix_into_4(phi, f1,f2,f3, xx,yy,zz,aa):
    """
    Admix populations 1,2, and 3 into population 4.

    Alters phi in place and returns the new version.

    phi:      phi corresponding to original 4 populations.
    f1:       Fraction of updated population 4 to be derived from population 1. 
    f2:       Fraction of updated population 4 to be derived from population 2. 
    f3:       Fraction of updated population 4 to be derived from population 3. 
              A fraction (1-f1-f2-f3) will be derived from the original pop 4.
    xx,yy,zz,aa: Mapping of points in phi to frequencies in populations 1,2,3 and 4.
    """
    lower_w_index, upper_w_index, frac_lower, frac_upper, norm \
            = _four_pop_admixture_intermediates(phi, f1,f2,f3, xx,yy,zz,aa, yy)

    lower_cont = frac_lower * norm
    upper_cont = frac_upper * norm

    idx_l = numpy.arange(phi.shape[3])
    for ii in range(phi.shape[0]):
        for jj in range(phi.shape[1]):
            for kk in range(phi.shape[2]):
                phi_int = numpy.zeros((phi.shape[3], phi.shape[3]))
                phi_int[idx_l, lower_w_index[ii,jj,kk,:]] = lower_cont[ii,jj,kk,:]
                phi_int[idx_l, upper_w_index[ii,jj,kk,:]] = upper_cont[ii,jj,kk,:]
                phi[ii,jj,kk,:] = Numerics.trapz(phi_int, aa, axis=0)

    return phi
def phi_4D_to_5D(phi, f1, f2, f3, xx, yy, zz, aa, bb)

Create population 5 from populations 1, 2, 3, and 4.

Returns a 5D sfs of shape (len(xx),len(yy),len(zz),len(aa),len(bb))

phi: phi corresponding to original 3 populations f1: Fraction of population 4 derived from population 1. f2: Fraction of population 4 derived from population 2. f4: Fraction of population 4 derived from population 3. (A fraction 1-f1-f2-f3 will be derived from population 4.) xx,yy,zz,aa: Mapping of points in phi to frequencies in populations 1, 2, 3, 4. bb: Frequency mapping that will be used along population bb axis.

Expand source code
def phi_4D_to_5D(phi, f1,f2,f3, xx,yy,zz,aa,bb):
    """
    Create population 5 from populations 1, 2, 3, and 4.

    Returns a 5D sfs of shape (len(xx),len(yy),len(zz),len(aa),len(bb))

    phi:   phi corresponding to original 3 populations
    f1:    Fraction of population 4 derived from population 1.
    f2:    Fraction of population 4 derived from population 2.
    f4:    Fraction of population 4 derived from population 3.
           (A fraction 1-f1-f2-f3 will be derived from population 4.)
    xx,yy,zz,aa: Mapping of points in phi to frequencies in populations 1, 2, 3, 4.
    bb:    Frequency mapping that will be used along population bb axis.
    """
    lower_z_index, upper_z_index, frac_lower, frac_upper, norm \
            = _four_pop_admixture_intermediates(phi, f1,f2,f3, xx,yy,zz,aa, bb)

    # Assemble our result.
    # This uses numpy's fancy indexing. It is much, much faster than an
    # explicit loop.
    # See the numpy-discussion post "Numpy Advanced Indexing Question" by
    # Robert Kern on July 16, 2008
    # http://projects.scipy.org/pipermail/numpy-discussion/2008-July/035776.html
    idx_i = numpy.arange(len(xx))[:,nuax,nuax,nuax]
    idx_j = numpy.arange(len(yy))[nuax,:,nuax,nuax]
    idx_k = numpy.arange(len(zz))[nuax,nuax,:,nuax]
    idx_l = numpy.arange(len(aa))[nuax,nuax,nuax,:]

    phi_5D = numpy.zeros((len(xx), len(yy), len(zz), len(aa), len(bb)))
    phi_5D[idx_i, idx_j, idx_k, idx_l, lower_z_index] = frac_lower*norm
    phi_5D[idx_i, idx_j, idx_k, idx_l, upper_z_index] += frac_upper*norm

    return phi_5D
def phi_5D_admix_into_1(phi, f2, f3, f4, f5, xx, yy, zz, aa, bb)

Admix populations 2, 3, 4, and 5 into population 1.

Alters phi in place and returns the new version.

phi: phi corresponding to original 5 populations. f2: Fraction of updated population 1 to be derived from population 2. f3: Fraction of updated population 1 to be derived from population 3. f4: Fraction of updated population 1 to be derived from population 4. f5: Fraction of updated population 1 to be derived from population 5. A fraction (1-f2-f3-f4-f5) will be derived from the original pop 1. xx,yy,zz,aa,bb: Mapping of points in phi to frequencies in populations 1,2,3,4, and 5.

Expand source code
def phi_5D_admix_into_1(phi, f2,f3,f4,f5, xx,yy,zz,aa,bb):
    """
    Admix populations 2, 3, 4, and 5 into population 1.

    Alters phi in place and returns the new version.

    phi:      phi corresponding to original 5 populations.
    f2:       Fraction of updated population 1 to be derived from population 2. 
    f3:       Fraction of updated population 1 to be derived from population 3. 
    f4:       Fraction of updated population 1 to be derived from population 4. 
    f5:       Fraction of updated population 1 to be derived from population 5. 
              A fraction (1-f2-f3-f4-f5) will be derived from the original pop 1.
    xx,yy,zz,aa,bb: Mapping of points in phi to frequencies in populations 1,2,3,4, and 5.
    """
    lower_w_index, upper_w_index, frac_lower, frac_upper, norm \
            = _five_pop_admixture_intermediates(phi, 1-f2-f3-f4-f5,f2,f3,f4, xx,yy,zz,aa,bb, xx)

    lower_cont = frac_lower * norm
    upper_cont = frac_upper * norm

    idx_i = numpy.arange(phi.shape[0])
    for jj in range(phi.shape[1]):
        for kk in range(phi.shape[2]):
            for ll in range(phi.shape[3]):
                for mm in range(phi.shape[4]):
                    phi_int = numpy.zeros((phi.shape[0], phi.shape[0]))
                    phi_int[idx_i, lower_w_index[:,jj,kk,ll,mm]] = lower_cont[:,jj,kk,ll,mm]
                    phi_int[idx_i, upper_w_index[:,jj,kk,ll,mm]] = upper_cont[:,jj,kk,ll,mm]
                    phi[:,jj,kk,ll,mm] = Numerics.trapz(phi_int, xx, axis=0)

    return phi
def phi_5D_admix_into_2(phi, f1, f3, f4, f5, xx, yy, zz, aa, bb)

Admix populations 1, 3, 4, and 5 into population 2.

Alters phi in place and returns the new version.

phi: phi corresponding to original 5 populations. f1: Fraction of updated population 2 to be derived from population 1. f3: Fraction of updated population 2 to be derived from population 3. f4: Fraction of updated population 2 to be derived from population 4. f5: Fraction of updated population 2 to be derived from population 5. A fraction (1-f1-f3-f4-f5) will be derived from the original pop 2. xx,yy,zz,aa,bb: Mapping of points in phi to frequencies in populations 1,2,3,4, and 5.

Expand source code
def phi_5D_admix_into_2(phi, f1,f3,f4,f5, xx,yy,zz,aa,bb):
    """
    Admix populations 1, 3, 4, and 5 into population 2.

    Alters phi in place and returns the new version.

    phi:      phi corresponding to original 5 populations.
    f1:       Fraction of updated population 2 to be derived from population 1. 
    f3:       Fraction of updated population 2 to be derived from population 3. 
    f4:       Fraction of updated population 2 to be derived from population 4. 
    f5:       Fraction of updated population 2 to be derived from population 5. 
              A fraction (1-f1-f3-f4-f5) will be derived from the original pop 2.
    xx,yy,zz,aa,bb: Mapping of points in phi to frequencies in populations 1,2,3,4, and 5.
    """
    lower_w_index, upper_w_index, frac_lower, frac_upper, norm \
            = _five_pop_admixture_intermediates(phi, f1, 1-f1-f3-f4-f5,f3,f4, xx,yy,zz,aa,bb, xx)

    lower_cont = frac_lower * norm
    upper_cont = frac_upper * norm

    idx_j = numpy.arange(phi.shape[1])
    for ii in range(phi.shape[0]):
        for kk in range(phi.shape[2]):
            for ll in range(phi.shape[3]):
                for mm in range(phi.shape[4]):
                    phi_int = numpy.zeros((phi.shape[1], phi.shape[1]))
                    phi_int[idx_j, lower_w_index[ii,:,kk,ll,mm]] = lower_cont[ii,:,kk,ll,mm]
                    phi_int[idx_j, upper_w_index[ii,:,kk,ll,mm]] = upper_cont[ii,:,kk,ll,mm]
                    phi[ii,:,kk,ll,mm] = Numerics.trapz(phi_int, xx, axis=0)

    return phi
def phi_5D_admix_into_3(phi, f1, f2, f4, f5, xx, yy, zz, aa, bb)

Admix populations 1, 2, 4, and 5 into population 3.

Alters phi in place and returns the new version.

phi: phi corresponding to original 5 populations. f1: Fraction of updated population 3 to be derived from population 1. f2: Fraction of updated population 3 to be derived from population 2. f4: Fraction of updated population 3 to be derived from population 4. f5: Fraction of updated population 3 to be derived from population 5. A fraction (1-f1-f2-f4-f5) will be derived from the original pop 3. xx,yy,zz,aa,bb: Mapping of points in phi to frequencies in populations 1,2,3,4, and 5.

Expand source code
def phi_5D_admix_into_3(phi, f1,f2,f4,f5, xx,yy,zz,aa,bb):
    """
    Admix populations 1, 2, 4, and 5 into population 3.

    Alters phi in place and returns the new version.

    phi:      phi corresponding to original 5 populations.
    f1:       Fraction of updated population 3 to be derived from population 1. 
    f2:       Fraction of updated population 3 to be derived from population 2. 
    f4:       Fraction of updated population 3 to be derived from population 4. 
    f5:       Fraction of updated population 3 to be derived from population 5. 
              A fraction (1-f1-f2-f4-f5) will be derived from the original pop 3.
    xx,yy,zz,aa,bb: Mapping of points in phi to frequencies in populations 1,2,3,4, and 5.
    """
    lower_w_index, upper_w_index, frac_lower, frac_upper, norm \
            = _five_pop_admixture_intermediates(phi, f1, f2, 1-f1-f2-f4-f5,f4, xx,yy,zz,aa,bb, xx)

    lower_cont = frac_lower * norm
    upper_cont = frac_upper * norm

    idx_k = numpy.arange(phi.shape[2])
    for ii in range(phi.shape[0]):
        for jj in range(phi.shape[1]):
            for ll in range(phi.shape[3]):
                for mm in range(phi.shape[4]):
                    phi_int = numpy.zeros((phi.shape[2], phi.shape[2]))
                    phi_int[idx_k, lower_w_index[ii,jj,:,ll,mm]] = lower_cont[ii,jj,:,ll,mm]
                    phi_int[idx_k, upper_w_index[ii,jj,:,ll,mm]] = upper_cont[ii,jj,:,ll,mm]
                    phi[ii,jj,:,ll,mm] = Numerics.trapz(phi_int, xx, axis=0)

    return phi
def phi_5D_admix_into_4(phi, f1, f2, f3, f5, xx, yy, zz, aa, bb)

Admix populations 1, 2, 3, and 5 into population 4.

Alters phi in place and returns the new version.

phi: phi corresponding to original 5 populations. f1: Fraction of updated population 4 to be derived from population 1. f2: Fraction of updated population 4 to be derived from population 2. f3: Fraction of updated population 4 to be derived from population 3. f5: Fraction of updated population 4 to be derived from population 5. A fraction (1-f1-f2-f3-f5) will be derived from the original pop 4. xx,yy,zz,aa,bb: Mapping of points in phi to frequencies in populations 1,2,3,4, and 5.

Expand source code
def phi_5D_admix_into_4(phi, f1,f2,f3,f5, xx,yy,zz,aa,bb):
    """
    Admix populations 1, 2, 3, and 5 into population 4.

    Alters phi in place and returns the new version.

    phi:      phi corresponding to original 5 populations.
    f1:       Fraction of updated population 4 to be derived from population 1. 
    f2:       Fraction of updated population 4 to be derived from population 2. 
    f3:       Fraction of updated population 4 to be derived from population 3. 
    f5:       Fraction of updated population 4 to be derived from population 5. 
              A fraction (1-f1-f2-f3-f5) will be derived from the original pop 4.
    xx,yy,zz,aa,bb: Mapping of points in phi to frequencies in populations 1,2,3,4, and 5.
    """
    lower_w_index, upper_w_index, frac_lower, frac_upper, norm \
            = _five_pop_admixture_intermediates(phi, f1, f2, f3, 1-f1-f2-f3-f5, xx,yy,zz,aa,bb, xx)

    lower_cont = frac_lower * norm
    upper_cont = frac_upper * norm

    idx_l = numpy.arange(phi.shape[3])
    for ii in range(phi.shape[0]):
        for jj in range(phi.shape[1]):
            for kk in range(phi.shape[2]):
                for mm in range(phi.shape[4]):
                    phi_int = numpy.zeros((phi.shape[3], phi.shape[3]))
                    phi_int[idx_l, lower_w_index[ii,jj,kk,:,mm]] = lower_cont[ii,jj,kk,:,mm]
                    phi_int[idx_l, upper_w_index[ii,jj,kk,:,mm]] = upper_cont[ii,jj,kk,:,mm]
                    phi[ii,jj,kk,:,mm] = Numerics.trapz(phi_int, xx, axis=0)

    return phi
def phi_5D_admix_into_5(phi, f1, f2, f3, f4, xx, yy, zz, aa, bb)

Admix populations 1, 2, 3, and 4 into population 5.

Alters phi in place and returns the new version.

phi: phi corresponding to original 5 populations. f1: Fraction of updated population 5 to be derived from population 1. f2: Fraction of updated population 5 to be derived from population 2. f3: Fraction of updated population 5 to be derived from population 3. f4: Fraction of updated population 5 to be derived from population 3. A fraction (1-f1-f2-f3-f4) will be derived from the original pop 5. xx,yy,zz,aa,bb: Mapping of points in phi to frequencies in populations 1,2,3,4, and 5.

Expand source code
def phi_5D_admix_into_5(phi, f1,f2,f3,f4, xx,yy,zz,aa,bb):
    """
    Admix populations 1, 2, 3, and 4 into population 5.

    Alters phi in place and returns the new version.

    phi:      phi corresponding to original 5 populations.
    f1:       Fraction of updated population 5 to be derived from population 1. 
    f2:       Fraction of updated population 5 to be derived from population 2. 
    f3:       Fraction of updated population 5 to be derived from population 3. 
    f4:       Fraction of updated population 5 to be derived from population 3. 
              A fraction (1-f1-f2-f3-f4) will be derived from the original pop 5.
    xx,yy,zz,aa,bb: Mapping of points in phi to frequencies in populations 1,2,3,4, and 5.
    """
    lower_w_index, upper_w_index, frac_lower, frac_upper, norm \
            = _five_pop_admixture_intermediates(phi, f1, f2, f3, f4, xx,yy,zz,aa,bb, xx)

    lower_cont = frac_lower * norm
    upper_cont = frac_upper * norm

    idx_m = numpy.arange(phi.shape[4])
    for ii in range(phi.shape[0]):
        for jj in range(phi.shape[1]):
            for kk in range(phi.shape[2]):
                for ll in range(phi.shape[3]):
                    phi_int = numpy.zeros((phi.shape[4], phi.shape[4]))
                    phi_int[idx_m, lower_w_index[ii,jj,kk,ll,:]] = lower_cont[ii,jj,kk,ll,:]
                    phi_int[idx_m, upper_w_index[ii,jj,kk,ll,:]] = upper_cont[ii,jj,kk,ll,:]
                    phi[ii,jj,kk,ll,:] = Numerics.trapz(phi_int, xx, axis=0)

    return phi
def remove_pop(phi, xx, popnum)

Remove a population from phi.

Returns new phi with one fewer population.

phi: phi corresponding to original populations xx: Mapping of points in phi to frequencies in population to be removed popnum: Population number to remove, numbering from 1.

Expand source code
def remove_pop(phi, xx, popnum):
    """
    Remove a population from phi.

    Returns new phi with one fewer population.

    phi: phi corresponding to original populations
    xx: Mapping of points in phi to frequencies in population to be removed
    popnum: Population number to remove, numbering from 1.
    """
    return Numerics.trapz(phi, xx, axis=popnum-1)
def reorder_pops(phi, neworder)

Get Spectrum with populations in new order

Returns new Spectrum with same number of populations, but in a different order

neworder: Integer list defining new order of populations, indexing the orginal populations from 1. Must contain all integers from 1 to number of pops.

Expand source code
def reorder_pops(phi, neworder):
    """
    Get Spectrum with populations in new order

    Returns new Spectrum with same number of populations, but in a different order

    neworder: Integer list defining new order of populations, indexing the orginal
              populations from 1. Must contain all integers from 1 to number of pops.
    """
    if sorted(neworder) != [_+1 for _ in range(phi.ndim)]:
        raise(ValueError("neworder argument misspecified"))
    newaxes = [_-1 for _ in neworder]
    phi = phi.transpose(newaxes)
    
    return phi