def test_agn_caching(self):
        """
        Test that the light curve caching in applyAgn does not change
        the outcomes of the delta_mag calculations.  We will do this
        by simulating the same AGN twice:  once using caching, once resetting
        the cache after every time step.  We will then verify that the two sets
        of outputs are identical.
        """

        rng = np.random.RandomState(8374)
        nn = 5
        var = ExtraGalacticVariabilityModels()

        seed_list = rng.random_integers(0, 20000, nn)
        toff_list = rng.random_sample(nn) * 10000.0 + 40000.0
        sfz_list = rng.random_sample(nn) * 2.0
        sfu_list = rng.random_sample(nn) * 2.0
        sfg_list = rng.random_sample(nn) * 2.0
        sfr_list = rng.random_sample(nn) * 2.0
        sfi_list = rng.random_sample(nn) * 2.0
        sfy_list = rng.random_sample(nn) * 2.0
        tau_list = rng.random_sample(nn) * 20.0 + 20.0

        param_list = []
        for ix in range(nn):
            params = {
                'agn_sfu': np.array([sfu_list[ix]]),
                'agn_sfg': np.array([sfg_list[ix]]),
                'agn_sfr': np.array([sfr_list[ix]]),
                'agn_sfi': np.array([sfi_list[ix]]),
                'agn_sfz': np.array([sfz_list[ix]]),
                'agn_sfy': np.array([sfy_list[ix]]),
                't0_mjd': np.array([toff_list[ix]]),
                'agn_tau': np.array([tau_list[ix]]),
                'seed': np.array([seed_list[ix]])
            }
            param_list.append(params)

        mjd_list = rng.random_sample(100) * 10000.0 + 50000.0
        mjd_list = np.sort(mjd_list)

        caching_output = []

        for mjd in mjd_list:
            for pp in param_list:
                dd = var.applyAgn(np.where(np.array([True])), pp, mjd)
                for ix in range(6):
                    caching_output.append(dd[ix])

        uncached_output = []
        for mjd in mjd_list:
            for pp in param_list:
                reset_agn_lc_cache()
                dd = var.applyAgn(np.where(np.array([True])), pp, mjd)
                for ix in range(6):
                    uncached_output.append(dd[ix])

        np.testing.assert_array_almost_equal(np.array(caching_output),
                                             np.array(uncached_output),
                                             decimal=10)
    def test_agn_many(self):
        agn_model = ExtraGalacticVariabilityModels()
        rng = np.random.RandomState(7153)
        params  = {}
        n_obj = 5
        params['agn_tau'] = rng.random_sample(n_obj)*100.0+100.0
        params['agn_sfu'] = rng.random_sample(n_obj)*2.0
        params['agn_sfg'] = rng.random_sample(n_obj)*2.0
        params['agn_sfr'] = rng.random_sample(n_obj)*2.0
        params['agn_sfi'] = rng.random_sample(n_obj)*2.0
        params['agn_sfz'] = rng.random_sample(n_obj)*2.0
        params['agn_sfy'] = rng.random_sample(n_obj)*2.0
        params['t0_mjd'] = 48000.0+rng.random_sample(n_obj)*5.0
        params['seed'] = rng.randint(0, 20000, size=n_obj)
        redshift_arr = rng.random_sample(n_obj)*5.0

        mjd_arr = np.sort(rng.random_sample(6)*3653.3+59580.0)
        n_time = len(mjd_arr)

        valid_dexes = [np.arange(n_obj, dtype=int)]
        dmag_vector = agn_model.applyAgn(valid_dexes, params, mjd_arr,
                                         redshift=redshift_arr)
        self.assertEqual(dmag_vector.shape, (6, n_obj,n_time))

        for i_time, mjd in enumerate(mjd_arr):
            dmag_test = agn_model.applyAgn(valid_dexes, params, mjd,
                                           redshift=redshift_arr)
            self.assertEqual(dmag_test.shape, (6, n_obj))
            for i_band in range(6):
                for i_obj in range(n_obj):
                    self.assertAlmostEqual(dmag_vector[i_band][i_obj][i_time],
                                           dmag_test[i_band][i_obj], 6,
                                           msg='failed on band %d obj %d time %d' % (i_band, i_obj, i_time))
示例#3
0
    def test_agn_many(self):
        agn_model = ExtraGalacticVariabilityModels()
        rng = np.random.RandomState(7153)
        params = {}
        n_obj = 5
        params['agn_tau'] = rng.random_sample(n_obj) * 100.0 + 100.0
        params['agn_sfu'] = rng.random_sample(n_obj) * 2.0
        params['agn_sfg'] = rng.random_sample(n_obj) * 2.0
        params['agn_sfr'] = rng.random_sample(n_obj) * 2.0
        params['agn_sfi'] = rng.random_sample(n_obj) * 2.0
        params['agn_sfz'] = rng.random_sample(n_obj) * 2.0
        params['agn_sfy'] = rng.random_sample(n_obj) * 2.0
        params['t0_mjd'] = 48000.0 + rng.random_sample(n_obj) * 5.0
        params['seed'] = rng.randint(0, 20000, size=n_obj)

        mjd_arr = np.sort(rng.random_sample(6) * 3653.3 + 59580.0)
        n_time = len(mjd_arr)

        valid_dexes = [np.arange(n_obj, dtype=int)]
        dmag_vector = agn_model.applyAgn(valid_dexes, params, mjd_arr)
        self.assertEqual(dmag_vector.shape, (6, n_obj, n_time))

        for i_time, mjd in enumerate(mjd_arr):
            dmag_test = agn_model.applyAgn(valid_dexes, params, mjd)
            self.assertEqual(dmag_test.shape, (6, n_obj))
            for i_band in range(6):
                for i_obj in range(n_obj):
                    self.assertEqual(dmag_vector[i_band][i_obj][i_time],
                                     dmag_test[i_band][i_obj],
                                     msg='failed on band %d obj %d time %d' %
                                     (i_band, i_obj, i_time))
def simulate_agn(galid_list, param_dict_list, output_dir, log_file, lock):
    evm = ExtraGalacticVariabilityModels()

    duration = 3654.0
    survey_start = 59580.0

    tot_steps = 0
    for param_dict in param_dict_list:
        tau = param_dict['pars']['agn_tau']
        dt = tau / 100.0
        local_steps = int(np.ceil(duration / dt))
        tot_steps += local_steps

    t_start = time.time()
    n_simulated_steps = 0
    for galid, param_dict in zip(galid_list, param_dict_list):

        tau = param_dict['pars']['agn_tau']
        dt = tau / 100.0
        mjd_arr = survey_start + np.arange(0.0, duration + 5.0 * dt, dt)

        params = {}
        for key in param_dict['pars']:
            params[key] = np.array([param_dict['pars'][key]])

        dmag_arr = evm.applyAgn(np.array([[0]]), params, mjd_arr)
        assert dmag_arr.shape == (6, 1, len(mjd_arr))

        out_name = os.path.join(output_dir, 'agn_%d_lc.txt' % galid)
        with open(out_name, 'w') as out_file:
            out_file.write('# ')
            for mag_name in ('u', 'g', 'r', 'i', 'z', 'y'):
                out_file.write('%e ' %
                               param_dict['pars']['agn_sf%s' % mag_name])
            out_file.write('\n')
            for i_time in range(len(mjd_arr)):
                out_file.write('%.6f ' % mjd_arr[i_time])
                for i_filter in range(6):
                    out_file.write('%.4f ' % (dmag_arr[i_filter][0][i_time]))
                out_file.write('\n')

        n_simulated_steps += len(mjd_arr)
        lock.acquire()
        with open(log_file, 'a') as out_file:
            elapsed = (time.time() - t_start) / 3600.0
            projected = tot_steps * elapsed / n_simulated_steps
            out_file.write(
                'simulated %d -- %d -- tau %e hrs elapsed %e proj %e\n' %
                (galid, len(mjd_arr), tau, elapsed, projected))
        lock.release()
示例#5
0
    def test_agn_many_some_invalid(self):
        """
        Test that the correct thing happens when some of the objects
        do not use the variability model.
        """
        agn_model = ExtraGalacticVariabilityModels()
        rng = np.random.RandomState(7153)
        params = {}
        n_obj = 5
        params['agn_tau'] = rng.random_sample(n_obj) * 100.0 + 100.0
        params['agn_sfu'] = rng.random_sample(n_obj) * 2.0
        params['agn_sfg'] = rng.random_sample(n_obj) * 2.0
        params['agn_sfr'] = rng.random_sample(n_obj) * 2.0
        params['agn_sfi'] = rng.random_sample(n_obj) * 2.0
        params['agn_sfz'] = rng.random_sample(n_obj) * 2.0
        params['agn_sfy'] = rng.random_sample(n_obj) * 2.0
        params['t0_mjd'] = 48000.0 + rng.random_sample(n_obj) * 5.0
        params['seed'] = rng.randint(0, 20000, size=n_obj)
        redshift_arr = rng.random_sample(n_obj) * 5.0

        mjd_arr = np.sort(rng.random_sample(6) * 3653.3 + 59580.0)
        n_time = len(mjd_arr)

        valid_dexes = [np.array([1, 2, 4])]
        dmag_vector = agn_model.applyAgn(valid_dexes,
                                         params,
                                         mjd_arr,
                                         redshift=redshift_arr)
        self.assertEqual(dmag_vector.shape, (6, n_obj, n_time))

        for i_time, mjd in enumerate(mjd_arr):
            dmag_test = agn_model.applyAgn(valid_dexes,
                                           params,
                                           mjd,
                                           redshift=redshift_arr)
            self.assertEqual(dmag_test.shape, (6, n_obj))
            for i_band in range(6):
                for i_obj in range(n_obj):
                    if i_obj not in (1, 2, 4):
                        self.assertEqual(dmag_test[i_band][i_obj], 0.0)
                    self.assertAlmostEqual(
                        dmag_vector[i_band][i_obj][i_time],
                        dmag_test[i_band][i_obj],
                        6,
                        msg='failed on band %d obj %d time %d' %
                        (i_band, i_obj, i_time))
    def test_agn_many_some_invalid(self):
        """
        Test that the correct thing happens when some of the objects
        do not use the variability model.
        """
        agn_model = ExtraGalacticVariabilityModels()
        rng = np.random.RandomState(7153)
        params  = {}
        n_obj = 5
        params['agn_tau'] = rng.random_sample(n_obj)*100.0+100.0
        params['agn_sfu'] = rng.random_sample(n_obj)*2.0
        params['agn_sfg'] = rng.random_sample(n_obj)*2.0
        params['agn_sfr'] = rng.random_sample(n_obj)*2.0
        params['agn_sfi'] = rng.random_sample(n_obj)*2.0
        params['agn_sfz'] = rng.random_sample(n_obj)*2.0
        params['agn_sfy'] = rng.random_sample(n_obj)*2.0
        params['t0_mjd'] = 48000.0+rng.random_sample(n_obj)*5.0
        params['seed'] = rng.randint(0, 20000, size=n_obj)
        redshift_arr = rng.random_sample(n_obj)*5.0

        mjd_arr = np.sort(rng.random_sample(6)*3653.3+59580.0)
        n_time = len(mjd_arr)

        valid_dexes = [np.array([1,2,4])]
        dmag_vector = agn_model.applyAgn(valid_dexes, params, mjd_arr,
                                         redshift=redshift_arr)
        self.assertEqual(dmag_vector.shape, (6, n_obj,n_time))

        for i_time, mjd in enumerate(mjd_arr):
            dmag_test = agn_model.applyAgn(valid_dexes, params, mjd,
                                           redshift=redshift_arr)
            self.assertEqual(dmag_test.shape, (6, n_obj))
            for i_band in range(6):
                for i_obj in range(n_obj):
                    if i_obj not in (1,2,4):
                        self.assertEqual(dmag_test[i_band][i_obj], 0.0)
                    self.assertAlmostEqual(dmag_vector[i_band][i_obj][i_time],
                                           dmag_test[i_band][i_obj], 6,
                                           msg='failed on band %d obj %d time %d' % (i_band, i_obj, i_time))
def validate_agn_mags(cat_dir,
                      obsid,
                      agn_db,
                      opsim_db=os.path.join(
                          '/global/projecta/projectdirs/lsst',
                          'groups/SSim/DC2/',
                          'minion_1016_desc_dithered_v4_sfd.db')):
    """
    Parameters
    ----------
    cat_dir is the parent dir of $obsid

    obsid is the obsHistID of the pointing (an int)

    agn_db is the database of AGN parameters

    opsim_db is the path to the cadence database
    """
    if not os.path.isfile(agn_db):
        raise RuntimeError('\n%s\nis not a file\n' % agn_db)

    inst_cat_dir = os.path.join(cat_dir, '%.8d' % obsid)
    if not os.path.isdir(inst_cat_dir):
        raise RuntimeError('\n%s\nis not a dir\n' % inst_cat_dir)

    agn_name = os.path.join(inst_cat_dir, 'agn_gal_cat_%d.txt.gz' % obsid)
    if not os.path.isfile(agn_name):
        raise RuntimeError('\n%s\nis not a file\n' % agn_name)

    phosim_name = os.path.join(inst_cat_dir, 'phosim_cat_%d.txt' % obsid)
    if not os.path.isfile(agn_name):
        raise RuntimeError('\n%s\nis not a file\n' % phosim_name)

    bandpass = None
    vistime = None
    with open(phosim_name, 'r') as in_file:
        for line in in_file:
            params = line.strip().split()
            if params[0] == 'filter':
                bandpass = int(params[1])
            elif params[0] == 'vistime':
                vistime = float(params[1])

            if (bandpass is not None and vistime is not None):

                break

    if bandpass is None:
        raise RuntimeError("Did not read bandpass")

    if vistime is None:
        raise RuntimeError("Did not read vistime")

    if not os.path.isfile(opsim_db):
        raise RuntimeError('\n%s\nis not a file' % opsim_db)

    with sqlite3.connect(opsim_db) as conn:
        c = conn.cursor()
        r = c.execute('SELECT expMJD, descDitheredRA, descDitheredDec '
                      'FROM Summary WHERE obsHistID==%d' % obsid).fetchall()
        mjd = float(r[0][0])
        pointing_ra = float(r[0][1])
        pointing_dec = float(r[0][2])

    agn_colnames = [
        'obj', 'uniqueID', 'ra', 'dec', 'magnorm', 'sed', 'redshift', 'g1',
        'g2', 'kappa', 'dra', 'ddec', 'src_type', 'dust_rest', 'dust_obs',
        'obs_av', 'obs_rv'
    ]

    agn_col_types = {
        'ra': float,
        'dec': float,
        'magnorm': float,
        'redshift': float,
        'sed': bytes,
        'uniqueID': int
    }

    agn_df = pd.read_csv(agn_name,
                         delimiter=' ',
                         compression='gzip',
                         names=agn_colnames,
                         dtype=agn_col_types,
                         nrows=None)

    agn_df['galaxy_id'] = pd.Series(agn_df['uniqueID'] // 1024,
                                    index=agn_df.index)

    vv = np.array([
        np.cos(pointing_dec) * np.cos(pointing_ra),
        np.cos(pointing_dec) * np.sin(pointing_ra),
        np.sin(pointing_dec)
    ])
    hp_list = healpy.query_disc(32,
                                vv,
                                np.radians(2.2),
                                nest=False,
                                inclusive=True)

    chunk_size = 10000
    agn_gid = []
    agn_magnorm = []
    agn_varParamStr = []
    with sqlite3.connect(agn_db) as agn_params_conn:
        agn_params_cursor = agn_params_conn.cursor()
        query = 'SELECT galaxy_id, magNorm, varParamStr FROM agn_params'
        agn_query = agn_params_cursor.execute(query)
        agn_chunk = agn_query.fetchmany(size=chunk_size)
        while len(agn_chunk) > 0:
            agn_chunk = np.array(agn_chunk).transpose()
            chunk_gid = agn_chunk[0].astype(int)
            chunk_magnorm = agn_chunk[1].astype(float)
            chunk_varParamStr = agn_chunk[2]
            valid_agn = np.where(np.in1d(chunk_gid,
                                         agn_df['galaxy_id'].values))
            agn_gid.append(chunk_gid[valid_agn])
            agn_magnorm.append(chunk_magnorm[valid_agn])
            agn_varParamStr.append(chunk_varParamStr[valid_agn])
            agn_chunk = agn_query.fetchmany(size=chunk_size)

    agn_gid = np.concatenate(agn_gid)
    agn_magnorm = np.concatenate(agn_magnorm)
    agn_varParamStr = np.concatenate(agn_varParamStr)
    print('sql gave %d agn' % len(agn_gid))

    sorted_dex = np.argsort(agn_gid)
    agn_gid = agn_gid[sorted_dex]
    agn_magnorm = agn_magnorm[sorted_dex]
    agn_varParamStr = agn_varParamStr[sorted_dex]

    instcat_gid = agn_df['galaxy_id'].values
    instcat_magnorm = agn_df['magnorm'].values
    instcat_z = agn_df['redshift'].values

    valid = np.where(instcat_gid < 1.0e11)
    instcat_gid = instcat_gid[valid]
    instcat_magnorm = instcat_magnorm[valid]
    instcat_z = instcat_z[valid]
    sorted_dex = np.argsort(instcat_gid)
    instcat_gid = instcat_gid[sorted_dex]
    instcat_magnorm = instcat_magnorm[sorted_dex]
    instcat_z = instcat_z[sorted_dex]

    cat = GCRCatalogs.load_catalog('cosmoDC2_v1.1.4_image')

    cat_q = {}
    cat_q['galaxy_id'] = []
    cat_q['redshift_true'] = []
    for hp in hp_list:
        hp_query = GCRQuery('healpix_pixel==%d' % hp)

        local_q = cat.get_quantities(['galaxy_id', 'redshift_true'],
                                     native_filters=[hp_query])

        valid = np.in1d(local_q['galaxy_id'], agn_df['galaxy_id'])
        if valid.any():
            for k in cat_q:
                cat_q[k].append(local_q[k][valid])

    for k in cat_q:
        cat_q[k] = np.concatenate(cat_q[k])

    print('we have %d agn' % len(cat_q['galaxy_id']))

    sorted_dex = np.argsort(cat_q['galaxy_id'])
    for k in cat_q:
        cat_q[k] = cat_q[k][sorted_dex]

    if not np.array_equal(cat_q['galaxy_id'], instcat_gid):
        msg = "GCR gid not equal to InstCat\n"
        msg += "len gcr %d\n" % len(cat_q['galaxy_id'])
        msg += "len instcat %d\n" % len(instcat_gid)
        msg += "other comparison %s\n" % str(
            np.array_equal(instcat_gid, agn_gid))
        raise RuntimeError(msg)

    if not np.array_equal(instcat_gid, agn_gid):
        raise RuntimeError("galaxy_id arrays are not equal")

    if len(instcat_gid) == 0:
        raise RuntimeError("no AGN to test")

    agn_params = None
    for var in agn_varParamStr:
        var_dict = json.loads(var)
        if agn_params is None:
            agn_params = {}
            for k in var_dict['p']:
                agn_params[k] = []
        for k in var_dict['p']:
            agn_params[k].append(var_dict['p'][k])

    for k in agn_params:
        agn_params[k] = np.array(agn_params[k])

    agn_simulator = ExtraGalacticVariabilityModels()
    agn_simulator._agn_threads = 3
    d_mag = agn_simulator.applyAgn([np.arange(len(agn_gid), dtype=int)],
                                   agn_params,
                                   mjd,
                                   redshift=cat_q['redshift_true'])

    d_mag_instcat = instcat_magnorm - agn_magnorm
    error = np.abs(d_mag[bandpass] - d_mag_instcat)
    max_error = error.max()
    violation = np.where(error > 1.0e-5)
    for ii in violation[0]:
        print("%e -- %e %e %e" % (error[ii], d_mag[bandpass][ii],
                                  d_mag_instcat[ii], instcat_magnorm[ii]))

        for k in agn_params:
            print('    %s: %e' % (k, agn_params[k][ii]))

    valid = np.where(error <= 1.0e-5)
    d_mag_valid = d_mag_instcat[valid]
    mag_valid = instcat_magnorm[valid]

    if np.max(error) > 1.0e-5:
        raise RuntimeError("\n%s\nAGN validation failed: max mag error %e" %
                           (agn_name, max_error))
示例#8
0
def process_agn_chunk(chunk, filter_obs, mjd_obs, m5_obs,
                      coadd_m5, m5_single,
                      obs_md_list, proper_chip, out_data,
                      lock):
    t_start_chunk = time.time()
    #print('processing %d' % len(chunk))
    ct_first = 0
    ct_at_all = 0
    ct_tot = 0

    n_t = len(filter_obs)
    n_obj = len(chunk)

    agn_model = ExtraGalacticVariabilityModels()
    dust_model = EBVbase()

    with h5py.File('data/ebv_grid.h5', 'r') as in_file:
       ebv_grid = in_file['ebv_grid'].value
       extinction_grid = in_file['extinction_grid'].value

    coadd_visits = {}
    coadd_visits['u'] = 6
    coadd_visits['g'] = 8
    coadd_visits['r'] = 18
    coadd_visits['i'] = 18
    coadd_visits['z'] = 16
    coadd_visits['y'] = 16

    gamma_coadd = {}
    for bp in 'ugrizy':
        gamma_coadd[bp] = None

    gamma_single = {}
    for bp in 'ugrizy':
       gamma_single[bp] = [None]*n_t

    params = {}
    params['agn_sfu'] = chunk['agn_sfu']
    params['agn_sfg'] = chunk['agn_sfg']
    params['agn_sfr'] = chunk['agn_sfr']
    params['agn_sfi'] = chunk['agn_sfi']
    params['agn_sfz'] = chunk['agn_sfz']
    params['agn_sfy'] = chunk['agn_sfy']
    params['agn_tau'] = chunk['agn_tau']
    params['seed'] = chunk['id']+1

    ebv = dust_model.calculateEbv(equatorialCoordinates=np.array([np.radians(chunk['ra']),
                                                                  np.radians(chunk['dec'])]),
                                  interp=True)

    for i_bp, bp in enumerate('ugrizy'):
        extinction_values = np.interp(ebv, ebv_grid, extinction_grid[i_bp])
        chunk['%s_ab' % bp] += extinction_values
        chunk['AGNLSST%s' % bp] += extinction_values

    dmag = agn_model.applyAgn(np.where(np.array([True]*len(chunk))),
                              params, mjd_obs,
                              redshift=chunk['redshift'])

    dmag_mean = np.mean(dmag, axis=2)
    assert dmag_mean.shape == (6,n_obj)

    dummy_sed = Sed()
    lsst_bp = BandpassDict.loadTotalBandpassesFromFiles()
    flux_gal = np.zeros((6,n_obj), dtype=float)
    flux_agn_q = np.zeros((6,n_obj), dtype=float)
    flux_coadd = np.zeros((6,n_obj), dtype=float)
    mag_coadd = np.zeros((6,n_obj), dtype=float)
    snr_coadd = np.zeros((6,n_obj), dtype=float)
    snr_single = {}
    snr_single_mag_grid = np.arange(14.0, 30.0, 0.05)

    phot_params_single = PhotometricParameters(nexp=1,
                                               exptime=30.0)

    t_start_snr = time.time()

    for i_bp, bp in enumerate('ugrizy'):
        phot_params_coadd = PhotometricParameters(nexp=1,
                                                  exptime=30.0*coadd_visits[bp])

        flux_gal[i_bp] = dummy_sed.fluxFromMag(chunk['%s_ab' % bp])
        flux_agn_q[i_bp] = dummy_sed.fluxFromMag(chunk['AGNLSST%s' % bp] +
                                                 dmag_mean[i_bp,:])
        flux_coadd[i_bp] = flux_gal[i_bp]+flux_agn_q[i_bp]
        mag_coadd[i_bp] = dummy_sed.magFromFlux(flux_coadd[i_bp])

        (snr_coadd[i_bp],
         gamma) = SNR.calcSNR_m5(mag_coadd[i_bp],
                                 lsst_bp[bp],
                                 coadd_m5[bp],
                                 phot_params_coadd)


        (snr_single[bp],
         gamma) = SNR.calcSNR_m5(snr_single_mag_grid,
                                 lsst_bp[bp],
                                 m5_single[bp],
                                 phot_params_single)

    #print('got all snr in %e' % (time.time()-t_start_snr))


    t_start_obj = time.time()
    photometry_mask = np.zeros((n_obj, n_t), dtype=bool)
    photometry_mask_1d = np.zeros(n_obj, dtype=bool)
    snr_arr = np.zeros((n_obj, n_t), dtype=float)
    for i_bp, bp in enumerate('ugrizy'):
        valid_obs = np.where(filter_obs==i_bp)
        n_bp = len(valid_obs[0])
        if n_bp == 0:
            continue
        mag0_arr = chunk['AGNLSST%s' % bp]
        dmag_bp = dmag[i_bp][:,valid_obs[0]]
        assert dmag_bp.shape == (n_obj, n_bp)
        agn_flux_tot = dummy_sed.fluxFromMag(mag0_arr[:,None]+dmag_bp)
        q_flux = flux_agn_q[i_bp]
        agn_dflux = np.abs(agn_flux_tot-q_flux[:,None])
        flux_tot = flux_gal[i_bp][:, None] + agn_flux_tot
        assert flux_tot.shape == (n_obj, n_bp)
        mag_tot = dummy_sed.magFromFlux(flux_tot)
        snr_single_val = np.interp(mag_tot,
                                   snr_single_mag_grid,
                                   snr_single[bp])

        noise_coadd = flux_coadd[i_bp]/snr_coadd[i_bp]
        noise_single = flux_tot/snr_single_val
        noise = np.sqrt(noise_coadd[:,None]**2 + noise_single**2)
        dflux_thresh = 5.0*noise
        detected = (agn_dflux>=dflux_thresh)
        assert detected.shape == (n_obj, n_bp)
        snr_arr[:,valid_obs[0]] = agn_dflux/noise
        for i_obj in range(n_obj):
            if detected[i_obj].any():
                photometry_mask_1d[i_obj] = True
                photometry_mask[i_obj, valid_obs[0]] = detected[i_obj]

    t_before_chip = time.time()
    chip_mask = apply_focal_plane(chunk['ra'], chunk['dec'],
                                  photometry_mask_1d, obs_md_list,
                                  filter_obs, proper_chip)
    duration = (time.time()-t_before_chip)/3600.0

    unq_out = -1*np.ones(n_obj, dtype=int)
    mjd_out = -1.0*np.ones(n_obj, dtype=float)
    snr_out = -1.0*np.ones(n_obj, dtype=float)
    for i_obj in range(n_obj):
        if photometry_mask_1d[i_obj]:
            detected = photometry_mask[i_obj,:] & chip_mask[i_obj,:]

            if detected.any():
                unq_out[i_obj] = chunk['galtileid'][i_obj]
                first_dex = np.where(detected)[0].min()
                mjd_out[i_obj] = mjd_obs[first_dex]
                snr_out[i_obj] = snr_arr[i_obj, first_dex]

    valid = np.where(unq_out>=0)
    unq_out = unq_out[valid]
    mjd_out = mjd_out[valid]
    snr_out = snr_out[valid]
    with lock:
        existing_keys = list(out_data.keys())
        if len(existing_keys) == 0:
            key_val = 0
        else:
            key_val = max(existing_keys)
        while key_val in existing_keys:
            key_val += 1
        out_data[key_val] = (None, None, None)

    out_data[key_val] = (unq_out, mjd_out, snr_out)