def main():
    # parse command-line arguments
    parser = argparse.ArgumentParser(
        formatter_class=argparse.ArgumentDefaultsHelpFormatter)
    parser.add_argument('--prefix', type=str, default='',
        help = 'Optional prefix to use for output filenames.')
    parser.add_argument('--template-file', type=str,
        default='./intemplates/QSOtemplate.fits',
        help = 'Name of file containing random QSO templates to use.')
    parser.add_argument('--template', type=int, default=None,
        help = 'Index of template to use (or randomize if not set).')
    parser.add_argument('--no-noise', action='store_true',
        help = 'Do not add random noise to each spectrum.')
    parser.add_argument('--write-bricks', action='store_true',
        help = 'Write bricks consistent with datamodel instead of simplied output.')
    parser.add_argument('-seed', type=int, default=123,
        help = 'Random number generator seed to use.')
    parser.add_argument('--zmock', type=float,
        help = 'The z each mock should be redshifted to.')
#    parser.add_argument('--g_mag', type=double,
#        help = 'The g mag each mock should be scaled to.')
    args = parser.parse_args()

    # We require that the SPECSIM_MODEL environment variable is set.
    if 'SPECSIM_MODEL' not in os.environ:
        raise RuntimeError('The environment variable SPECSIM_MODEL must be set.')

    # Set the random seed.
    generator = np.random.RandomState(args.seed)

    # Initialize the simulation grid.
    g_grid = np.linspace(22.5, 23.0, 2)
    z = args.zmock

    if args.template is not None and args.template < 0:
        # Read the z=2.4, r=22.62 template from desimodel/data/spectra/
        args.template_file = './intemplates/spec-qso-z2.4-rmag22.62.dat'
        template_data = astropy.table.Table.read(args.template_file, format='ascii')
        wlen = template_data['WAVELENGTH']
        fluxes = template_data['FLUX'][np.newaxis, :]
        template_z = 2.4
        print('Using desimodel z={:.1f} QSO template.'.format(template_z))
    else:
        # Read the template file.
#        templates = fitsio.FITS(args.template_file, mode='r')
        template_data = astropy.table.Table.read(args.template_file, format='ascii')
        wlen = template_data['WAVELENGTH']
        fluxes = template_data['FLUX'][np.newaxis, :]
#        data = templates[0].read().view('>f8').reshape((5398,2))
#        templates.close()
#        wlen = data[:, 0]
#        fluxes = data[:, 1].transpose()
        if args.template is not None:
            fluxes = fluxes[args.template:args.template+1]
        template_z = 0.0

    num_templates = len(fluxes)
    print('Using {} z={:.1f} template(s) from {}.'
        .format(num_templates, template_z, args.template_file))

    # Extend each template so it can be redshifted over the range 0.5 - 3.5.
    wlen_min = wlen[0] * (1 + template_z) / (1 + z)
    wlen_max = max(10000, wlen[-1] * (1 + template_z) / (1 + z))
    # Extrapolate down to wlen_min
    flux_lo, flux_hi = None, None
    if wlen_min < wlen[0]:
        print('Extrapolate down to z={:.1f} at {:0f}A.'.format(z, wlen_min))
        wlen_lo = np.linspace(wlen_min - 1, wlen[0] - 0.1, 10)
        flux_lo = np.empty_like(wlen_lo)
        wlen = np.hstack([wlen_lo, wlen])
    # Extrapolate up to wlen_max
    if wlen_max > wlen[-1]:
        print('Extrapolate up to z={:.1f} at {:0f}A.'.format(z, wlen_max))
        wlen_hi = np.linspace(wlen[-1] + 0.1, wlen_max + 1, 10)
        flux_hi = np.empty_like(wlen_hi)
        wlen = np.hstack([wlen, wlen_hi])
    # Prepare each template.
    templates = [ ]
    for flux in fluxes:
        # Use the average flux at each end of the spectrum for extraploation.
        # Combine the pieces.
        if flux_lo is not None:
            flux_lo[:] = np.mean(flux[:10])
            flux = np.hstack([flux_lo, flux])
        if flux_hi is not None:
            flux_hi[:] = np.mean(flux[-10:])
            flux = np.hstack([flux, flux_hi])
        template = specsim.spectrum.SpectralFluxDensity(wlen, flux)
        templates.append(template)

    # Create the default atmosphere for the requested sky conditions.
    atmosphere = specsim.atmosphere.Atmosphere(
        skyConditions='dark', basePath=os.environ['SPECSIM_MODEL'])

    # Create a quick simulator using the default instrument model.
    qsim = specsim.quick.Quick(
        atmosphere=atmosphere, basePath=os.environ['SPECSIM_MODEL'])

    # Specify the simulation wavelength grid to use (in Angstroms).
    qsim.setWavelengthGrid(3500.3, 9999.7, 0.1)

    bands = 'brz'
    num_cameras = len(bands)
    flux = None

    # Initialize down sampling of the 0.1A simulation grid to 0.5A
    downsampling = 10
    ndown = qsim.wavelengthGrid.size // downsampling

    # Allocate output arrays.
    num_spec = len(g_grid)
    flux = np.zeros((num_cameras, num_spec, ndown))
    ivar = np.zeros_like(flux)
    wave = np.empty_like(flux)
    true_z = np.empty((num_cameras,num_spec))
    g_band_mag = np.empty_like(true_z)
    r_band_mag = np.empty_like(true_z)
    z_band_mag = np.empty_like(true_z)
    W1_band_mag = np.empty_like(true_z)
    W2_band_mag = np.empty_like(true_z)
    
    # Loop over g-band magnitudes and redshifts.
    spec_index = 0
    for g in g_grid:
        print('Simulating g = {:.2f}'.format(g))
        #        for z in z_grid:
        # Pick a random template to use. We do not use np.random.choice()
        # for Julien's benefit.
        template_index = int(generator.uniform()*num_templates)
        template = templates[template_index]
        # Run the simulation.
        input_spectrum = (template
            .createRedshifted(newZ=z, oldZ=template_z)
            .createRescaled(sdssBand='g', abMagnitude=g))
        results = qsim.simulate(
            sourceType='qso', sourceSpectrum=input_spectrum,
            airmass=1.0, expTime=900., downsampling=downsampling)
        # Loop over cameras
        for camera in range(num_cameras):
            snr = (results.snr)[: ,camera]
            mask = (results.obsflux > 0) & (snr > 0)
            flux[camera, spec_index, mask] = results[mask].obsflux
            ivar[camera, spec_index, mask] = (snr[mask] / results[mask].obsflux)**2
            if not args.no_noise:
                flux[camera, spec_index, mask] += np.random.normal(
                    scale=ivar[camera, spec_index, mask]**-0.5)
            wave[camera, spec_index] = results.wave

            true_z[camera, spec_index] = z
            g_band_mag[camera, spec_index] = g
            r_band_mag[camera, spec_index] = g
            z_band_mag[camera, spec_index] = g
            W1_band_mag[camera, spec_index] = g
            W2_band_mag[camera, spec_index] = g
        spec_index += 1

    for camera, band in enumerate(bands):
        if args.write_bricks:
            write_brick.write_brick_file(
                band=band,brickname='1234p567',NSpectra=num_spec,NWavelength=ndown,
                Flux=flux[camera],InvVar=ivar[camera],Wavelength=wave[camera],
                Resolution=wave[camera],TrueZ=true_z[camera],GBand=g_band_mag[camera],
                RBand=r_band_mag[camera],ZBand=z_band_mag[camera],
                W1Band=W1_band_mag[camera],W2Band=W2_band_mag[camera])
        else:
            output = fitsio.FITS(args.prefix + band + '.fits', 'rw', clobber=True)
            output.write(flux[camera])
            output.write(ivar[camera])
            output.write(wave[camera])
            output.close()
Example #2
0
def main():
    # parse command-line arguments
    parser = argparse.ArgumentParser(
        formatter_class=argparse.ArgumentDefaultsHelpFormatter)
    parser.add_argument('--prefix',
                        type=str,
                        default='',
                        help='Optional prefix to use for output filenames.')
    parser.add_argument(
        '--template-file',
        type=str,
        default='../simulate/QSOtemplate.fits',
        help='Name of file containing random QSO templates to use.')
    parser.add_argument(
        '--template',
        type=int,
        default=None,
        help='Index of template to use (or randomize if not set).')
    parser.add_argument('--no-noise',
                        action='store_true',
                        help='Do not add random noise to each spectrum.')
    parser.add_argument(
        '--write-bricks',
        action='store_true',
        help=
        'Write bricks consistent with datamodel instead of simplied output.')
    parser.add_argument(
        '--brickname',
        type=str,
        default='1234p567',
        help='Name of brick to write (ignored without --write-bricks).')
    parser.add_argument('-seed',
                        type=int,
                        default=123,
                        help='Random number generator seed to use.')
    parser.add_argument('--numsamples',
                        type=int,
                        default=100,
                        help='Number of random samples')
    args = parser.parse_args()

    #Tables from Palanque2012 and Palanque2015
    table2012 = np.array([
        15.75, 50, 11, 7, 4, 4, 4, 4, 4, 3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
        0, 0, 0, 0, 0, 0, 0, 0, 0, 16.25, 92, 34, 20, 14, 13, 13, 12, 12, 10,
        8, 6, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16.75,
        159, 96, 62, 43, 42, 41, 39, 37, 31, 25, 22, 21, 12, 4, 1, 0, 0, 0, 0,
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 17.25, 249, 248, 182, 131, 130, 128,
        120, 114, 96, 77, 65, 58, 34, 16, 8, 4, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0,
        0, 0, 0, 0, 17.75, 354, 558, 483, 381, 387, 384, 365, 347, 296, 238,
        192, 158, 91, 44, 22, 11, 5, 3, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
        18.25, 461, 1076, 1125, 1009, 1066, 1074, 1050, 1008, 876, 713, 553,
        431, 246, 119, 59, 29, 15, 8, 4, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0,
        18.75, 548, 1790, 2224, 2318, 2565, 2671, 2715, 2642, 2374, 1982, 1528,
        1126, 650, 318, 161, 80, 40, 20, 10, 5, 3, 1, 1, 0, 0, 0, 0, 0, 0, 0,
        19.25, 582, 2624, 3751, 4464, 5166, 5601, 5928, 5899, 5550, 4825, 3775,
        2744, 1626, 832, 428, 216, 108, 56, 28, 14, 7, 4, 2, 1, 0, 0, 0, 0, 0,
        0, 19.75, 503, 3517, 5561, 7237, 8674, 9779, 10631, 10847, 10630, 9685,
        7904, 5822, 3695, 2047, 1103, 569, 292, 151, 75, 39, 20, 10, 5, 3, 1,
        1, 0, 0, 0, 0, 20.25, 198, 4473, 7528, 10277, 12641, 14591, 16079,
        16737, 16843, 15972, 13518, 10254, 7186, 4500, 2627, 1445, 760, 405,
        203, 106, 54, 27, 14, 7, 4, 2, 1, 0, 0, 0, 20.75, 0, 4976, 9650, 13461,
        16826, 19623, 21758, 22913, 23360, 22704, 19534, 14971, 11576, 8356,
        5491, 3330, 1886, 1035, 540, 286, 145, 75, 39, 19, 10, 5, 3, 1, 1, 0,
        21.25, 0, 4569, 12028, 16929, 21338, 24976, 27767, 29400, 30142, 29653,
        25414, 19052, 15753, 12809, 9602, 6619, 4164, 2484, 1363, 744, 391,
        201, 105, 53, 28, 14, 7, 4, 2, 1, 21.75, 0, 2676, 14806, 20913, 26454,
        31008, 34512, 36621, 37628, 37214, 31417, 22460, 19085, 16765, 13967,
        10897, 7823, 5190, 3161, 1833, 999, 532, 285, 142, 75, 38, 19, 10, 5,
        2, 22.25, 0, 84, 15784, 25646, 32491, 38098, 42423, 45057, 46339,
        45926, 38025, 25572, 21704, 19852, 17646, 15057, 12150, 9109, 6291,
        4023, 2400, 1338, 736, 381, 203, 102, 52, 27, 13, 7, 22.75, 0, 0, 9053,
        31359, 39749, 46622, 51924, 55165, 56750, 56291, 45702, 28725, 23970,
        22325, 20498, 18418, 16044, 13305, 10392, 7496, 5006, 3077, 1810, 975,
        530, 276, 141, 73, 36, 19, 23.25, 0, 0, 232, 29955, 48563, 56956,
        63439, 67410, 69360, 68823, 54820, 32149, 26141, 24537, 22848, 21047,
        19095, 16860, 14383, 11591, 8748, 6068, 3930, 2330, 1333, 714, 373,
        198, 98, 51, 23.75, 0, 0, 0, 6251, 54772, 69546, 77462, 82312, 84692,
        84046, 65808, 35985, 28366, 26707, 25016, 23292, 21523, 19635, 17592,
        15282, 12711, 9950, 7240, 4836, 3014, 1753, 959, 519, 260, 137, 24.25,
        0, 0, 0, 0, 15743, 79815, 94554, 100480, 103394, 102610, 79067, 40365,
        30717, 28956, 27187, 25426, 23677, 21910, 20107, 18177, 16049, 13701,
        11073, 8398, 5869, 3795, 2254, 1299, 682, 364, 24.75, 0, 0, 0, 0, 0,
        18500, 100062, 122644, 126193, 125238, 95158, 45393, 33235, 31346,
        29458, 27598, 25781, 24001, 22249, 20487, 18656, 16705, 14505, 12119,
        9500, 6953, 4650, 2915, 1656, 927, 25.25, 0, 0, 0, 0, 0, 0, 8489,
        90591, 153382, 152869, 114645, 51218, 35949, 33912, 31882, 29890,
        27956, 26090, 24287, 22535, 20802, 19060, 17206, 15222, 12983, 10546,
        7997, 5625, 3605, 2163, 25.75, 0, 0, 0, 0, 0, 0, 0, 0, 38955, 134619,
        138257, 57968, 38880, 36680, 34488, 32343, 30266, 28272, 26363, 24538,
        22780, 21075, 19370, 17641, 15781, 13728, 11472, 9007, 6581, 4416,
        26.25, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 28228, 37600, 40111, 39669, 37301,
        34985, 32744, 30600, 28552, 26608, 24759, 22997, 21298, 19641, 17974,
        16232, 14366, 12237, 9959, 7525
    ]).reshape(22, 31)

    table2015a = np.array([
        15.75, 30, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
        0, 0, 0, 0, 0, 0, 0, 0, 0, 16.25, 60, 8, 4, 5, 5, 4, 4, 4, 4, 3, 2, 1,
        1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16.75, 117, 29,
        17, 19, 18, 17, 16, 16, 15, 12, 8, 6, 3, 2, 1, 1, 0, 0, 0, 0, 0, 0, 0,
        0, 0, 0, 0, 0, 0, 0, 17.25, 216, 101, 62, 70, 69, 64, 61, 62, 59, 45,
        32, 22, 13, 7, 4, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 17.75,
        358, 312, 224, 255, 253, 235, 227, 231, 224, 171, 121, 82, 47, 25, 13,
        7, 3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 18.25, 525, 788, 722, 855,
        869, 819, 803, 824, 811, 630, 452, 309, 171, 88, 46, 22, 9, 3, 1, 0, 0,
        0, 0, 0, 0, 0, 0, 0, 0, 0, 18.75, 703, 1563, 1890, 2393, 2544, 2493,
        2507, 2612, 2622, 2112, 1572, 1096, 603, 309, 157, 76, 28, 8, 3, 1, 0,
        0, 0, 0, 0, 0, 0, 0, 0, 0, 19.25, 898, 2490, 3740, 5086, 5758, 5971,
        6214, 6580, 6745, 5779, 4613, 3369, 1913, 1004, 516, 249, 93, 26, 10,
        4, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 19.75, 1125, 3445, 5827, 8319, 9913,
        10805, 11590, 12422, 12937, 11839, 10261, 8011, 4902, 2771, 1499, 753,
        289, 78, 31, 12, 3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 20.25, 1399, 4456, 7930,
        11585, 14183, 15895, 17350, 18718, 19660, 18783, 17275, 14270, 9517,
        5936, 3513, 1919, 804, 228, 91, 34, 10, 2, 1, 0, 0, 0, 0, 0, 0, 0,
        20.75, 1734, 5616, 10195, 15029, 18589, 21065, 23176, 25094, 26479,
        25795, 24410, 20801, 14695, 9899, 6391, 3856, 1851, 599, 248, 94, 27,
        6, 1, 1, 0, 0, 0, 0, 0, 0, 21.25, 2141, 7016, 12842, 18997, 23563,
        26793, 29584, 32124, 34027, 33395, 31948, 27600, 20026, 14047, 9572,
        6217, 3399, 1325, 598, 241, 73, 17, 4, 1, 1, 1, 0, 0, 0, 0, 21.75,
        2631, 8738, 16067, 23807, 29528, 33591, 37170, 40481, 43047, 42378,
        40701, 35383, 25928, 18498, 12931, 8731, 5198, 2395, 1211, 541, 182,
        45, 10, 3, 2, 2, 1, 1, 0, 0, 22.25, 3211, 10871, 20058, 29754, 36864,
        41912, 46457, 50760, 54210, 53457, 51424, 44870, 32982, 23683, 16738,
        11500, 7140, 3651, 2036, 1022, 394, 110, 25, 8, 6, 4, 2, 2, 1, 1,
        22.75, 3875, 13520, 25026, 37157, 45968, 52212, 57971, 63564, 68202,
        67344, 64840, 56742, 41732, 30041, 21339, 14774, 9334, 5003, 2969,
        1636, 730, 239, 60, 21, 13, 8, 5, 3, 2, 1, 23.25, 4591, 16812, 31220,
        46395, 57302, 65011, 72306, 79586, 85821, 84853, 81750, 71739, 52744,
        38010, 27078, 18823, 11969, 6500, 3980, 2322, 1159, 450, 133, 48, 30,
        19, 12, 7, 5, 3, 23.75, 5270, 20905, 38950, 57934, 71426, 80937, 90180,
        99667, 108052, 106983, 103130, 90753, 66677, 48080, 34331, 23929,
        15247, 8253, 5120, 3076, 1645, 733, 256, 102, 63, 39, 24, 15, 9, 6,
        24.25, 5713, 25993, 48598, 72353, 89037, 100762, 112480, 124858,
        136130, 134989, 130200, 114903, 84351, 60850, 43543, 30420, 19392,
        10379, 6468, 3939, 2184, 1061, 432, 192, 119, 73, 45, 27, 17, 10,
        24.75, 5464, 32318, 60643, 90374, 110997, 125444, 140309, 156474,
        171625, 170467, 164509, 145614, 106798, 77072, 55275, 38702, 24667,
        13006, 8110, 4969, 2803, 1428, 648, 317, 199, 123, 76, 46, 28, 17
    ]).reshape(19, 31)

    table2015b = np.array([
        15.75, 23, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 3, 1, 0, 0, 0, 0, 0, 0, 0,
        0, 0, 0, 0, 0, 0, 0, 0, 0, 16.25, 49, 4, 2, 2, 2, 2, 2, 2, 2, 1, 1, 16,
        10, 5, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16.75, 99, 16,
        8, 10, 10, 9, 9, 8, 8, 5, 3, 40, 25, 12, 6, 3, 1, 0, 0, 0, 0, 0, 0, 0,
        0, 0, 0, 0, 0, 0, 17.25, 190, 69, 38, 44, 45, 41, 39, 39, 36, 24, 15,
        104, 65, 32, 15, 7, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 17.75,
        326, 248, 165, 196, 199, 185, 177, 176, 163, 113, 69, 268, 167, 82, 39,
        17, 6, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 18.25, 488, 699, 628,
        775, 805, 763, 744, 751, 709, 501, 314, 679, 422, 211, 102, 46, 16, 5,
        2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 18.75, 664, 1453, 1808, 2389, 2615,
        2602, 2624, 2702, 2629, 1968, 1308, 1650, 1027, 532, 262, 119, 42, 12,
        4, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 19.25, 866, 2337, 3638, 5131, 5991,
        6356, 6674, 7031, 7076, 5840, 4349, 3696, 2334, 1283, 657, 307, 111,
        30, 11, 3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 19.75, 1113, 3252, 5609, 8203,
        9995, 11093, 11997, 12825, 13218, 11932, 10033, 7168, 4740, 2850, 1566,
        769, 288, 77, 27, 9, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 20.25, 1423, 4274,
        7646, 11336, 14053, 15897, 17425, 18758, 19553, 18488, 16711, 11689,
        8346, 5597, 3399, 1817, 723, 195, 70, 23, 6, 1, 0, 0, 0, 0, 0, 0, 0, 0,
        20.75, 1814, 5517, 9994, 14889, 18542, 21093, 23243, 25116, 26334,
        25315, 23523, 16660, 12773, 9487, 6456, 3873, 1709, 482, 177, 58, 15,
        3, 1, 0, 0, 0, 0, 0, 0, 0, 21.25, 2303, 7084, 12906, 19262, 23994,
        27320, 30181, 32721, 34466, 33311, 31222, 21941, 17567, 14054, 10566,
        7166, 3647, 1143, 436, 148, 38, 7, 1, 0, 0, 0, 0, 0, 0, 0, 21.75, 2911,
        9082, 16610, 24818, 30879, 35139, 38892, 42310, 44774, 43370, 40764,
        27761, 22625, 18918, 15230, 11423, 6772, 2503, 1034, 367, 96, 19, 3, 1,
        0, 0, 0, 0, 0, 0, 22.25, 3652, 11639, 21358, 31945, 39682, 45108,
        50015, 54614, 58082, 56344, 53020, 34486, 28144, 24081, 20162, 16146,
        10868, 4879, 2277, 876, 241, 49, 9, 2, 1, 1, 0, 0, 0, 0, 22.75, 4527,
        14913, 27461, 41112, 50977, 57875, 64289, 70486, 75363, 73207, 68942,
        42521, 34433, 29793, 25456, 21136, 15480, 8290, 4489, 1955, 585, 122,
        22, 5, 3, 1, 1, 0, 0, 0, 23.25, 5504, 19106, 35311, 52915, 65485,
        74247, 82634, 90995, 97849, 95188, 89701, 52304, 41818, 36375, 31400,
        26555, 20387, 12378, 7728, 3931, 1345, 302, 55, 14, 7, 3, 2, 1, 0, 0,
        23.75, 6479, 24477, 45409, 68119, 84128, 95249, 106225, 117520, 127141,
        123880, 116812, 64335, 50637, 44159, 38330, 32717, 25709, 16775, 11668,
        6924, 2830, 719, 137, 35, 18, 9, 4, 2, 1, 0, 24.25, 7195, 31358, 58404,
        87705, 108088, 122196, 136567, 151843, 165336, 161372, 152261, 79215,
        61261, 53494, 46590, 39969, 31740, 21404, 15927, 10682, 5278, 1609,
        337, 89, 45, 22, 11, 5, 3, 1, 24.75, 7043, 40171, 75127, 112945,
        138885, 156770, 175600, 196278, 215178, 210413, 198658, 97685, 74113,
        64767, 56549, 48670, 38821, 26430, 20396, 14815, 8615, 3269, 793, 220,
        112, 56, 28, 13, 6, 3
    ]).reshape(19, 31)

    table2015 = 0.5 * (table2015a + table2015b)

    # We require that the SPECSIM_MODEL environment variable is set.
    if 'SPECSIM_MODEL' not in os.environ:
        raise RuntimeError(
            'The environment variable SPECSIM_MODEL must be set.')

    # Set the random seed.
    generator = np.random.RandomState(args.seed)

    # Initialize the simulation grid.
    #g_grid = np.linspace(22.0, 23.0, 5)
    #z_grid = np.linspace(0.5, 3.5, 31)

    # We use the QLF from the table to sample the quasars instead using a grid.
    g_grid, z_grid = sample(table2015, args.numsamples, seed=args.seed)
    if args.template is not None and args.template < 0:
        # Read the z=2.4, r=22.62 template from desimodel/data/spectra/
        args.template_file = 'spec-qso-z2.4-rmag22.62.dat'
        template_data = astropy.table.Table.read(args.template_file,
                                                 format='ascii')
        wlen = template_data['WAVELENGTH']
        fluxes = template_data['FLUX'][np.newaxis, :]
        template_z = 2.4
        print('Using desimodel z={:.1f} QSO template.'.format(template_z))
    else:
        # Read the template file.
        templates = fitsio.FITS(args.template_file, mode='r')
        data = templates[1].read().view('>f8').reshape((40800, 501))
        templates.close()
        wlen = data[:, 0]
        fluxes = data[:, 1:].transpose()
        if args.template is not None:
            fluxes = fluxes[args.template:args.template + 1]
        template_z = 0.0

    num_templates = len(fluxes)
    print('Using {} z={:.1f} template(s) from {}.'.format(
        num_templates, template_z, args.template_file))

    # Extend each template so it can be redshifted over the range 0.5 - 3.5
    # and also be used to calculate magnitudes.
    wlen_min = wlen[0] * (1 + template_z) / (1 + z_grid[-1])
    wlen_max = max(10000, wlen[-1] * (1 + template_z) / (1 + z_grid[0]))
    # Extrapolate down to wlen_min
    flux_lo, flux_hi = None, None
    if wlen_min < wlen[0]:
        print('Extrapolate down to z={:.1f} at {:0f}A.'.format(
            z_grid[0], wlen_min))
        wlen_lo = np.linspace(wlen_min - 1, wlen[0] - 0.1, 10)
        flux_lo = np.empty_like(wlen_lo)
        wlen = np.hstack([wlen_lo, wlen])
    # Extrapolate up to wlen_max
    if wlen_max > wlen[-1]:
        print('Extrapolate up to z={:.1f} at {:0f}A.'.format(
            z_grid[-1], wlen_max))
        wlen_hi = np.linspace(wlen[-1] + 0.1, wlen_max + 1, 10)
        flux_hi = np.empty_like(wlen_hi)
        wlen = np.hstack([wlen, wlen_hi])
    # Prepare each template.
    templates = []
    for flux in fluxes:
        # Use the average flux at each end of the spectrum for extraploation.
        # Combine the pieces.
        if flux_lo is not None:
            flux_lo[:] = np.mean(flux[:10])
            flux = np.hstack([flux_lo, flux])
        if flux_hi is not None:
            flux_hi[:] = np.mean(flux[-10:])
            flux = np.hstack([flux, flux_hi])
        template = specsim.spectrum.SpectralFluxDensity(wlen, flux)
        templates.append(template)

    # Create the default atmosphere for the requested sky conditions.
    atmosphere = specsim.atmosphere.Atmosphere(
        skyConditions='dark', basePath=os.environ['SPECSIM_MODEL'])

    # Create a quick simulator using the default instrument model.
    qsim = specsim.quick.Quick(atmosphere=atmosphere,
                               basePath=os.environ['SPECSIM_MODEL'])

    # Specify the simulation wavelength grid to use (in Angstroms).
    qsim.setWavelengthGrid(3500.3, 9999.7, 0.1)

    bands = 'brz'
    num_cameras = len(bands)
    flux = None

    # Initialize down sampling of the 0.1A simulation grid to 1.0A
    downsampling = 10
    ndown = qsim.wavelengthGrid.size // downsampling

    # Allocate output arrays for data.
    num_spec = args.numsamples
    flux = np.zeros((num_cameras, num_spec, ndown))
    ivar = np.zeros_like(flux)
    wave = np.empty_like(flux)

    # Allocate output arrays for truth metadata.
    truth = astropy.table.Table(names=('TRUEZ', 'GBANDT', 'RBANDT', 'ZBANDT',
                                       'W1BANDT', 'W2BANDT', 'TMPID'),
                                dtype=('f4', 'f4', 'f4', 'f4', 'f4', 'f4',
                                       'i4'))

    # Loop over g-band magnitudes and redshifts.
    spec_index = 0
    for i in range(0, num_spec):
        g = g_grid[i]
        z = z_grid[i]
        print('Simulating g = {:.2f}'.format(g))
        # Pick a random template to use. We do not use np.random.choice()
        # for Julien's benefit.
        template_index = int(generator.uniform() * num_templates)
        template = templates[template_index]
        # Run the simulation.
        input_spectrum = (template.createRedshifted(
            newZ=z, oldZ=template_z).createRescaled(sdssBand='g',
                                                    abMagnitude=g))
        results = qsim.simulate(sourceType='qso',
                                sourceSpectrum=input_spectrum,
                                airmass=1.0,
                                expTime=900.,
                                downsampling=downsampling)
        # Loop over cameras
        for camera in range(num_cameras):
            snr = (results.snr)[:, camera]
            mask = (results.obsflux > 0) & (snr > 0)
            flux[camera, spec_index, mask] = results[mask].obsflux
            ivar[camera, spec_index,
                 mask] = (snr[mask] / results[mask].obsflux)**2
            if not args.no_noise:
                flux[camera, spec_index,
                     mask] += np.random.normal(scale=ivar[camera, spec_index,
                                                          mask]**-0.5)
            wave[camera, spec_index] = results.wave
        # Save the truth for this simulated QSO.
        tmpid = args.template or template_index
        truth.add_row(
            dict(TRUEZ=z,
                 GBANDT=g,
                 RBANDT=g,
                 ZBANDT=g,
                 W1BANDT=g,
                 W2BANDT=g,
                 TMPID=tmpid))
        spec_index += 1

    for camera, band in enumerate(bands):
        if args.write_bricks:
            write_brick.write_brick_file(band=band,
                                         brickname=args.brickname,
                                         Flux=flux[camera],
                                         InvVar=ivar[camera],
                                         Wavelength=wave[camera],
                                         Resolution=wave[camera],
                                         truth=truth)
        else:
            output = fitsio.FITS(args.prefix + band + '.fits',
                                 'rw',
                                 clobber=True)
            output.write(flux[camera])
            output.write(ivar[camera])
            output.write(wave[camera])
            output.close()
Example #3
0
def main():
    # parse command-line arguments
    parser = argparse.ArgumentParser(
        formatter_class=argparse.ArgumentDefaultsHelpFormatter)
    parser.add_argument('--prefix',
                        type=str,
                        default='',
                        help='Optional prefix to use for output filenames.')
    parser.add_argument(
        '--template-file',
        type=str,
        default='./intemplates/QSOtemplate.fits',
        help='Name of file containing random QSO templates to use.')
    parser.add_argument(
        '--template',
        type=int,
        default=None,
        help='Index of template to use (or randomize if not set).')
    parser.add_argument('--no-noise',
                        action='store_true',
                        help='Do not add random noise to each spectrum.')
    parser.add_argument(
        '--write-bricks',
        action='store_true',
        help=
        'Write bricks consistent with datamodel instead of simplied output.')
    parser.add_argument('-seed',
                        type=int,
                        default=123,
                        help='Random number generator seed to use.')
    parser.add_argument('--zmock',
                        type=float,
                        help='The z each mock should be redshifted to.')
    #    parser.add_argument('--g_mag', type=double,
    #        help = 'The g mag each mock should be scaled to.')
    args = parser.parse_args()

    # We require that the SPECSIM_MODEL environment variable is set.
    if 'SPECSIM_MODEL' not in os.environ:
        raise RuntimeError(
            'The environment variable SPECSIM_MODEL must be set.')

    # Set the random seed.
    generator = np.random.RandomState(args.seed)

    # Initialize the simulation grid.
    g_grid = np.linspace(22.5, 23.0, 2)
    z = args.zmock

    if args.template is not None and args.template < 0:
        # Read the z=2.4, r=22.62 template from desimodel/data/spectra/
        args.template_file = './intemplates/spec-qso-z2.4-rmag22.62.dat'
        template_data = astropy.table.Table.read(args.template_file,
                                                 format='ascii')
        wlen = template_data['WAVELENGTH']
        fluxes = template_data['FLUX'][np.newaxis, :]
        template_z = 2.4
        print('Using desimodel z={:.1f} QSO template.'.format(template_z))
    else:
        # Read the template file.
        #        templates = fitsio.FITS(args.template_file, mode='r')
        template_data = astropy.table.Table.read(args.template_file,
                                                 format='ascii')
        wlen = template_data['WAVELENGTH']
        fluxes = template_data['FLUX'][np.newaxis, :]
        #        data = templates[0].read().view('>f8').reshape((5398,2))
        #        templates.close()
        #        wlen = data[:, 0]
        #        fluxes = data[:, 1].transpose()
        if args.template is not None:
            fluxes = fluxes[args.template:args.template + 1]
        template_z = 0.0

    num_templates = len(fluxes)
    print('Using {} z={:.1f} template(s) from {}.'.format(
        num_templates, template_z, args.template_file))

    # Extend each template so it can be redshifted over the range 0.5 - 3.5.
    wlen_min = wlen[0] * (1 + template_z) / (1 + z)
    wlen_max = max(10000, wlen[-1] * (1 + template_z) / (1 + z))
    # Extrapolate down to wlen_min
    flux_lo, flux_hi = None, None
    if wlen_min < wlen[0]:
        print('Extrapolate down to z={:.1f} at {:0f}A.'.format(z, wlen_min))
        wlen_lo = np.linspace(wlen_min - 1, wlen[0] - 0.1, 10)
        flux_lo = np.empty_like(wlen_lo)
        wlen = np.hstack([wlen_lo, wlen])
    # Extrapolate up to wlen_max
    if wlen_max > wlen[-1]:
        print('Extrapolate up to z={:.1f} at {:0f}A.'.format(z, wlen_max))
        wlen_hi = np.linspace(wlen[-1] + 0.1, wlen_max + 1, 10)
        flux_hi = np.empty_like(wlen_hi)
        wlen = np.hstack([wlen, wlen_hi])
    # Prepare each template.
    templates = []
    for flux in fluxes:
        # Use the average flux at each end of the spectrum for extraploation.
        # Combine the pieces.
        if flux_lo is not None:
            flux_lo[:] = np.mean(flux[:10])
            flux = np.hstack([flux_lo, flux])
        if flux_hi is not None:
            flux_hi[:] = np.mean(flux[-10:])
            flux = np.hstack([flux, flux_hi])
        template = specsim.spectrum.SpectralFluxDensity(wlen, flux)
        templates.append(template)

    # Create the default atmosphere for the requested sky conditions.
    atmosphere = specsim.atmosphere.Atmosphere(
        skyConditions='dark', basePath=os.environ['SPECSIM_MODEL'])

    # Create a quick simulator using the default instrument model.
    qsim = specsim.quick.Quick(atmosphere=atmosphere,
                               basePath=os.environ['SPECSIM_MODEL'])

    # Specify the simulation wavelength grid to use (in Angstroms).
    qsim.setWavelengthGrid(3500.3, 9999.7, 0.1)

    bands = 'brz'
    num_cameras = len(bands)
    flux = None

    # Initialize down sampling of the 0.1A simulation grid to 0.5A
    downsampling = 10
    ndown = qsim.wavelengthGrid.size // downsampling

    # Allocate output arrays.
    num_spec = len(g_grid)
    flux = np.zeros((num_cameras, num_spec, ndown))
    ivar = np.zeros_like(flux)
    wave = np.empty_like(flux)
    true_z = np.empty((num_cameras, num_spec))
    g_band_mag = np.empty_like(true_z)
    r_band_mag = np.empty_like(true_z)
    z_band_mag = np.empty_like(true_z)
    W1_band_mag = np.empty_like(true_z)
    W2_band_mag = np.empty_like(true_z)

    # Loop over g-band magnitudes and redshifts.
    spec_index = 0
    for g in g_grid:
        print('Simulating g = {:.2f}'.format(g))
        #        for z in z_grid:
        # Pick a random template to use. We do not use np.random.choice()
        # for Julien's benefit.
        template_index = int(generator.uniform() * num_templates)
        template = templates[template_index]
        # Run the simulation.
        input_spectrum = (template.createRedshifted(
            newZ=z, oldZ=template_z).createRescaled(sdssBand='g',
                                                    abMagnitude=g))
        results = qsim.simulate(sourceType='qso',
                                sourceSpectrum=input_spectrum,
                                airmass=1.0,
                                expTime=900.,
                                downsampling=downsampling)
        # Loop over cameras
        for camera in range(num_cameras):
            snr = (results.snr)[:, camera]
            mask = (results.obsflux > 0) & (snr > 0)
            flux[camera, spec_index, mask] = results[mask].obsflux
            ivar[camera, spec_index,
                 mask] = (snr[mask] / results[mask].obsflux)**2
            if not args.no_noise:
                flux[camera, spec_index,
                     mask] += np.random.normal(scale=ivar[camera, spec_index,
                                                          mask]**-0.5)
            wave[camera, spec_index] = results.wave

            true_z[camera, spec_index] = z
            g_band_mag[camera, spec_index] = g
            r_band_mag[camera, spec_index] = g
            z_band_mag[camera, spec_index] = g
            W1_band_mag[camera, spec_index] = g
            W2_band_mag[camera, spec_index] = g
        spec_index += 1

    for camera, band in enumerate(bands):
        if args.write_bricks:
            write_brick.write_brick_file(band=band,
                                         brickname='1234p567',
                                         NSpectra=num_spec,
                                         NWavelength=ndown,
                                         Flux=flux[camera],
                                         InvVar=ivar[camera],
                                         Wavelength=wave[camera],
                                         Resolution=wave[camera],
                                         TrueZ=true_z[camera],
                                         GBand=g_band_mag[camera],
                                         RBand=r_band_mag[camera],
                                         ZBand=z_band_mag[camera],
                                         W1Band=W1_band_mag[camera],
                                         W2Band=W2_band_mag[camera])
        else:
            output = fitsio.FITS(args.prefix + band + '.fits',
                                 'rw',
                                 clobber=True)
            output.write(flux[camera])
            output.write(ivar[camera])
            output.write(wave[camera])
            output.close()
Example #4
0
def main():
    # parse command-line arguments
    parser = argparse.ArgumentParser(
        formatter_class=argparse.ArgumentDefaultsHelpFormatter)
    parser.add_argument('--prefix', type=str, default='',
        help = 'Optional prefix to use for output filenames.')
    parser.add_argument('--template-file', type=str,
        default='../simulate/QSOtemplate.fits',
        help = 'Name of file containing random QSO templates to use.')
    parser.add_argument('--template', type=int, default=None,
        help = 'Index of template to use (or randomize if not set).')
    parser.add_argument('--no-noise', action='store_true',
        help = 'Do not add random noise to each spectrum.')
    parser.add_argument('--write-bricks', action='store_true',
        help = 'Write bricks consistent with datamodel instead of simplied output.')
    parser.add_argument('--brickname', type=str, default='1234p567',
        help = 'Name of brick to write (ignored without --write-bricks).')
    parser.add_argument('-seed', type=int, default=123,
        help = 'Random number generator seed to use.')
    parser.add_argument('--numsamples',type=int, default=100,
        help = 'Number of random samples') 
    args = parser.parse_args()
    
    #Tables from Palanque2012 and Palanque2015
    table2012 = np.array([
        15.75, 50, 11, 7, 4, 4, 4, 4, 4, 3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
        16.25, 92, 34, 20, 14, 13, 13, 12, 12, 10, 8, 6, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
        16.75, 159, 96, 62, 43, 42, 41, 39, 37, 31, 25, 22, 21, 12, 4, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
        17.25, 249, 248, 182, 131, 130, 128, 120, 114, 96, 77, 65, 58, 34, 16, 8, 4, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
        17.75, 354, 558, 483, 381, 387, 384, 365, 347, 296, 238, 192, 158, 91, 44, 22, 11, 5, 3, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
        18.25, 461, 1076, 1125, 1009, 1066, 1074, 1050, 1008, 876, 713, 553, 431, 246, 119, 59, 29, 15, 8, 4, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0,
        18.75, 548, 1790, 2224, 2318, 2565, 2671, 2715, 2642, 2374, 1982, 1528, 1126, 650, 318, 161, 80, 40, 20, 10, 5, 3, 1, 1, 0, 0, 0, 0, 0, 0, 0,
        19.25, 582, 2624, 3751, 4464, 5166, 5601, 5928, 5899, 5550, 4825, 3775, 2744, 1626, 832, 428, 216, 108, 56, 28, 14, 7, 4, 2, 1, 0, 0, 0, 0, 0, 0,
        19.75, 503, 3517, 5561, 7237, 8674, 9779, 10631, 10847, 10630, 9685, 7904, 5822, 3695, 2047, 1103, 569, 292, 151, 75, 39, 20, 10, 5, 3, 1, 1, 0, 0, 0, 0,
        20.25, 198, 4473, 7528, 10277, 12641, 14591, 16079, 16737, 16843, 15972, 13518, 10254, 7186, 4500, 2627, 1445, 760, 405, 203, 106, 54, 27, 14, 7, 4, 2, 1, 0, 0, 0,
        20.75, 0, 4976, 9650, 13461, 16826, 19623, 21758, 22913, 23360, 22704, 19534, 14971, 11576, 8356, 5491, 3330, 1886, 1035, 540, 286, 145, 75, 39, 19, 10, 5, 3, 1, 1, 0,
        21.25, 0, 4569, 12028, 16929, 21338, 24976, 27767, 29400, 30142, 29653, 25414, 19052, 15753, 12809, 9602, 6619, 4164, 2484, 1363, 744, 391, 201, 105, 53, 28, 14, 7, 4, 2, 1,
        21.75, 0, 2676, 14806, 20913, 26454, 31008, 34512, 36621, 37628, 37214, 31417, 22460, 19085, 16765, 13967, 10897, 7823, 5190, 3161, 1833, 999, 532, 285, 142, 75, 38, 19, 10, 5, 2,
        22.25, 0, 84, 15784, 25646, 32491, 38098, 42423, 45057, 46339, 45926, 38025, 25572, 21704, 19852, 17646, 15057, 12150, 9109, 6291, 4023, 2400, 1338, 736, 381, 203, 102, 52, 27, 13, 7,
        22.75, 0, 0, 9053, 31359, 39749, 46622, 51924, 55165, 56750, 56291, 45702, 28725, 23970, 22325, 20498, 18418, 16044, 13305, 10392, 7496, 5006, 3077, 1810, 975, 530, 276, 141, 73, 36, 19,
        23.25, 0, 0, 232, 29955, 48563, 56956, 63439, 67410, 69360, 68823, 54820, 32149, 26141, 24537, 22848, 21047, 19095, 16860, 14383, 11591, 8748, 6068, 3930, 2330, 1333, 714, 373, 198, 98, 51,
        23.75, 0, 0, 0, 6251, 54772, 69546, 77462, 82312, 84692, 84046, 65808, 35985, 28366, 26707, 25016, 23292, 21523, 19635, 17592, 15282, 12711, 9950, 7240, 4836, 3014, 1753, 959, 519, 260, 137,
        24.25, 0, 0, 0, 0, 15743, 79815, 94554, 100480, 103394, 102610, 79067, 40365, 30717, 28956, 27187, 25426, 23677, 21910, 20107, 18177, 16049, 13701, 11073, 8398, 5869, 3795, 2254, 1299, 682, 364,
        24.75, 0, 0, 0, 0, 0, 18500, 100062, 122644, 126193, 125238, 95158, 45393, 33235, 31346, 29458, 27598, 25781, 24001, 22249, 20487, 18656, 16705, 14505, 12119, 9500, 6953, 4650, 2915, 1656, 927,
        25.25, 0, 0, 0, 0, 0, 0, 8489, 90591, 153382, 152869, 114645, 51218, 35949, 33912, 31882, 29890, 27956, 26090, 24287, 22535, 20802, 19060, 17206, 15222, 12983, 10546, 7997, 5625, 3605, 2163,
        25.75, 0, 0, 0, 0, 0, 0, 0, 0, 38955, 134619, 138257, 57968, 38880, 36680, 34488, 32343, 30266, 28272, 26363, 24538, 22780, 21075, 19370, 17641, 15781, 13728, 11472, 9007, 6581, 4416,
        26.25, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 28228, 37600, 40111, 39669, 37301, 34985, 32744, 30600, 28552, 26608, 24759, 22997, 21298, 19641, 17974, 16232, 14366, 12237, 9959, 7525
    ]).reshape(22, 31)

    table2015a = np.array([
    15.75, 30, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    16.25, 60, 8, 4, 5, 5, 4, 4, 4, 4, 3, 2, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    16.75, 117, 29, 17, 19, 18, 17, 16, 16, 15, 12, 8, 6, 3, 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    17.25, 216, 101, 62, 70, 69, 64, 61, 62, 59, 45, 32, 22, 13, 7, 4, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    17.75, 358, 312, 224, 255, 253, 235, 227, 231, 224, 171, 121, 82, 47, 25, 13, 7, 3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    18.25, 525, 788, 722, 855, 869, 819, 803, 824, 811, 630, 452, 309, 171, 88, 46, 22, 9, 3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    18.75, 703, 1563, 1890, 2393, 2544, 2493, 2507, 2612, 2622, 2112, 1572, 1096, 603, 309, 157, 76, 28, 8, 3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    19.25, 898, 2490, 3740, 5086, 5758, 5971, 6214, 6580, 6745, 5779, 4613, 3369, 1913, 1004, 516, 249, 93, 26, 10, 4, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    19.75, 1125, 3445, 5827, 8319, 9913, 10805, 11590, 12422, 12937, 11839, 10261, 8011, 4902, 2771, 1499, 753, 289, 78, 31, 12, 3, 1, 0, 0, 0, 0, 0, 0, 0, 0,
    20.25, 1399, 4456, 7930, 11585, 14183, 15895, 17350, 18718, 19660, 18783, 17275, 14270, 9517, 5936, 3513, 1919, 804, 228, 91, 34, 10, 2, 1, 0, 0, 0, 0, 0, 0, 0,
    20.75, 1734, 5616, 10195, 15029, 18589, 21065, 23176, 25094, 26479, 25795, 24410, 20801, 14695, 9899, 6391, 3856, 1851, 599, 248, 94, 27, 6, 1, 1, 0, 0, 0, 0, 0, 0,
    21.25, 2141, 7016, 12842, 18997, 23563, 26793, 29584, 32124, 34027, 33395, 31948, 27600, 20026, 14047, 9572, 6217, 3399, 1325, 598, 241, 73, 17, 4, 1, 1, 1, 0, 0, 0, 0,
    21.75, 2631, 8738, 16067, 23807, 29528, 33591, 37170, 40481, 43047, 42378, 40701, 35383, 25928, 18498, 12931, 8731, 5198, 2395, 1211, 541, 182, 45, 10, 3, 2, 2, 1, 1, 0, 0,
    22.25, 3211, 10871, 20058, 29754, 36864, 41912, 46457, 50760, 54210, 53457, 51424, 44870, 32982, 23683, 16738, 11500, 7140, 3651, 2036, 1022, 394, 110, 25, 8, 6, 4, 2, 2, 1, 1,
    22.75, 3875, 13520, 25026, 37157, 45968, 52212, 57971, 63564, 68202, 67344, 64840, 56742, 41732, 30041, 21339, 14774, 9334, 5003, 2969, 1636, 730, 239, 60, 21, 13, 8, 5, 3, 2, 1,
    23.25, 4591, 16812, 31220, 46395, 57302, 65011, 72306, 79586, 85821, 84853, 81750, 71739, 52744, 38010, 27078, 18823, 11969, 6500, 3980, 2322, 1159, 450, 133, 48, 30, 19, 12, 7, 5, 3,
    23.75, 5270, 20905, 38950, 57934, 71426, 80937, 90180, 99667, 108052, 106983, 103130, 90753, 66677, 48080, 34331, 23929, 15247, 8253, 5120, 3076, 1645, 733, 256, 102, 63, 39, 24, 15, 9, 6,
    24.25, 5713, 25993, 48598, 72353, 89037, 100762, 112480, 124858, 136130, 134989, 130200, 114903, 84351, 60850, 43543, 30420, 19392, 10379, 6468, 3939, 2184, 1061, 432, 192, 119, 73, 45, 27, 17, 10,
    24.75, 5464, 32318, 60643, 90374, 110997, 125444, 140309, 156474, 171625, 170467, 164509, 145614, 106798, 77072, 55275, 38702, 24667, 13006, 8110, 4969, 2803, 1428, 648, 317, 199, 123, 76, 46, 28, 17
    ]).reshape(19, 31)

    table2015b = np.array([
    15.75, 23, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ,
    16.25, 49, 4, 2, 2, 2, 2, 2, 2, 2, 1, 1, 16, 10, 5, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ,
    16.75, 99, 16, 8, 10, 10, 9, 9, 8, 8, 5, 3, 40, 25, 12, 6, 3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ,
    17.25, 190, 69, 38, 44, 45, 41, 39, 39, 36, 24, 15, 104, 65, 32, 15, 7, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    17.75, 326, 248, 165, 196, 199, 185, 177, 176, 163, 113, 69, 268, 167, 82, 39, 17, 6, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ,
    18.25, 488, 699, 628, 775, 805, 763, 744, 751, 709, 501, 314, 679, 422, 211, 102, 46, 16, 5, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ,
    18.75, 664, 1453, 1808, 2389, 2615, 2602, 2624, 2702, 2629, 1968, 1308, 1650, 1027, 532, 262, 119, 42, 12, 4, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ,
    19.25, 866, 2337, 3638, 5131, 5991, 6356, 6674, 7031, 7076, 5840, 4349, 3696, 2334, 1283, 657, 307, 111, 30, 11, 3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 ,
    19.75, 1113, 3252, 5609, 8203, 9995, 11093, 11997, 12825, 13218, 11932, 10033, 7168, 4740, 2850, 1566, 769, 288, 77, 27, 9, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0 ,
    20.25, 1423, 4274, 7646, 11336, 14053, 15897, 17425, 18758, 19553, 18488, 16711, 11689, 8346, 5597, 3399, 1817, 723, 195, 70, 23, 6, 1, 0, 0, 0, 0, 0, 0, 0, 0 ,
    20.75, 1814, 5517, 9994, 14889, 18542, 21093, 23243, 25116, 26334, 25315, 23523, 16660, 12773, 9487, 6456, 3873, 1709, 482, 177, 58, 15, 3, 1, 0, 0, 0, 0, 0, 0, 0 ,
    21.25, 2303, 7084, 12906, 19262, 23994, 27320, 30181, 32721, 34466, 33311, 31222, 21941, 17567, 14054, 10566, 7166, 3647, 1143, 436, 148, 38, 7, 1, 0, 0, 0, 0, 0, 0, 0 ,
    21.75, 2911, 9082, 16610, 24818, 30879, 35139, 38892, 42310, 44774, 43370, 40764, 27761, 22625, 18918, 15230, 11423, 6772, 2503, 1034, 367, 96, 19, 3, 1, 0, 0, 0, 0, 0, 0 ,
    22.25, 3652, 11639, 21358, 31945, 39682, 45108, 50015, 54614, 58082, 56344, 53020, 34486, 28144, 24081, 20162, 16146, 10868, 4879, 2277, 876, 241, 49, 9, 2, 1, 1, 0, 0, 0, 0 ,
    22.75, 4527, 14913, 27461, 41112, 50977, 57875, 64289, 70486, 75363, 73207, 68942, 42521, 34433, 29793, 25456, 21136, 15480, 8290, 4489, 1955, 585, 122, 22, 5, 3, 1, 1, 0, 0, 0 ,
    23.25, 5504, 19106, 35311, 52915, 65485, 74247, 82634, 90995, 97849, 95188, 89701, 52304, 41818, 36375, 31400, 26555, 20387, 12378, 7728, 3931, 1345, 302, 55, 14, 7, 3, 2, 1, 0, 0,
    23.75, 6479, 24477, 45409, 68119, 84128, 95249, 106225, 117520, 127141, 123880, 116812, 64335, 50637, 44159, 38330, 32717, 25709, 16775, 11668, 6924, 2830, 719, 137, 35, 18, 9, 4, 2, 1, 0 ,
    24.25, 7195, 31358, 58404, 87705, 108088, 122196, 136567, 151843, 165336, 161372, 152261, 79215, 61261, 53494, 46590, 39969, 31740, 21404, 15927, 10682, 5278, 1609, 337, 89, 45, 22, 11, 5, 3, 1,
    24.75, 7043, 40171, 75127, 112945, 138885, 156770, 175600, 196278, 215178, 210413, 198658, 97685, 74113, 64767, 56549, 48670, 38821, 26430, 20396, 14815, 8615, 3269, 793, 220, 112, 56, 28, 13, 6, 3
    ]).reshape(19, 31)

    table2015 = 0.5 * (table2015a + table2015b)
    
   
  

    # We require that the SPECSIM_MODEL environment variable is set.
    if 'SPECSIM_MODEL' not in os.environ:
        raise RuntimeError('The environment variable SPECSIM_MODEL must be set.')

    # Set the random seed.
    generator = np.random.RandomState(args.seed)

    # Initialize the simulation grid.
    #g_grid = np.linspace(22.0, 23.0, 5)
    #z_grid = np.linspace(0.5, 3.5, 31)

    # We use the QLF from the table to sample the quasars instead using a grid.
    g_grid, z_grid = sample(table2015, args.numsamples, seed=args.seed)
    if args.template is not None and args.template < 0:
        # Read the z=2.4, r=22.62 template from desimodel/data/spectra/
        args.template_file = 'spec-qso-z2.4-rmag22.62.dat'
        template_data = astropy.table.Table.read(args.template_file, format='ascii')
        wlen = template_data['WAVELENGTH']
        fluxes = template_data['FLUX'][np.newaxis, :]
        template_z = 2.4
        print('Using desimodel z={:.1f} QSO template.'.format(template_z))
    else:
        # Read the template file.
        templates = fitsio.FITS(args.template_file, mode='r')
        data = templates[1].read().view('>f8').reshape((40800,501))
        templates.close()
        wlen = data[:, 0]
        fluxes = data[:, 1:].transpose()
        if args.template is not None:
            fluxes = fluxes[args.template:args.template+1]
        template_z = 0.0

    num_templates = len(fluxes)
    print('Using {} z={:.1f} template(s) from {}.'
        .format(num_templates, template_z, args.template_file))

    # Extend each template so it can be redshifted over the range 0.5 - 3.5
    # and also be used to calculate magnitudes.
    wlen_min = wlen[0] * (1 + template_z) / (1 + z_grid[-1])
    wlen_max = max(10000, wlen[-1] * (1 + template_z) / (1 + z_grid[0]))
    # Extrapolate down to wlen_min
    flux_lo, flux_hi = None, None
    if wlen_min < wlen[0]:
        print('Extrapolate down to z={:.1f} at {:0f}A.'.format(z_grid[0], wlen_min))
        wlen_lo = np.linspace(wlen_min - 1, wlen[0] - 0.1, 10)
        flux_lo = np.empty_like(wlen_lo)
        wlen = np.hstack([wlen_lo, wlen])
    # Extrapolate up to wlen_max
    if wlen_max > wlen[-1]:
        print('Extrapolate up to z={:.1f} at {:0f}A.'.format(z_grid[-1], wlen_max))
        wlen_hi = np.linspace(wlen[-1] + 0.1, wlen_max + 1, 10)
        flux_hi = np.empty_like(wlen_hi)
        wlen = np.hstack([wlen, wlen_hi])
    # Prepare each template.
    templates = [ ]
    for flux in fluxes:
        # Use the average flux at each end of the spectrum for extraploation.
        # Combine the pieces.
        if flux_lo is not None:
            flux_lo[:] = np.mean(flux[:10])
            flux = np.hstack([flux_lo, flux])
        if flux_hi is not None:
            flux_hi[:] = np.mean(flux[-10:])
            flux = np.hstack([flux, flux_hi])
        template = specsim.spectrum.SpectralFluxDensity(wlen, flux)
        templates.append(template)

    # Create the default atmosphere for the requested sky conditions.
    atmosphere = specsim.atmosphere.Atmosphere(
        skyConditions='dark', basePath=os.environ['SPECSIM_MODEL'])

    # Create a quick simulator using the default instrument model.
    qsim = specsim.quick.Quick(
        atmosphere=atmosphere, basePath=os.environ['SPECSIM_MODEL'])

    # Specify the simulation wavelength grid to use (in Angstroms).
    qsim.setWavelengthGrid(3500.3, 9999.7, 0.1)

    bands = 'brz'
    num_cameras = len(bands)
    flux = None

    # Initialize down sampling of the 0.1A simulation grid to 1.0A
    downsampling = 10
    ndown = qsim.wavelengthGrid.size // downsampling

    # Allocate output arrays for data.
    num_spec = args.numsamples
    flux = np.zeros((num_cameras, num_spec, ndown))
    ivar = np.zeros_like(flux)
    wave = np.empty_like(flux)

    # Allocate output arrays for truth metadata.
    truth = astropy.table.Table(
        names=('TRUEZ', 'GBANDT', 'RBANDT', 'ZBANDT', 'W1BANDT',
        'W2BANDT', 'TMPID'), dtype=('f4', 'f4', 'f4', 'f4', 'f4', 'f4', 'i4'))

    # Loop over g-band magnitudes and redshifts.
    spec_index = 0
    for i in range(0,num_spec):
        g=g_grid[i]
        z=z_grid[i]
        print('Simulating g = {:.2f}'.format(g))
        # Pick a random template to use. We do not use np.random.choice()
        # for Julien's benefit.
        template_index = int(generator.uniform()*num_templates)
        template = templates[template_index]
        # Run the simulation.
        input_spectrum = (template
            .createRedshifted(newZ=z, oldZ=template_z)
            .createRescaled(sdssBand='g', abMagnitude=g))
        results = qsim.simulate(
            sourceType='qso', sourceSpectrum=input_spectrum,
            airmass=1.0, expTime=900., downsampling=downsampling)
        # Loop over cameras
        for camera in range(num_cameras):
            snr = (results.snr)[: ,camera]
            mask = (results.obsflux > 0) & (snr > 0)
            flux[camera, spec_index, mask] = results[mask].obsflux
            ivar[camera, spec_index, mask] = (snr[mask] / results[mask].obsflux)**2
            if not args.no_noise:
                flux[camera, spec_index, mask] += np.random.normal(
                    scale=ivar[camera, spec_index, mask]**-0.5)
            wave[camera, spec_index] = results.wave
        # Save the truth for this simulated QSO.
        tmpid = args.template or template_index
        truth.add_row(dict(
            TRUEZ=z, GBANDT=g, RBANDT=g, ZBANDT=g, W1BANDT=g,
            W2BANDT=g, TMPID=tmpid))
        spec_index += 1

    for camera, band in enumerate(bands):
        if args.write_bricks:
            write_brick.write_brick_file(
                band=band, brickname=args.brickname,
                Flux=flux[camera], InvVar=ivar[camera], Wavelength=wave[camera],
                Resolution=wave[camera], truth=truth)
        else:
            output = fitsio.FITS(args.prefix + band + '.fits', 'rw', clobber=True)
            output.write(flux[camera])
            output.write(ivar[camera])
            output.write(wave[camera])
            output.close()