def compute_luminosity_function(z, m, M, m_max, archive_file):
    """Compute the luminosity function and archive in the given file.

    If the file exists, then the saved results are returned.
    """
    Mmax = m_max - (m - M)
    zmax = z_mu(m_max - M)

    if not os.path.exists(archive_file):
        print ("- computing bootstrapped luminosity function ", "for %i points" % len(z))

        zbins = np.linspace(0.08, 0.12, 21)
        Mbins = np.linspace(-24, -20.2, 21)
        dist_z, err_z, dist_M, err_M = bootstrap_Cminus(z, M, zmax, Mmax, zbins, Mbins, Nbootstraps=20, normalize=True)
        np.savez(archive_file, zbins=zbins, dist_z=dist_z, err_z=err_z, Mbins=Mbins, dist_M=dist_M, err_M=err_M)
    else:
        print "- using precomputed bootstrapped luminosity function results"
        archive = np.load(archive_file)
        zbins = archive["zbins"]
        dist_z = archive["dist_z"]
        err_z = archive["err_z"]
        Mbins = archive["Mbins"]
        dist_M = archive["dist_M"]
        err_M = archive["err_M"]

    return zbins, dist_z, err_z, Mbins, dist_M, err_M
Exemple #2
0
def compute_luminosity_function(z, m, M, m_max, archive_file):
    """Compute the luminosity function and archive in the given file.

    If the file exists, then the saved results are returned.
    """
    Mmax = m_max - (m - M)
    zmax = z_mu(m_max - M)

    if not os.path.exists(archive_file):
        print("- computing bootstrapped luminosity function ",
              "for {0} points".format(len(z)))

        zbins = np.linspace(0.08, 0.12, 21)
        Mbins = np.linspace(-24, -20.2, 21)
        dist_z, err_z, dist_M, err_M = bootstrap_Cminus(z, M, zmax, Mmax,
                                                        zbins, Mbins,
                                                        Nbootstraps=20,
                                                        normalize=True)
        np.savez(archive_file,
                 zbins=zbins, dist_z=dist_z, err_z=err_z,
                 Mbins=Mbins, dist_M=dist_M, err_M=err_M)
    else:
        print("- using precomputed bootstrapped luminosity function results")
        archive = np.load(archive_file)
        zbins = archive['zbins']
        dist_z = archive['dist_z']
        err_z = archive['err_z']
        Mbins = archive['Mbins']
        dist_M = archive['dist_M']
        err_M = archive['err_M']

    return zbins, dist_z, err_z, Mbins, dist_M, err_M
Exemple #3
0
def compute_luminosity_function(z,
                                m,
                                M,
                                m_max,
                                archive_file,
                                Nbootstraps=20,
                                bin_type='freedman',
                                z_mu=None):
    ''' Compute the luminosity function and archive in the given file. If the
    file exists, then the saved results are returned.

    Parameters
    ----------
    z : array-like
        Redshifts of galaxies.
    m : array-like
        Apparent magnitudes of galaxies.
    M : array-like
        Absolute magnitudes of galaxies.
    m_max : float
        Maximum sensitivity in apparent magnitudes.
    archive_file : str
        Name of file saved for bootstrapping.
    Nbootstraps : int
        Number of bootstraps to perform.
    bin_type : str
        Type of binning method to use. Options are 'freedman' or 'scott'
    z_mu : func
        Distance modulus as a function of redshift.

    Returns
    -------
    zbins : array-like
        Redshift bin centers.
    dist_z : array-like
        Redshift count distribution.
    err_z : array-like
        Redshift count errors.
    Mbins : array-like
        Absolute magnitude bin centers.
    dist_M : array-like
        Absolute magnitude count distribution.
    err_M : array-like
        Absolute magnitude count errors.

    '''

    from astroML.density_estimation import scotts_bin_width as scott_bin
    from astroML.density_estimation import freedman_bin_width as free_bin
    from astroML.lumfunc import binned_Cminus, bootstrap_Cminus

    Mmax = m_max - (m - M)
    zmax = z_mu(m_max - M)

    if not os.path.exists(archive_file):
        print("- computing bootstrapped luminosity function ",
              "for %i points" % len(z))

        if bin_type == 'freedman':
            zbin_width, zbins = free_bin(z, return_bins=True)
            Mbin_width, Mbins = free_bin(M, return_bins=True)
        elif bin_type == 'scott':
            zbin_width, zbins = scott_bin(z, return_bins=True)
            Mbin_width, Mbins = scott_bin(M, return_bins=True)

        dist_z, err_z, dist_M, err_M = bootstrap_Cminus(z,
                                                        M,
                                                        zmax,
                                                        Mmax,
                                                        zbins,
                                                        Mbins,
                                                        Nbootstraps=20,
                                                        normalize=True)
        np.savez(archive_file,
                 zbins=zbins,
                 dist_z=dist_z,
                 err_z=err_z,
                 Mbins=Mbins,
                 dist_M=dist_M,
                 err_M=err_M)
    else:
        print "- using precomputed bootstrapped luminosity function results"
        archive = np.load(archive_file)
        zbins = archive['zbins']
        dist_z = archive['dist_z']
        err_z = archive['err_z']
        Mbins = archive['Mbins']
        dist_M = archive['dist_M']
        err_M = archive['err_M']

    return zbins, dist_z, err_z, Mbins, dist_M, err_M
Exemple #4
0
ymax[ymax > 1] = 1  # cutoff at y=1

# truncate the data
flag = (x < xmax) & (y < ymax)
x = x[flag]
y = y[flag]
xmax = xmax[flag]
ymax = ymax[flag]

x_fit = np.linspace(0, 1, 21)
y_fit = np.linspace(0, 1, 21)

#------------------------------------------------------------
# compute the Cminus distributions (with bootstrap)
x_dist, dx_dist, y_dist, dy_dist = bootstrap_Cminus(x, y, xmax, ymax,
                                                    x_fit, y_fit,
                                                    Nbootstraps=20,
                                                    normalize=True)

x_mid = 0.5 * (x_fit[1:] + x_fit[:-1])
y_mid = 0.5 * (y_fit[1:] + y_fit[:-1])

#------------------------------------------------------------
# Plot the results
fig = plt.figure(figsize=(5, 2))
fig.subplots_adjust(bottom=0.2, top=0.95,
                    left=0.1, right=0.92, wspace=0.25)

# First subplot is the true & inferred 1D distributions
ax = fig.add_subplot(121)
ax.plot(x_mid, x_pdf.pdf(x_mid), '-k', label='$p(x)$')
ax.plot(y_mid, y_pdf.pdf(y_mid), '--k', label='$p(y)$')
Exemple #5
0
# truncate the data
flag = (x < xmax) & (y < ymax)
x = x[flag]
y = y[flag]
xmax = xmax[flag]
ymax = ymax[flag]

x_fit = np.linspace(0, 1, 21)
y_fit = np.linspace(0, 1, 21)

#------------------------------------------------------------
# compute the Cminus distributions (with bootstrap)
x_dist, dx_dist, y_dist, dy_dist = bootstrap_Cminus(x,
                                                    y,
                                                    xmax,
                                                    ymax,
                                                    x_fit,
                                                    y_fit,
                                                    Nbootstraps=20,
                                                    normalize=True)

x_mid = 0.5 * (x_fit[1:] + x_fit[:-1])
y_mid = 0.5 * (y_fit[1:] + y_fit[:-1])

#------------------------------------------------------------
# Plot the results
fig = plt.figure(figsize=(5, 2))
fig.subplots_adjust(bottom=0.2, top=0.95, left=0.1, right=0.92, wspace=0.25)

# First subplot is the true & inferred 1D distributions
ax = fig.add_subplot(121)
ax.plot(x_mid, x_pdf.pdf(x_mid), '-k', label='$p(x)$')
Exemple #6
0
def doAstr511HW2(truth, obs, diskLF, haloLF):
    """Make a six-panel plot illustrating Astr511 homework #2"""

    # a) Define a ``gold parallax sample" by piObs/piErr > 10 and compare distance 
    #    modulus from the parallax measurement (D/kpc=1 milliarcsec/piObs) 
    #    to the distance modulus determined from r and Mr listed in the ``truth" file 
    piObs = obs[14]
    piErr = obs[15]
    piSNR = piObs / piErr
    D = np.where(piObs>0, 1/piObs, 1.0e6) 
    mask = (piSNR>10) 
    Dok = D[mask]
    DM = 5*np.log10(Dok/0.01)
    DMtrue = truth[4] - truth[8]
    diffDM = DM - DMtrue[mask]

    if (0):
        print '  mean =', np.mean(diffDM)
        print '   rms =', np.std(diffDM)
        med, sigG = median_sigmaG(diffDM)
        print 'median =', med
        print 'sigmaG =', sigG

    ### make vectors of x=distance modulus and y=absolute magnitude to run Cminus
    # sample faint limit
    rFaintLimit = 24.5
    # use either true distances, or estimated from parallax
    if (0):
        # using "truth" values of distance modulus, DM, to compute LF
        Mr = truth[8]
        rTrue = truth[4]
        DMtrue = rTrue - Mr 
        # type could be estimated from colors...
        typeWD = truth[13]
        # here the sample is defined using true apparent magnitude
        maskC = (rTrue < rFaintLimit) & (typeWD == 1) 
        xC = DMtrue[maskC]
        yC = Mr[maskC]
    else:
        # observed apparent magnitude
        rObs = obs[6]
        if (1):
            # fit a 4-th order polynomial to Mr(gr) relation (high-SNR sample)
            A, B, C, D, E = fitPhotomParallax(truth, obs)
            # and now apply
            gr = obs[4]-obs[6]
            MrPP = WDppFit(gr, A, B, C, D, E)
        else:
            # testing here: take true Mr...
            MrPP = truth[8]
        # here the sample is defined using observed apparent magnitude
        DMobs = rObs - MrPP
        # type could be estimated from colors...
        typeWD = truth[13]
        maskC = (rObs < rFaintLimit)  & (typeWD == 1) 
        xC = DMobs[maskC]
        yC = MrPP[maskC]


    ## the sample boundary is a simple linear function because using DM
    xCmax = rFaintLimit - yC
    yCmax = rFaintLimit - xC 
    ## split disk and halo, assume populations are known
    pop = truth[14]
    popC = pop[maskC]
    maskD = (popC==1) 
    xCD = xC[maskD]
    yCD = yC[maskD]
    xCmaxD = xCmax[maskD]
    yCmaxD = yCmax[maskD]
    maskH = (popC==2) 
    xCH = xC[maskH]
    yCH = yC[maskH]
    xCmaxH = xCmax[maskH]
    yCmaxH = yCmax[maskH]

    print 'observed sample size of xCD (disk)', np.size(xCD)
    print 'observed sample size of xCH (halo)', np.size(xCH)

    ### and now corrected Mr distributions: application of the Cminus method
    ## note that Cminus returns **cumulative** distribution functions
    ## however, bootstrap_Cminus (which calls Cminus) then does conversion 
    ## and returns **differential** distribution functions 

    #### this is where we run Cminus method (or use stored results) ####
    archive_file = 'CminusZI.npz'
    if not os.path.exists(archive_file):
        print ("- computing bootstrapped differential luminosity function ")
        #------------------------------------------------------------
        # aux arrays 
        x_fit = np.linspace(0, 15, 42)
        y_fit = np.linspace(10, 17, 42)
        #------------------------------------------------------------
        ## compute the Cminus distributions (with bootstrap)
        # disk subsample 
        print ("Disk sample with %i points" % len(xCD))
        xD_dist, dxD_dist, yD_dist, dyD_dist = bootstrap_Cminus(xCD, yCD, xCmaxD, yCmaxD,
                                                    x_fit, y_fit, Nbootstraps=20, normalize=True)
        # halo subsample 
        print ("Halo sample with %i points" % len(xCH))
        xH_dist, dxH_dist, yH_dist, dyH_dist = bootstrap_Cminus(xCH, yCH, xCmaxH, yCmaxH,
                                                    x_fit, y_fit, Nbootstraps=20, normalize=True)
        np.savez(archive_file,
                 x_fit=x_fit, xD_dist=xD_dist, dxD_dist=dxD_dist,
                 y_fit=y_fit, yD_dist=yD_dist, dyD_dist=dyD_dist)
        archive_file = 'CminusZIhalo.npz'
        np.savez(archive_file,
                 xH_dist=xH_dist, dxH_dist=dxH_dist,
                 yH_dist=yH_dist, dyH_dist=dyH_dist)

        #------------------------------------------------------------
    else:
        print "- using ZI's precomputed bootstrapped luminosity function results"
        # disk
        archive_file = 'CminusZI.npz'
        archive = np.load(archive_file)
        x_fit = archive['x_fit']
        xD_dist = archive['xD_dist']
        dxD_dist = archive['dxD_dist']
        y_fit = archive['y_fit']
        yD_dist = archive['yD_dist']
        dyD_dist = archive['dyD_dist']
        # halo
        archive_file = 'CminusZIhalo.npz'
        archive = np.load(archive_file)
        xH_dist = archive['xH_dist']
        dxH_dist = archive['dxH_dist']
        yH_dist = archive['yH_dist']
        dyH_dist = archive['dyH_dist']
        
    # bin mid positions for plotting the results:
    x_mid = 0.5 * (x_fit[1:] + x_fit[:-1])
    y_mid = 0.5 * (y_fit[1:] + y_fit[:-1])


    ### determine normalization of luminosity functions and number density distributions
    ## we need to renormalize LFs: what we have are differential distributions
    ## normalized so that their cumulative LF is 1 at the last point;
    ## we need to account for the fractional sky coverage and for flux selection effect
    ## and do a bit of extrapolation to our position (DM=0) at the end
    # 1) the fraction of covered sky: we know that the sample is defined by bGal > bMin
    bMin = 60.0  # in degrees
    # in steradians, given b > 60deg, dOmega = 2*pi*[1-cos(90-60)] = 0.8418 sr
    dOmega = 2*math.radians(180)*(1-math.cos(math.radians(90-bMin)))

    # 2) renormalization constants due to selection effects
    # these correspond to the left-hand side of eq. 12 from lecture 4 (page 26)
    DMmax0 = 11.0
    MrMax0 = rFaintLimit - DMmax0
    NmaskD = ((xCD < DMmax0) & (yCD < MrMax0)) 
    Ndisk = np.sum(NmaskD)
    NmaskH = ((xCH < DMmax0) & (yCH < MrMax0)) 
    Nhalo = np.sum(NmaskH)
    print 'Size of volume-limited samples D and H:', Ndisk, ' and ', Nhalo,  '(DM<', DMmax0, ', Mr<', MrMax0, ')'
       ## Size of volume-limited samples D and H: 49791  and  2065 (DM< 11.0 , Mr< 13.5 )

    # now we need to find what fraction of the total sample (without flux selection effects)
    # is found for x < DMmax0 and y < MrMax0. To do so, we need to integrate differential 
    # distributions returned by bootstrap_Cminus up to x=DMmax0 and y=MrMax0
    # (NB: we could have EASILY read off these from the original cumulative distributions returned
    #      from Cminus, but unfortunately bootstrap_Cminus returns only differential distributions;
    #      a design bug??) 
    # n.b. verified that all four distributions integrate to 1
    XfracD = IntDiffDistr(x_fit, xD_dist, DMmax0)
    YfracD = IntDiffDistr(y_fit, yD_dist, MrMax0)
    XfracH = IntDiffDistr(x_fit, xH_dist, DMmax0)
    YfracH = IntDiffDistr(y_fit, yH_dist, MrMax0)
    # and use eq. 12 to obtain normalization constant C for both subsamples
    Cdisk = Ndisk / (XfracD * YfracD)
    Chalo = Nhalo / (XfracH * YfracH)
    print 'Cdisk = ', Cdisk, 'Chalo = ', Chalo
       ## Cdisk = 608,082  Chalo = 565,598

    ## number density normalization
    # need to go from per DM to per pc:  dDM/dV = 5 / [ln(10) * dOmega * D^3] 
    Dpc = 10 * 10**(0.2*x_mid)
    # the units are "number of stars per pc^3" (of all absolute magnitudes Mr) 
    # note that here we assume isotropic sky distribution (division by dOmega!) 
    rhoD = Cdisk * 5/np.log(10)/dOmega / Dpc**3 * xD_dist
    rhoH = Chalo * 5/np.log(10)/dOmega / Dpc**3 * xH_dist
    # same fractional errors remain after renormalization 
    dxD_distN = rhoD * dxD_dist / xD_dist
    dxH_distN = rhoH * dxH_dist / xH_dist

    # differential luminosity function: per pc3 and mag, ** at the solar position **
    # to get proper normalization, we simply multiply differential distributions (whose
    # integrals are already normalized to 1) by the local number density distributions)
    # Therefore, we need to extrapolate rhoD and rhoH to D=0
    # and then use these values as normalization factors
    # The choice of extrapolation function is tricky (much more so than in case of
    # interpolation): based on our "prior knowledge", we choose
    # for disk: fit exponential disk, ln(rho) = ln(rho0) - H*D for 100 < D/pc < 500
    xFitExpDisk = Dpc[(Dpc > 100) & (Dpc < 500)]
    yFitExpDisk = np.log(rhoD[(Dpc > 100) & (Dpc < 500)])
    A, H = curve_fit(ExpDiskDensity, xFitExpDisk, yFitExpDisk)[0]
    rhoD0 = np.exp(A) 
    print 'rhoD0', rhoD0, ' scale height H (pc) =', 1/H
    # and for halo: simply take the median value for 500 < D/pc < 2000
    rhoH0 = np.median(rhoH[(Dpc > 500) & (Dpc < 2000)])
    print 'rhoH0', rhoH0
    # and now renormalize (rhoD0, rhoH0 = 0.0047 and 0.0000217) 
    # the units are "number of stars per pc^3 AND per mag" (at D=0)
    yD_distN = yD_dist * rhoD0
    yH_distN = yH_dist * rhoH0 
    # same fractional errors remain after renormalization 
    dyD_distN = yD_distN * dyD_dist / yD_dist
    dyH_distN = yH_distN * dyH_dist / yH_dist


    ### PLOTTING ###
    plot_kwargs = dict(color='k', linestyle='none', marker='.', markersize=1)
    plt.subplots_adjust(bottom=0.09, top=0.96, left=0.09, right=0.96, wspace=0.31, hspace=0.32)

    # plot the distribution of the distance modulus difference and compute its median
    # and root-mean-square scatter (hint: beware of outliers and clip at 3sigma!)
    hist, bins = np.histogram(diffDM, bins=50)    ## N.B. diffDM defined at the top
    center = (bins[:-1]+bins[1:])/2
    ax1 = plt.subplot(3,2,1)
    ax1.plot(center, hist, drawstyle='steps')   
    ax1.set_xlim(-1, 1)
    ax1.set_xlabel(r'$\mathrm{\Delta DM (mag)}$')
    ax1.set_ylabel(r'$\mathrm{dN/d(\Delta DM)}$')
    ax1.set_title('parallax SNR>10')
    ax1.plot(label=r'mpj legend')

    plt.text(0.25, 0.9,
         r'$\bar{x}=-0.023$',
         fontsize=12, bbox=dict(ec='k', fc='w', alpha=0.9),
         ha='center', va='center', transform=plt.gca().transAxes)
    plt.text(0.75, 0.9,
         r'$med.=-0.035$',
         fontsize=12, bbox=dict(ec='k', fc='w', alpha=0.9),
         ha='center', va='center', transform=plt.gca().transAxes)
    plt.text(0.2, 0.7,
         r'$\sigma=0.158$',
         fontsize=12, bbox=dict(ec='k', fc='w', alpha=0.9),
         ha='center', va='center', transform=plt.gca().transAxes)
    plt.text(0.75, 0.7,
         r'$\sigma_G=0.138$',
         fontsize=12, bbox=dict(ec='k', fc='w', alpha=0.9),
         ha='center', va='center', transform=plt.gca().transAxes)


    # plot uncorrected Mr vs. DM for disk
    ax2 = plt.subplot(3,2,2)
    ax2.set_xlim(4, 15)
    ax2.set_ylim(17, 10)
    ax2.set_xlabel(r'$\mathrm{DM}$')
    ax2.set_ylabel(r'$\mathrm{M_{r}}$')
    scatter_contour(xCD, yCD, threshold=20, log_counts=True, ax=ax2,
                histogram2d_args=dict(bins=40),
                plot_args=dict(marker='.', linestyle='none',
                               markersize=1, color='black'),
                contour_args=dict(cmap=plt.cm.bone))
    # ax2.plot(xCmaxD, yCD, color='blue')
    plt.text(0.75, 0.20,
         r'$disk$',
         fontsize=22, bbox=dict(ec='k', fc='w', alpha=0.3),
         ha='center', va='center', transform=plt.gca().transAxes)


    # plot uncorrected Mr vs. DM for halo
    ax3 = plt.subplot(3,2,3)
    ax3.set_xlim(4, 15)
    ax3.set_ylim(17, 10)
    ax3.set_xlabel(r'$\mathrm{DM}$')
    ax3.set_ylabel(r'$\mathrm{M_{r}}$')
    scatter_contour(xCH, yCH, threshold=20, log_counts=True, ax=ax3,
                histogram2d_args=dict(bins=40),
                plot_args=dict(marker='.', linestyle='none',
                               markersize=1, color='black'),
                contour_args=dict(cmap=plt.cm.bone))
    # ax3.plot(xCH, yCmaxH, '.k', markersize=0.1, color='blue')
    plt.text(0.75, 0.20,
         r'$halo$',
         fontsize=22, bbox=dict(ec='k', fc='w', alpha=0.3),
         ha='center', va='center', transform=plt.gca().transAxes)


    # uncorrected Mr distributions 
    ax4 = plt.subplot(3,2,4)
    ax4.hist(yCD, bins=30, log=True, histtype='step', color='red')
    ax4.hist(yCH, bins=30, log=True, histtype='step', color='blue')
    ax4.set_xlim(10, 17)
    ax4.set_ylim(1, 50000)
    ax4.set_xlabel(r'$\mathrm{M_r}$')
    ax4.set_ylabel(r'$\mathrm{dN/dM_r}$')
    plt.text(0.52, 0.20,
         r'$uncorrected \, M_r \, distribution$',
         fontsize=12, bbox=dict(ec='k', fc='w', alpha=0.3),
         ha='center', va='center', transform=plt.gca().transAxes)


    ## now plot the two remaining panels: corrected rho and Mr distributions
    # density distributions
    ax5 = plt.subplot(325, yscale='log')
    ax5.errorbar(Dpc, rhoD, dxD_distN, fmt='^k', ecolor='k', lw=1, color='red', label='disk')
    ax5.errorbar(Dpc, rhoH, dxH_distN, fmt='^k', ecolor='k', lw=1, color='blue', label='halo')

    ax5.set_xlim(0, 5000)
    ax5.set_ylim(0.000001, 0.01)
    ax5.set_xlabel(r'$\mathrm{D (pc)}$')
    ax5.set_ylabel(r'$\mathrm{\rho(D)} (pc^{-3}) $')
    ax5.legend(loc='upper right')

    # luminosity functions
    ax6 = plt.subplot(326, yscale='log')
    ax6.errorbar(y_mid, yD_distN, dyD_distN, fmt='^k', ecolor='k', lw=1, color='red')
    ax6.errorbar(y_mid, yH_distN, dyH_distN, fmt='^k', ecolor='k', lw=1, color='blue')

    # true luminosity functions
    MbinD = diskLF[0]
    psiD = diskLF[1] 
    MbinH = haloLF[0]
    psiH = haloLF[1] / 200
    ax6.plot(MbinD, psiD, color='red')
    ax6.plot(MbinH, psiH, color='blue')
 
    plt.text(0.62, 0.15,
         r'$\Psi \, in \, pc^{-3} mag^{-1}$',
         fontsize=12, bbox=dict(ec='k', fc='w', alpha=0.3),
         ha='center', va='center', transform=plt.gca().transAxes)

    ax6.set_xlim(10, 17)
    ax6.set_xlabel(r'$\mathrm{M_r}$')
    ax6.set_ylabel(r'$\Psi(M_r)=\mathrm{dN^2/dV \,dM_r}$')

    plt.savefig('Astr511HW2.png')
    plt.show() 
def compute_luminosity_function(z, m, M, m_max, archive_file, Nbootstraps=20,
        bin_type='freedman', z_mu=None):

    ''' Compute the luminosity function and archive in the given file. If the
    file exists, then the saved results are returned.

    Parameters
    ----------
    z : array-like
        Redshifts of galaxies.
    m : array-like
        Apparent magnitudes of galaxies.
    M : array-like
        Absolute magnitudes of galaxies.
    m_max : float
        Maximum sensitivity in apparent magnitudes.
    archive_file : str
        Name of file saved for bootstrapping.
    Nbootstraps : int
        Number of bootstraps to perform.
    bin_type : str
        Type of binning method to use. Options are 'freedman' or 'scott'
    z_mu : func
        Distance modulus as a function of redshift.

    Returns
    -------
    zbins : array-like
        Redshift bin centers.
    dist_z : array-like
        Redshift count distribution.
    err_z : array-like
        Redshift count errors.
    Mbins : array-like
        Absolute magnitude bin centers.
    dist_M : array-like
        Absolute magnitude count distribution.
    err_M : array-like
        Absolute magnitude count errors.

    '''

    from astroML.density_estimation import scotts_bin_width as scott_bin
    from astroML.density_estimation import freedman_bin_width as free_bin
    from astroML.lumfunc import binned_Cminus, bootstrap_Cminus

    Mmax = m_max - (m - M)
    zmax = z_mu(m_max - M)

    if not os.path.exists(archive_file):
        print ("- computing bootstrapped luminosity function ",
               "for %i points" % len(z))

        if bin_type == 'freedman':
            zbin_width, zbins = free_bin(z, return_bins=True)
            Mbin_width, Mbins = free_bin(M, return_bins=True)
        elif bin_type == 'scott':
            zbin_width, zbins = scott_bin(z, return_bins=True)
            Mbin_width, Mbins = scott_bin(M, return_bins=True)

        dist_z, err_z, dist_M, err_M = bootstrap_Cminus(z, M, zmax, Mmax,
                                                        zbins, Mbins,
                                                        Nbootstraps=20,
                                                        normalize=True)
        np.savez(archive_file,
                 zbins=zbins, dist_z=dist_z, err_z=err_z,
                 Mbins=Mbins, dist_M=dist_M, err_M=err_M)
    else:
        print "- using precomputed bootstrapped luminosity function results"
        archive = np.load(archive_file)
        zbins = archive['zbins']
        dist_z = archive['dist_z']
        err_z = archive['err_z']
        Mbins = archive['Mbins']
        dist_M = archive['dist_M']
        err_M = archive['err_M']

    return zbins, dist_z, err_z, Mbins, dist_M, err_M