def prepare_aat_catalog( target_catalog, write_to=None, verbose=True, flux_star_removal_threshold=20.0 * u.arcsec, flux_star_r_range=(17, 17.7), flux_star_gr_range=(0.1, 0.4), sky_fiber_void_radius=10.0 * u.arcsec, sky_fiber_needed=100, sky_fiber_max=1.1 * u.deg, sky_fiber_host_rvir_threshold=0.7 * u.deg, sky_fiber_radial_adjustment=2.0, targeting_score_threshold=900, seed=123, ): """ Prepare AAT target catalog. If the host's radius is less than `sky_fiber_host_rvir_threshold`, all sky fiber will be distributed between `sky_fiber_max` and host's radius. Otherwise, first fill the annulus between `sky_fiber_max` and host's radius, then distribute the rest within the host (but prefer outer region, as controlled by `sky_fiber_radial_adjustment`) Format needed: # TargetName(unique for header) RA(h m s) Dec(d m s) TargetType(Program,Fiducial,Sky) Priority(9 is highest) Magnitude 0 Notes 1237648721248518305 14 42 17.79 -0 12 05.95 P 2 22.03 0 magcol=fiber2mag_r, model_r=20.69 1237648721786045341 14 48 37.16 +0 21 33.81 P 1 21.56 0 magcol=fiber2mag_r, model_r=20.55 """ # pylint: disable=no-member if 'TARGETING_SCORE' not in target_catalog.colnames: return KeyError( '`target_catalog` does not have column "TARGETING_SCORE".' 'Have you run `compile_target_list` or `assign_targeting_score`?') if not isinstance(flux_star_removal_threshold, u.Quantity): flux_star_removal_threshold = flux_star_removal_threshold * u.arcsec if not isinstance(sky_fiber_void_radius, u.Quantity): sky_fiber_void_radius = sky_fiber_void_radius * u.arcsec if not isinstance(sky_fiber_max, u.Quantity): sky_fiber_max = sky_fiber_max * u.deg if not isinstance(sky_fiber_host_rvir_threshold, u.Quantity): sky_fiber_host_rvir_threshold = sky_fiber_host_rvir_threshold * u.deg host_ra = target_catalog['HOST_RA'][0] * u.deg host_dec = target_catalog['HOST_DEC'][0] * u.deg host_dist = target_catalog['HOST_DIST'][0] host_rvir = np.arcsin(0.3 / host_dist) * u.rad annulus_actual = (sky_fiber_max**2.0 - host_rvir**2.0) annulus_wanted = (sky_fiber_max**2.0 - sky_fiber_host_rvir_threshold**2.0) if annulus_actual < 0: raise ValueError( '`sky_fiber_max` too small, this host is larger than that!') if annulus_wanted < 0: raise ValueError( '`sky_fiber_max` must be larger than `sky_fiber_host_rvir_threshold`!' ) def _gen_dist_rand(seed_this, size): U = np.random.RandomState(seed_this).rand(size) return np.sqrt(U * annulus_actual + host_rvir**2.0) if annulus_actual < annulus_wanted: def gen_dist_rand(seed_this, size): size_out = int(np.around(size * annulus_actual / annulus_wanted)) size_in = size - size_out dist_rand_out = _gen_dist_rand(seed_this, size_out) index = (1.0 / (sky_fiber_radial_adjustment + 2.0)) dist_rand_in = (np.random.RandomState(seed_this + 1).rand(size_in) **index) * host_rvir return np.concatenate( [dist_rand_out.to_value("deg"), dist_rand_in.to_value("deg")]) * u.deg else: gen_dist_rand = _gen_dist_rand n_needed = sky_fiber_needed ra_sky = [] dec_sky = [] base_sc = SkyCoord(target_catalog['RA'], target_catalog['DEC'], unit='deg') while n_needed > 0: n_rand = int(np.ceil(n_needed * 1.1)) dist_rand = gen_dist_rand(seed, n_rand) theta_rand = np.random.RandomState(seed + 1).rand(n_rand) * (2.0 * np.pi) ra_rand = np.remainder(host_ra + dist_rand * np.cos(theta_rand), 360.0 * u.deg) dec_rand = host_dec + dist_rand * np.sin(theta_rand) ok_mask = (dec_rand >= -90.0 * u.deg) & (dec_rand <= 90.0 * u.deg) ra_rand = ra_rand[ok_mask] dec_rand = dec_rand[ok_mask] sky_sc = SkyCoord(ra_rand, dec_rand) sep = sky_sc.match_to_catalog_sky(base_sc)[1] ok_mask = (sep > sky_fiber_void_radius) n_needed -= np.count_nonzero(ok_mask) ra_sky.append(ra_rand[ok_mask].to_value("deg")) dec_sky.append(dec_rand[ok_mask].to_value("deg")) seed += np.random.RandomState(seed + 2).randint(100, 200) del ra_rand, dec_rand, sky_sc, sep, ok_mask del base_sc ra_sky = np.concatenate(ra_sky)[:sky_fiber_needed] dec_sky = np.concatenate(dec_sky)[:sky_fiber_needed] is_target = Query('TARGETING_SCORE >= 0', 'TARGETING_SCORE < {}'.format(targeting_score_threshold)) is_des = Query((lambda s: s == 'des', 'survey')) is_star = Query('morphology_info == 0', is_des) | Query( ~is_des, ~Query('is_galaxy')) is_flux_star = Query(is_star, 'r_mag >= {}'.format(flux_star_r_range[0]), 'r_mag < {}'.format(flux_star_r_range[1])) is_flux_star &= Query('gr >= {}'.format(flux_star_gr_range[0]), 'gr < {}'.format(flux_star_gr_range[1])) target_catalog = (is_target | is_flux_star).filter(target_catalog) target_catalog['Priority'] = target_catalog['TARGETING_SCORE'] // 100 target_catalog['Priority'][Query('Priority < 1').mask(target_catalog)] = 1 target_catalog['Priority'][Query('Priority > 8').mask(target_catalog)] = 8 target_catalog['Priority'] = 9 - target_catalog['Priority'] target_catalog['Priority'][is_flux_star.mask(target_catalog)] = 9 flux_star_indices = np.flatnonzero(is_flux_star.mask(target_catalog)) flux_star_sc = SkyCoord(*target_catalog[['RA', 'DEC' ]][flux_star_indices].itercols(), unit='deg') target_sc = SkyCoord(*is_target.filter(target_catalog)[['RA', 'DEC']].itercols(), unit='deg') sep = flux_star_sc.match_to_catalog_sky(target_sc)[1] target_catalog['Priority'][flux_star_indices[ sep < flux_star_removal_threshold]] = 0 target_catalog = Query('Priority > 0').filter(target_catalog) n_flux_star = Query('Priority == 9').count(target_catalog) del flux_star_indices, flux_star_sc, target_sc, sep target_catalog['TargetType'] = 'P' target_catalog['0'] = 0 target_catalog['Notes'] = 'targets' target_catalog['Notes'][is_flux_star.mask(target_catalog)] = 'flux' target_catalog.rename_column('DEC', 'Dec') target_catalog.rename_column('OBJID', 'TargetName') target_catalog.rename_column('r_mag', 'Magnitude') target_catalog.sort(['TARGETING_SCORE', 'Magnitude']) target_catalog = target_catalog[[ 'TargetName', 'RA', 'Dec', 'TargetType', 'Priority', 'Magnitude', '0', 'Notes' ]] sky_catalog = Table({ 'TargetName': np.arange(len(ra_sky)), 'RA': ra_sky, 'Dec': dec_sky, 'TargetType': np.repeat('S', len(ra_sky)), 'Priority': np.repeat(9, len(ra_sky)), 'Magnitude': np.repeat(99.0, len(ra_sky)), '0': np.repeat(0, len(ra_sky)), 'Notes': np.repeat('sky', len(ra_sky)), }) target_catalog = vstack([target_catalog, sky_catalog]) if verbose: print('# of flux stars =', n_flux_star) print('# of sky fibers =', len(sky_catalog)) for rank in range(1, 10): print('# of Priority={} targets ='.format(rank), Query('Priority == {}'.format(rank)).count(target_catalog)) if write_to: if verbose: print('Writing to {}'.format(write_to)) target_catalog.write( write_to, delimiter=' ', quotechar='"', format='ascii.fast_commented_header', overwrite=True, formats={ 'RA': lambda x: Angle(x, 'deg').wrap_at(360 * u.deg).to_string( 'hr', sep=' ', precision=2), # pylint: disable=E1101 'Dec': lambda x: Angle(x, 'deg').to_string( 'deg', sep=' ', precision=2), 'Magnitude': '%.2f', }) with open(write_to) as fh: content = fh.read() with open(write_to, 'w') as fh: fh.write(content.replace('"', '')) return target_catalog
def prepare_mmt_catalog(target_catalog, write_to=None, flux_star_removal_threshold=20.0, verbose=True): """ Prepare MMT target catalog. Parameters ---------- target_catalog : astropy.table.Table Need to have `TARGETING_SCORE` column. You can use `TargetSelection.build_target_catalogs` to generate `target_catalog` write_to : str, optional If set, it will write the catalog in MMT format to `write_to`. flux_star_removal_threshold : float, optional In arcseconds verbose : bool, optional Returns ------- mmt_target_catalog : astropy.table.Table Examples -------- >>> import SAGA >>> from SAGA.targets import prepare_mmt_catalog >>> saga_database = SAGA.Database('/path/to/SAGA/Dropbox') >>> saga_targets = SAGA.TargetSelection(saga_database, gmm_parameters='gmm_parameters_no_outlier') >>> mmt18_hosts = [161174, 52773, 163956, 69028, 144953, 165082, 165707, 145729, 165980, 147606] >>> for host_id, target_catalog in saga_targets.build_target_catalogs(mmt18_hosts, return_as='dict').items(): >>> print('Working host NSA', host_id) >>> SAGA.targets.prepare_mmt_catalog(target_catalog, '/home/yymao/Downloads/mmt_nsa{}.cat'.format(host_id)) >>> print() Notes ----- See https://www.cfa.harvard.edu/mmti/hectospec/hecto_software_manual.htm#4.1.1 for required format """ if 'TARGETING_SCORE' not in target_catalog.colnames: return KeyError( '`target_catalog` does not have column "TARGETING_SCORE".' 'Have you run `compile_target_list` or `assign_targeting_score`?') is_target = Query('TARGETING_SCORE >= 0', 'TARGETING_SCORE < 900') is_star = Query('PHOTPTYPE == 6') is_guide_star = is_star & Query('PSFMAG_R >= 14', 'PSFMAG_R < 15') is_flux_star = is_star & Query('PSFMAG_R >= 17', 'PSFMAG_R < 18') is_flux_star &= Query('PSFMAG_U - PSFMAG_G >= 0.6', 'PSFMAG_U - PSFMAG_G < 1.2') is_flux_star &= Query('PSFMAG_G - PSFMAG_R >= 0', 'PSFMAG_G - PSFMAG_R < 0.6') is_flux_star &= Query( '(PSFMAG_G - PSFMAG_R) > 0.75 * (PSFMAG_U - PSFMAG_G) - 0.45') target_catalog = (is_target | is_guide_star | is_flux_star).filter(target_catalog) target_catalog['rank'] = target_catalog['TARGETING_SCORE'] // 100 target_catalog['rank'][Query('rank < 2').mask( target_catalog)] = 2 # regular targets start at rank 2 target_catalog['rank'][is_flux_star.mask(target_catalog)] = 1 target_catalog['rank'][is_guide_star.mask( target_catalog)] = 99 # set to 99 for sorting flux_star_indices = np.flatnonzero(is_flux_star.mask(target_catalog)) flux_star_sc = SkyCoord(*target_catalog[['RA', 'DEC' ]][flux_star_indices].itercols(), unit='deg') target_sc = SkyCoord(*is_target.filter(target_catalog)[['RA', 'DEC']].itercols(), unit='deg') sep = flux_star_sc.match_to_catalog_sky(target_sc)[1] target_catalog['rank'][flux_star_indices[ sep.arcsec < flux_star_removal_threshold]] = 0 target_catalog = Query('rank > 0').filter(target_catalog) if verbose: print('# of guide stars =', is_guide_star.count(target_catalog)) print('# of flux stars =', is_flux_star.count(target_catalog)) print('# of rank>1 targets =', is_target.count(target_catalog)) for rank in range(1, 9): print('# of rank={} targets ='.format(rank), Query('rank == {}'.format(rank)).count(target_catalog)) target_catalog['type'] = 'TARGET' target_catalog['type'][is_guide_star.mask(target_catalog)] = 'guide' target_catalog.rename_column('RA', 'ra') target_catalog.rename_column('DEC', 'dec') target_catalog.rename_column('OBJID', 'object') target_catalog.rename_column('r_mag', 'mag') target_catalog.sort(['rank', 'TARGETING_SCORE', 'mag']) target_catalog = target_catalog[[ 'ra', 'dec', 'object', 'rank', 'type', 'mag' ]] if write_to: if verbose: print('Writing to {}'.format(write_to)) if not write_to.endswith('.cat'): print('Warning: filename should end with \'.cat\'') with open(write_to, 'w') as fh: fh.write('\t'.join(target_catalog.colnames) + '\n') # the MMT format is odd and *requires* "---"'s in the second header line fh.write('\t'.join(('-' * len(s) for s in target_catalog.colnames)) + '\n') target_catalog.write( fh, delimiter='\t', format='ascii.fast_no_header', formats={ 'ra': lambda x: Angle(x, 'deg').wrap_at(360 * u.deg).to_string( 'hr', sep=':', precision=3), # pylint: disable=E1101 'dec': lambda x: Angle(x, 'deg').to_string( 'deg', sep=':', precision=3), 'mag': '%.2f', 'rank': lambda x: '' if x == 99 else '{:d}'.format(x), }) return target_catalog