def tile_region(skymap_path, credzone=0.9, tile_area=1, log=None): if log is None: log = logging.getLogger(__name__) # Read the HEALPix sky map: try: prob, dist_mu, dist_sigma, dist_norm = hp.read_map(skymap_path, field=None, verbose=False) except Exception as e: log.error('Failed to read sky map!') send_mail(subject="[GW@Wise] Failed to read LVC sky map", text='''FITS file: {} Exception: {}'''.format(skymap_path, e), log=log) sort_idx = np.flipud(np.argsort(prob, kind="stable")) sorted_credible_levels = np.cumsum(prob[sort_idx]) credible_levels = np.empty_like(sorted_credible_levels) credible_levels[sort_idx] = sorted_credible_levels # pixels inside the credible zone good_pix = sort_idx[0:np.sum(credible_levels <= credzone)] npix = len(prob) nside = hp.npix2nside(npix) theta, phi = hp.pix2ang(nside, good_pix) sky_area = 4 * 180**2 / np.pi # [deg^2] nside_obs = int(np.ceil(np.sqrt(sky_area / tile_area / 12))) obs_pix, unique_idx = np.unique(hp.ang2pix(nside_obs, theta, phi), return_inverse=True) probability = np.bincount(unique_idx, weights=prob[good_pix]) # sort by descending probability sort_idx = np.flipud(np.argsort(probability, kind="stable")) probability = probability[sort_idx] obs_pix = obs_pix[sort_idx] theta, phi = hp.pix2ang(nside_obs, obs_pix) ra = Angle(np.rad2deg(phi) * u.deg) dec = Angle(np.rad2deg(0.5 * np.pi - theta) * u.deg) return ra, dec, probability
def process_gcn(payload, root): alerts_path = config.get('ALERT FILES', 'PATH') # event alert file path fits_path = config.get('EVENT FILES', 'PATH') # event FITS file path is_test = config.getboolean('GENERAL', 'TEST') if config.has_option( 'GENERAL', 'TEST') else False # Respond only to 'test'/'observation' events if is_test: role = 'test' else: role = 'observation' if root.attrib['role'] != role: logging.info('Not {}, aborting.'.format(role)) return ivorn = root.attrib['ivorn'] filename = ntpath.basename(ivorn).split('#')[1] log = init_log(filename) # Is retracted? if gcn.handlers.get_notice_type(root) == gcn.notice_types.LVC_RETRACTION: # Save alert to file with open(alerts_path + filename + '.xml', "wb") as f: f.write(payload) log.info("Event {} retracted, doing nothing.".format(ivorn)) send_mail( subject="[GW@Wise] LVC event retracted", text="Attached GCN/LVC retraction {} received, doing nothing.". format(ivorn), files=[alerts_path + filename + '.xml']) return v = vp.loads(payload) # Read all of the VOEvent parameters from the "What" section params = { elem.attrib['name']: elem.attrib['value'] for elem in v.iterfind('.//Param') } # Respond only to 'CBC' (compact binary coalescence candidates) events. # Change 'CBC' to 'Burst' to respond to only unmodeled burst events. if params['Group'] != 'CBC': log.info('Not CBC, aborting.') return # Save alert to file with open(alerts_path + filename + '.xml', "wb") as f: f.write(payload) log.info("GCN/LVC alert {} received, started processing.".format(ivorn)) # Read VOEvent attributes keylist = ['ivorn', 'role', 'version'] for key in keylist: params[key] = v.attrib[key] # Read Who params['author_ivorn'] = v.Who.Author.contactName params['date_ivorn'] = v.Who.Date # Read WhereWhen params[ 'observatorylocation_id'] = v.WhereWhen.ObsDataLocation.ObservatoryLocation.attrib[ 'id'] params[ 'astrocoordsystem_id'] = v.WhereWhen.ObsDataLocation.ObservationLocation.AstroCoordSystem.attrib[ 'id'] params[ 'isotime'] = v.WhereWhen.ObsDataLocation.ObservationLocation.AstroCoords.Time.TimeInstant.ISOTime # Read How description = "" for item in v.How.iterfind('Description'): description = description + ", " + item params['how_description'] = description # Insert VOEvent to the database mysql_update.insert_voevent('voevent_lvc', params, log) # Send alert email send_mail( subject="[GW@Wise] LVC alert received", text="Attached GCN/LVC alert {} received, started processing.".format( ivorn), files=[alerts_path + filename + '.xml']) # Download the HEALPix sky map FITS file. tmp_path = download_file(params['skymap_fits']) skymap_path = fits_path + filename + "_" + ntpath.basename( params['skymap_fits']) shutil.move(tmp_path, skymap_path) # Create the galaxy list galaxies, ra, dec = galaxy_list.find_galaxy_list(skymap_path, log=log) # Create Wise plan wise.process_galaxy_list(galaxies, filename=ivorn.split('/')[-1], ra_event=ra, dec_event=dec, log=log) # Finish and delete logger log.info("Done.") close_log(log)
def find_galaxy_list(skymap_path, log=None, completeness=completenessp, credzone=0.99, relaxed_credzone=0.99995): if log is None: log = logging.getLogger(__name__) # Read the HEALPix sky map: try: prob, dist_mu, dist_sigma, dist_norm = hp.read_map(skymap_path, field=None, verbose=False) except Exception as e: log.error('Failed to read sky map!') send_mail(subject="[GW@Wise] Failed to read LVC sky map", text='''FITS file: {} Exception: {}'''.format(skymap_path, e)) # Load the galaxy catalog (glade_id, RA, DEC, distance, Bmag): galaxy_cat = np.load(cat_file) galaxy_cat = Table(galaxy_cat, names=('ID', 'RA', 'Dec', 'Dist', 'Bmag')) galaxy_cat = galaxy_cat[np.where(galaxy_cat['Dist'] > 0)] # remove entries with a negative distance galaxy_cat = galaxy_cat[np.where(~np.isnan(galaxy_cat['Bmag']))] # remove entries with no Bmag # Skymap parameters: npix = len(prob) nside = hp.npix2nside(npix) # Convert galaxy WCS (RA, DEC) to spherical coordinates (theta, phi): theta = 0.5 * np.pi - np.deg2rad(galaxy_cat['Dec']) phi = np.deg2rad(galaxy_cat['RA']) d = np.array(galaxy_cat['Dist']) # Convert galaxy coordinates to skymap pixels: galaxy_pix = hp.ang2pix(nside, theta, phi) # Most probable sky location theta_maxprob, phi_maxprob = hp.pix2ang(nside, np.argmax(prob)) ra_maxprob = np.rad2deg(phi_maxprob) dec_maxprob = np.rad2deg(0.5*np.pi - theta_maxprob) # Convert to coordinates ra_maxprob = Angle(ra_maxprob * u.deg) dec_maxprob = Angle(dec_maxprob * u.deg) # Find given percent probability zone (default is 99%): prob_cutoff = 1 prob_sum = 0 npix_credzone = 0 prob_sorted = np.sort(prob, kind="stable") while prob_sum < credzone: prob_sum = prob_sum + prob_sorted[-1] prob_cutoff = prob_sorted[-1] prob_sorted = prob_sorted[:-1] npix_credzone = npix_credzone + 1 # area = npix_credzone * hp.nside2pixarea(nside, degrees=True) #################################################### # calculate probability for galaxies by the localization map: p = prob[galaxy_pix] p_dist = dist_norm[galaxy_pix] * norm(dist_mu[galaxy_pix], dist_sigma[galaxy_pix]).pdf(d) # cutoffs - 99% of probability by angles and 3sigma by distance: within_dist_idx = np.where(np.abs(d - dist_mu[galaxy_pix]) < nsigmas_in_d*dist_sigma[galaxy_pix]) within_credzone_idx = np.where(p >= prob_cutoff) within_idx = np.intersect1d(within_credzone_idx, within_dist_idx) do_mass_cutoff = True # Relax credzone limits if no galaxies are found: if within_idx.size == 0: while prob_sum < relaxed_credzone: if prob_sorted.size == 0: break prob_sum = prob_sum + prob_sorted[-1] prob_cutoff = prob_sorted[-1] prob_sorted = prob_sorted[:-1] npix_credzone = npix_credzone + 1 within_dist_idx = np.where(np.abs(d - dist_mu[galaxy_pix]) < relaxed_nsigmas_in_d * dist_sigma[galaxy_pix]) within_credzone_idx = np.where(p >= prob_cutoff) within_idx = np.intersect1d(within_credzone_idx, within_dist_idx) do_mass_cutoff = False p = p[within_idx] p = (p * (p_dist[within_idx])) # d**2? galaxy_cat = galaxy_cat[within_idx] if len(galaxy_cat) == 0: log.warning("No galaxies in field!") log.warning("99.995% of probability is ", npix_credzone*hp.nside2pixarea(nside, degrees=True), "deg^2") log.warning("Peaking at (deg) RA = {}, Dec = {}".format( ra_maxprob.to_string(unit=u.hourangle, sep=':', precision=2, pad=True), dec_maxprob.to_string(sep=':', precision=2, alwayssign=True, pad=True))) return # Normalize luminosity to account for mass: luminosity = mag.L_nu_from_magAB(galaxy_cat['Bmag'] - 5 * np.log10(galaxy_cat['Dist'] * (10 ** 5))) luminosity_norm = luminosity / np.sum(luminosity) normalization = np.sum(p * luminosity_norm) score = p * luminosity_norm / normalization # Take 50% of mass: # The area under the Schechter function between L=inf and the brightest galaxy in the field: missing_piece = gammaincc(alpha + 2, 10 ** (-(min(galaxy_cat['Bmag'] - 5*np.log10(galaxy_cat['Dist']*(10**5))) - MB_star) / 2.5)) # there are no galaxies brighter than this in the field, so don't count that part of the Schechter function while do_mass_cutoff: MB_max = MB_star + 2.5 * np.log10(gammaincinv(alpha + 2, completeness + missing_piece)) if (min(galaxy_cat['Bmag'] - 5*np.log10(galaxy_cat['Dist']*(10**5))) - MB_star) > 0: MB_max = 100 # if the brightest galaxy in the field is fainter than the cutoff brightness - don't cut by brightness brightest = np.where(galaxy_cat['Bmag'] - 5*np.log10(galaxy_cat['Dist']*(10**5)) < MB_max) # print MB_max if len(brightest[0]) < min_galaxies: # Not enough galaxies, allowing fainter galaxies if completeness >= 0.9: # Tried hard enough, just take all of them completeness = 1 # Just to be consistent do_mass_cutoff = False else: completeness = (completeness + (1. - completeness) / 2) else: # got enough galaxies galaxy_cat = galaxy_cat[brightest] p = p[brightest] luminosity_norm = luminosity_norm[brightest] score = score[brightest] do_mass_cutoff = False # Account for the distance absolute_sensitivity = sensitivity - 5 * np.log10(galaxy_cat['Dist'] * (10 ** 5)) absolute_sensitivity_lum = mag.f_nu_from_magAB(absolute_sensitivity) distance_factor = np.zeros(len(galaxy_cat)) distance_factor[:] = ((maxL - absolute_sensitivity_lum) / (maxL - minL)) distance_factor[min_dist_factor > (maxL - absolute_sensitivity_lum) / (maxL - minL)] = min_dist_factor distance_factor[absolute_sensitivity_lum < minL] = 1 distance_factor[absolute_sensitivity > maxL] = min_dist_factor # Sort galaxies by probability ranking_idx = np.argsort(p*luminosity_norm*distance_factor, kind="stable")[::-1] # # Count galaxies that constitute 50% of the probability (~0.5*0.98) # sum = 0 # galaxies50per = 0 # sum_seen = 0 # while sum < 0.5: # if galaxies50per >= len(ranking_idx): # break # sum = sum + (p[ranking_idx[galaxies50per]] * luminosity_norm[ranking_idx[galaxies50per]]) / float(normalization) # sum_seen = sum_seen + (p[ranking_idx[galaxies50per]] * luminosity_norm[ranking_idx[galaxies50per]] * distance_factor[ranking_idx[galaxies50per]]) / float(normalization) # galaxies50per = galaxies50per + 1 # # # Event statistics: # # Ngalaxies_50percent = the number of galaxies consisting 50% of probability (including luminosity but not distance factor) # # actual_percentage = usually around 50 # # seen_percentage = if we include the distance factor - how much do the same galaxies worth # # 99percent_area = area of map in [deg^2] consisting 99% (using only the map from LIGO) # stats = {"Ngalaxies_50percent": galaxies50per, "actual_percentage": sum*100, "seen_percentage": sum_seen, "99percent_area": area} # Limit the maximal number of galaxies to use: if len(ranking_idx) > max_galaxies: n = max_galaxies else: n = len(ranking_idx) # Create sorted galaxy list (glade_id, RA, DEC, distance(Mpc), Bmag, score, distance factor (between 0-1)) # The score is normalized so that all the galaxies in the field sum to 1 (before applying luminosity cutoff) galaxylist = np.ndarray((n, 7)) for i in range(ranking_idx.shape[0])[:n]: ind = ranking_idx[i] galaxylist[i, :] = [galaxy_cat[ind]['ID'], galaxy_cat[ind]['RA'], galaxy_cat[ind]['Dec'], galaxy_cat[ind]['Dist'], galaxy_cat[ind]['Bmag'], score[ind], distance_factor[ind]] # Update galaxy table in SQL database: lvc_galaxy_dict = {'voeventid': '(SELECT MAX(id) from voevent_lvc)', 'score': score[ind], 'gladeid': galaxy_cat[ind]['ID']} mysql_update.insert_values('lvc_galaxies', lvc_galaxy_dict) return galaxylist, ra_maxprob, dec_maxprob
def process_gcn(payload, root): alerts_path = config.get('ALERT FILES', 'PATH') # event alert file path fits_path = config.get('EVENT FILES', 'PATH') # event FITS file path is_test = config.getboolean('GENERAL', 'TEST') if config.has_option( 'GENERAL', 'TEST') else False # Respond only to 'test'/'observation' events if is_test: role = 'test' else: role = 'observation' if root.attrib['role'] != role: logging.info('Not {}, aborting.'.format(role)) return ivorn = root.attrib['ivorn'] filename = ntpath.basename(ivorn).split('#')[1] log = init_log(filename) # Is retracted? if gcn.handlers.get_notice_type(root) == gcn.notice_types.LVC_RETRACTION: # Save alert to file with open(alerts_path + filename + '.xml', "wb") as f: f.write(payload) log.info("Event {} retracted, doing nothing.".format(filename)) send_mail(subject="[GW@Wise] {}".format(filename.split('-')[0]), text="GCN/LVC retraction {} received, doing nothing.".format( filename), html=format_html("<b>Alert retracted.</b><br>"), files=[alerts_path + filename + '.xml']) return v = vp.loads(payload) # Read all of the VOEvent parameters from the "What" section params = { elem.attrib['name']: elem.attrib['value'] for elem in v.iterfind('.//Param') } # Respond only to 'CBC' (compact binary coalescence candidates) events. # Change 'CBC' to 'Burst' to respond to only unmodeled burst events. if params['Group'] != 'CBC': log.info('Not CBC, aborting.') return # Respond only to specific merger types if ((config.getfloat("GENERAL", "BNS_MIN") < float(params["BNS"])) | (config.getfloat("GENERAL", "NSBH_MIN") < float(params["NSBH"])) | (config.getfloat("GENERAL", "MASSGAP_MIN") < float(params["MassGap"])) | (config.getfloat("GENERAL", "BBH_MIN") < float(params["BBH"])) | (config.getfloat("GENERAL", "HASNS_MIN") < float(params["HasNS"])) | (config.getfloat("GENERAL", "HASREMNANT_MIN") < float(params["HasRemnant"]))) & \ (config.getfloat("GENERAL", "TERRESTRIAL_MAX") >= float(params["Terrestrial"])) & \ (config.getfloat("GENERAL", "FAR_MAX") >= float(params["FAR"])*60*60*24*365): pass else: log.info("Uninteresting alert, aborting.") return # Save alert to file with open(alerts_path + filename + '.xml', "wb") as f: f.write(payload) log.info("GCN/LVC alert {} received, started processing.".format(ivorn)) # Read VOEvent attributes keylist = ['ivorn', 'role', 'version'] for key in keylist: params[key] = v.attrib[key] # Read Who params['author_ivorn'] = v.Who.Author.contactName params['date_ivorn'] = v.Who.Date # Read WhereWhen params[ 'observatorylocation_id'] = v.WhereWhen.ObsDataLocation.ObservatoryLocation.attrib[ 'id'] params[ 'astrocoordsystem_id'] = v.WhereWhen.ObsDataLocation.ObservationLocation.AstroCoordSystem.attrib[ 'id'] params[ 'isotime'] = v.WhereWhen.ObsDataLocation.ObservationLocation.AstroCoords.Time.TimeInstant.ISOTime # Read How description = "" for item in v.How.iterfind('Description'): description = description + ", " + item params['how_description'] = description # Insert VOEvent to the database mysql_update.insert_voevent('voevent_lvc', params, log) # Download the HEALPix sky map FITS file. tmp_path = download_file(params['skymap_fits'], cache=False) skymap_path = fits_path + filename + "_" + ntpath.basename( params['skymap_fits']) shutil.move(tmp_path, skymap_path) # Respond only to alerts with reasonable localization credzones = [ 0.5, 0.9, config.getfloat("GENERAL", "AREA_CREDZONE"), config.getfloat("TILE", "CREDZONE") ] area = get_sky_area(skymap_path, credzone=credzones) if area[2] > config.getfloat("GENERAL", "AREA_MAX"): log.info( f"""{credzones[2]} area is {area[2]} > {config.get("GENERAL", "AREA_MAX")} deg^2, aborting.""" ) send_mail( subject="[GW@Wise] {}".format(params["GraceID"]), text= f"""Attached {filename} GCN/LVC alert received, but {credzones[2]} area is {area[2]} > \ {config.get("GENERAL", "AREA_MAX")} deg^2, aborting.""", html=format_alert(params, area[0:1]), files=[alerts_path + filename + '.xml'], log=log) return # Send alert email send_mail( subject="[GW@Wise] {}".format(params["GraceID"]), text="Attached {} GCN/LVC alert received, started processing.".format( filename), html=format_alert(params, area[0:2]), files=[alerts_path + filename + '.xml'], log=log) if area[3] > config.getfloat("TILE", "AREA_MAX"): # Create the galaxy list galaxies, ra, dec = galaxy_list.find_galaxy_list(skymap_path, log=log) # Save galaxy list to csv file and send it ascii.write(galaxies, "galaxy_list.csv", format="csv", overwrite=True, names=[ "GladeID", "RA", "Dec", "Dist", "Bmag", "Score", "Distance factor" ]) send_mail( subject="[GW@Wise] {} Galaxy list".format(params["GraceID"]), text="{} GCN/LVC alert galaxy list is attached.".format(filename), files=["galaxy_list.csv"], log=log) # Create Wise plan wise.process_galaxy_list(galaxies, alertname=ivorn.split('/')[-1], ra_event=ra, dec_event=dec, log=log) else: # Tile the credible region wise.process_tiles(skymap_path, alertname=ivorn.split('/')[-1], log=log) # Finish and delete logger log.info("Done.") close_log(log)
def process_galaxy_list(galaxies, filename='galaxies', ra_event=None, dec_event=None, log=None): """Get the full galaxy list, and find which are good to observe at Wise""" if log is None: log = logging.getLogger(__name__) log.info("Event most probable RA={}, Dec={}.".format( ra_event.to_string(unit=u.hourangle, sep=':', precision=2, pad=True), dec_event.to_string(sep=':', precision=2, alwayssign=True, pad=True))) t = Time.now() if not is_night( lat=config.getfloat('WISE', 'LAT') * u.deg, lon=config.getfloat('WISE', 'LON') * u.deg, alt=config.getfloat('WISE', 'ALT') * u.m, t=t, sun_alt_twilight=config.getfloat('OBSERVING', 'SUN_ALT_MAX') * u.deg): log.info("Daytime at Wise! Preparing a plan for next sunset.") t = next_night( lat=config.getfloat('WISE', 'LAT') * u.deg, lon=config.getfloat('WISE', 'LON') * u.deg, alt=config.getfloat('WISE', 'ALT') * u.m, t=t, sun_alt_twilight=config.getfloat('OBSERVING', 'SUN_ALT_MAX') * u.deg) else: log.info("It's nighttime at Wise! Preparing a plan for NOW.") telescopes = config.get('WISE', 'TELESCOPES').split(',') nothing_to_observe = True for tel in range(0, len(telescopes)): log.info("Writing a plan for the {}".format(telescopes[tel])) root = rtml.init(name=config.get('OBSERVING', 'USER'), email=config.get('OBSERVING', 'EMAIL')) root = rtml.add_request( root, request_id=filename, bestefforts=config.get('OBSERVING', 'BESTEFFORTS'), user=config.get('OBSERVING', 'USER'), description=config.get('OBSERVING', 'DESCRIPTION'), project=config.get('OBSERVING', 'PROJECT'), airmass_min=config.get(telescopes[tel], 'AIRMASS_MIN'), airmass_max=config.get(telescopes[tel], 'AIRMASS_MAX'), hourangle_min=config.get(telescopes[tel], 'HOURANGLE_MIN'), hourangle_max=config.get(telescopes[tel], 'HOURANGLE_MAX')) log.debug( "Index\tGladeID\tRA\t\tDec\t\tAirmass\tHA\tDist\tBmag\tScore\t\tDist factor" ) for i in range(tel, galaxies.shape[0], len(telescopes)): ra = Angle(galaxies[i, 1] * u.deg) dec = Angle(galaxies[i, 2] * u.deg) is_observe, airmass, ha = is_observable( ra=ra, dec=dec, lat=config.getfloat('WISE', 'LAT') * u.deg, lon=config.getfloat('WISE', 'LON') * u.deg, alt=config.getfloat('WISE', 'ALT') * u.m, t=t, ha_min=config.getfloat(telescopes[tel], 'HOURANGLE_MIN') * u.hourangle, ha_max=config.getfloat(telescopes[tel], 'HOURANGLE_MAX') * u.hourangle, airmass_min=config.getfloat(telescopes[tel], 'AIRMASS_MIN'), airmass_max=config.getfloat(telescopes[tel], 'AIRMASS_MAX'), return_values=True) if is_observe: nothing_to_observe = False log.debug( "{}:\t{:.0f}\t{}\t{}\t{:+.2f}\t{:+.2f}\t{:.2f}\t{:.2f}\t{:.6g}\t\t{:.2f}\t\tadded to plan!" .format( i + 1, galaxies[i, 0], ra.to_string(unit=u.hourangle, sep=':', precision=2, pad=True), dec.to_string(sep=':', precision=2, alwayssign=True, pad=True), airmass, ha, galaxies[i, 3], galaxies[i, 4], galaxies[i, 5], galaxies[i, 6])) rtml.add_target(root, request_id=filename, ra=ra.to_string(unit=u.degree, decimal=True), dec=dec.to_string(unit=u.degree, decimal=True, alwayssign=True), name="GladeID_{:.0f}".format(galaxies[i, 0])) rtml.add_picture( root, filt=config.get(telescopes[tel], 'FILTER'), target_name="GladeID_{:.0f}".format(galaxies[i, 0]), exptime=config.get(telescopes[tel], 'EXPTIME'), binning=config.get(telescopes[tel], 'BINNING')) else: log.debug( "{}:\t{:.0f}\t{}\t{}\t{:+.2f}\t{:+.2f}\t{:.2f}\t{:.2f}\t{:.6g}\t\t{:.2f}" .format( i + 1, galaxies[i, 0], ra.to_string(unit=u.hourangle, sep=':', precision=2, pad=True), dec.to_string(sep=':', precision=2, alwayssign=True, pad=True), airmass, ha, galaxies[i, 3], galaxies[i, 4], galaxies[i, 5], galaxies[i, 6])) if nothing_to_observe: log.info("Nothing to observe.") send_mail( subject="[GW@Wise] Nothing to observe", text= "Nothing to observe for alert {}.\nEvent most probable at RA={}, Dec={}." .format( filename, ra_event.to_string(unit=u.hourangle, sep=':', precision=2, pad=True), dec_event.to_string(sep=':', precision=2, alwayssign=True, pad=True))) else: rtml_filename = config.get( 'WISE', 'PATH') + filename + '_' + telescopes[tel] + '.xml' rtml.write(root, rtml_filename) log.info("Created observing plan for alert {}.".format(filename)) send_mail( subject="[GW@Wise] {} observing plan".format(telescopes[tel]), text= "{} observing plan for alert {}.\nEvent most probable at RA={}, Dec={}." .format( telescopes[tel], filename, ra_event.to_string(unit=u.hourangle, sep=':', precision=2, pad=True), dec_event.to_string(sep=':', precision=2, alwayssign=True, pad=True)), files=[rtml_filename]) return
def process_tiles(skymap_path, alertname='GW', log=None): if log is None: log = logging.getLogger(__name__) eventname = alertname.split('#')[1] eventname = eventname.split('-')[0] t = Time.now() if not is_night( lat=config.getfloat('WISE', 'LAT') * u.deg, lon=config.getfloat('WISE', 'LON') * u.deg, alt=config.getfloat('WISE', 'ALT') * u.m, t=t, sun_alt_twilight=config.getfloat('OBSERVING', 'SUN_ALT_MAX') * u.deg): log.info("Daytime at Wise! Preparing a plan for next sunset.") t = next_sunset( lat=config.getfloat('WISE', 'LAT') * u.deg, lon=config.getfloat('WISE', 'LON') * u.deg, alt=config.getfloat('WISE', 'ALT') * u.m, t=t, sun_alt_twilight=config.getfloat('OBSERVING', 'SUN_ALT_MAX') * u.deg) else: log.info("It's nighttime at Wise! Preparing a plan for NOW.") t_sunrise = next_sunrise( lat=config.getfloat('WISE', 'LAT') * u.deg, lon=config.getfloat('WISE', 'LON') * u.deg, alt=config.getfloat('WISE', 'ALT') * u.m, t=t, sun_alt_twilight=config.getfloat('OBSERVING', 'SUN_ALT_MAX') * u.deg) log.debug("Now/sunset = {}, sunrise = {}".format(t, t_sunrise)) telescopes = config.get('WISE', 'TELESCOPES').split(', ') # change IERS table URL (to fix URL timeout problems) change_iers_url(url=config.get('IERS', 'URL')) nothing_to_observe = True for tel in range(0, len(telescopes)): log.info("Writing a plan for the {}".format(telescopes[tel])) root = rtml.init(name=config.get('OBSERVING', 'USER'), email=config.get('OBSERVING', 'EMAIL')) # Tile the credible region ra, dec, probability = tile.tile_region( skymap_path, credzone=config.getfloat("TILE", "CREDZONE"), tile_area=config.getfloat("TILE", "SIZE") * config.getfloat(telescopes[tel], "FOV"), log=log) log.debug("Index\tRA\t\tDec\tAirmass\tHA\tLunarDist\tProbability") csv_filename = f"{telescopes[tel]}_TileList.csv" fid = open(csv_filename, "w") fid.write("Index,RA,Dec,Airmass,HA,LunarDist,Probability\n") for i in range(len(ra)): is_observe, airmass, ha, lunar_dist = is_observable_in_interval( ra=ra[i], dec=dec[i], lat=config.getfloat('WISE', 'LAT') * u.deg, lon=config.getfloat('WISE', 'LON') * u.deg, alt=config.getfloat('WISE', 'ALT') * u.m, t1=t, t2=t_sunrise, ha_min=config.getfloat(telescopes[tel], 'HOURANGLE_MIN') * u.hourangle, ha_max=config.getfloat(telescopes[tel], 'HOURANGLE_MAX') * u.hourangle, airmass_min=config.getfloat(telescopes[tel], 'AIRMASS_MIN'), airmass_max=config.getfloat(telescopes[tel], 'AIRMASS_MAX'), min_lunar_distance=config.getfloat(telescopes[tel], 'MIN_LUNAR_DIST') * u.deg, return_values=True) if is_observe: nothing_to_observe = False log.debug( "{}:\t{}\t{}\t{:+.2f}\t{:+.2f}\t{:.2f}\t{:.6g}\t\tadded to plan!" .format( i + 1, ra[i].to_string(unit=u.hourangle, sep=':', precision=2, pad=True), dec[i].to_string(sep=':', precision=2, alwayssign=True, pad=True), airmass, ha, lunar_dist, probability[i])) fid.write("{},{},{},{:+.2f},{:+.2f},{:.2f},{:.6g}\n".format( i + 1, ra[i].to_string(unit=u.hourangle, sep=':', precision=2, pad=True), dec[i].to_string(sep=':', precision=2, alwayssign=True, pad=True), airmass, ha, lunar_dist, probability[i])) root = rtml.add_request( root, request_id="Tile_{:.0f}".format(i + 1), bestefforts=config.get('OBSERVING', 'BESTEFFORTS'), user=config.get('OBSERVING', 'USER'), description=config.get('OBSERVING', 'DESCRIPTION'), project=alertname, airmass_min=config.get(telescopes[tel], 'AIRMASS_MIN'), airmass_max=config.get(telescopes[tel], 'AIRMASS_MAX'), hourangle_min=config.get(telescopes[tel], 'HOURANGLE_MIN'), hourangle_max=config.get(telescopes[tel], 'HOURANGLE_MAX'), priority=str(len(ra) - i)) rtml.add_target(root, request_id="Tile_{:.0f}".format(i + 1), ra=ra[i].to_string(unit=u.degree, decimal=True), dec=dec[i].to_string(unit=u.degree, decimal=True, alwayssign=True), name="Tile_{:.0f}".format(i + 1)) rtml.add_picture(root, filt=config.get(telescopes[tel], 'FILTER'), target_name="Tile_{:.0f}".format(i + 1), exptime=config.get(telescopes[tel], 'EXPTIME'), binning=config.get(telescopes[tel], 'BINNING')) else: log.debug( "{}:\t{}\t{}\t{:+.2f}\t{:+.2f}\t{:+.2f}\t{:.6g}".format( i + 1, ra.to_string(unit=u.hourangle, sep=':', precision=2, pad=True), dec.to_string(sep=':', precision=2, alwayssign=True, pad=True), airmass, ha, lunar_dist, probability[i])) if nothing_to_observe: log.info("Nothing to observe.") send_mail( subject= f"[GW@Wise] {eventname} {telescopes[tel]} observing plan", text=f"Nothing to observe for alert {alertname}.") else: rtml_filename = config.get( 'WISE', 'PATH') + alertname + '_' + telescopes[tel] + '.xml' rtml.write(root, rtml_filename) fid.close() log.info(f"Created observing plan for alert {alertname}.") send_mail( subject= f"[GW@Wise] {eventname} {telescopes[tel]} observing plan", text="{} observing plan for alert {}.".format( telescopes[tel], alertname), files=[rtml_filename, csv_filename]) # upload to remote Scheduler if not config.get(telescopes[tel], 'HOST'): log.info("No host name was provided, skipping plan upload.") else: result = rtml.import_to_remote_scheduler( rtml_filename, username=config.get(telescopes[tel], 'USER'), remote_host=config.get(telescopes[tel], 'HOST'), remote_path=config.get(telescopes[tel], 'PATH'), cygwin_path=config.get(telescopes[tel], 'CYGWIN_PATH')) log.info(result) return
def process_galaxy_list(galaxies, alertname='GW', ra_event=None, dec_event=None, log=None): """Get the full galaxy list, and find which are good to observe at Wise""" if log is None: log = logging.getLogger(__name__) log.info("Event most probable RA={}, Dec={}.".format( ra_event.to_string(unit=u.hourangle, sep=':', precision=2, pad=True), dec_event.to_string(sep=':', precision=2, alwayssign=True, pad=True))) eventname = alertname.split('#')[1] eventname = eventname.split('-')[0] t = Time.now() if not is_night( lat=config.getfloat('WISE', 'LAT') * u.deg, lon=config.getfloat('WISE', 'LON') * u.deg, alt=config.getfloat('WISE', 'ALT') * u.m, t=t, sun_alt_twilight=config.getfloat('OBSERVING', 'SUN_ALT_MAX') * u.deg): log.info("Daytime at Wise! Preparing a plan for next sunset.") t = next_sunset( lat=config.getfloat('WISE', 'LAT') * u.deg, lon=config.getfloat('WISE', 'LON') * u.deg, alt=config.getfloat('WISE', 'ALT') * u.m, t=t, sun_alt_twilight=config.getfloat('OBSERVING', 'SUN_ALT_MAX') * u.deg) else: log.info("It's nighttime at Wise! Preparing a plan for NOW.") t_sunrise = next_sunrise( lat=config.getfloat('WISE', 'LAT') * u.deg, lon=config.getfloat('WISE', 'LON') * u.deg, alt=config.getfloat('WISE', 'ALT') * u.m, t=t, sun_alt_twilight=config.getfloat('OBSERVING', 'SUN_ALT_MAX') * u.deg) log.debug("Now/sunset = {}, sunrise = {}".format(t, t_sunrise)) telescopes = config.get('WISE', 'TELESCOPES').split(',') max_galaxies = config.getint( 'GALAXIES', 'MAXGALAXIESPLAN' ) # maximal number of galaxies to use in observation plan # change IERS table URL (to fix URL timeout problems) change_iers_url(url=config.get('IERS', 'URL')) nothing_to_observe = True for tel in range(0, len(telescopes)): log.info("Writing a plan for the {}".format(telescopes[tel])) root = rtml.init(name=config.get('OBSERVING', 'USER'), email=config.get('OBSERVING', 'EMAIL')) log.debug( "Index\tGladeID\tRA\t\tDec\t\tAirmass\tHA\tLunarDist\tDist\tBmag\tScore\t\tDist factor" ) csv_filename = f"{telescopes[tel]}_GalaxyList.csv" fid = open(csv_filename, "w") fid.write( "Index,GladeID,RA,Dec,Airmass,HA,LunarDist,Dist,Bmag,Score,Dist factor\n" ) n_galaxies_in_plan = 0 for i in range(tel, galaxies.shape[0], len(telescopes)): ra = Angle(galaxies[i, 1] * u.deg) dec = Angle(galaxies[i, 2] * u.deg) is_observe, airmass, ha, lunar_dist = is_observable_in_interval( ra=ra, dec=dec, lat=config.getfloat('WISE', 'LAT') * u.deg, lon=config.getfloat('WISE', 'LON') * u.deg, alt=config.getfloat('WISE', 'ALT') * u.m, t1=t, t2=t_sunrise, ha_min=config.getfloat(telescopes[tel], 'HOURANGLE_MIN') * u.hourangle, ha_max=config.getfloat(telescopes[tel], 'HOURANGLE_MAX') * u.hourangle, airmass_min=config.getfloat(telescopes[tel], 'AIRMASS_MIN'), airmass_max=config.getfloat(telescopes[tel], 'AIRMASS_MAX'), min_lunar_distance=config.getfloat(telescopes[tel], 'MIN_LUNAR_DIST') * u.deg, return_values=True) if is_observe: nothing_to_observe = False n_galaxies_in_plan += 1 log.debug( "{}:\t{:.0f}\t{}\t{}\t{:+.2f}\t{:+.2f}\t{:.2f}\t{:.2f}\t{:.2f}\t{:.6g}\t\t{:.2f}\t\tadded to plan!" .format( i + 1, galaxies[i, 0], ra.to_string(unit=u.hourangle, sep=':', precision=2, pad=True), dec.to_string(sep=':', precision=2, alwayssign=True, pad=True), airmass, ha, lunar_dist, galaxies[i, 3], galaxies[i, 4], galaxies[i, 5], galaxies[i, 6])) fid.write( "{},{:.0f},{},{},{:+.2f},{:+.2f},{:.2f},{:.2f},{:.2f},{:.6g},{:.2f}\n" .format( i + 1, galaxies[i, 0], ra.to_string(unit=u.hourangle, sep=':', precision=2, pad=True), dec.to_string(sep=':', precision=2, alwayssign=True, pad=True), airmass, ha, lunar_dist, galaxies[i, 3], galaxies[i, 4], galaxies[i, 5], galaxies[i, 6])) root = rtml.add_request( root, request_id="GladeID_{:.0f}".format(galaxies[i, 0]), bestefforts=config.get('OBSERVING', 'BESTEFFORTS'), user=config.get('OBSERVING', 'USER'), description=config.get('OBSERVING', 'DESCRIPTION'), project=alertname, airmass_min=config.get(telescopes[tel], 'AIRMASS_MIN'), airmass_max=config.get(telescopes[tel], 'AIRMASS_MAX'), hourangle_min=config.get(telescopes[tel], 'HOURANGLE_MIN'), hourangle_max=config.get(telescopes[tel], 'HOURANGLE_MAX'), priority=str( min(max_galaxies, galaxies.shape[0]) - n_galaxies_in_plan + 1)) rtml.add_target(root, request_id="GladeID_{:.0f}".format( galaxies[i, 0]), ra=ra.to_string(unit=u.degree, decimal=True), dec=dec.to_string(unit=u.degree, decimal=True, alwayssign=True), name="GladeID_{:.0f}".format(galaxies[i, 0])) rtml.add_picture( root, filt=config.get(telescopes[tel], 'FILTER'), target_name="GladeID_{:.0f}".format(galaxies[i, 0]), exptime=config.get(telescopes[tel], 'EXPTIME'), binning=config.get(telescopes[tel], 'BINNING')) else: log.debug( "{}:\t{:.0f}\t{}\t{}\t{:+.2f}\t{:+.2f}\t{:+.2f}\t{:.2f}\t{:.2f}\t{:.6g}\t\t{:.2f}" .format( i + 1, galaxies[i, 0], ra.to_string(unit=u.hourangle, sep=':', precision=2, pad=True), dec.to_string(sep=':', precision=2, alwayssign=True, pad=True), airmass, ha, lunar_dist, galaxies[i, 3], galaxies[i, 4], galaxies[i, 5], galaxies[i, 6])) if n_galaxies_in_plan >= max_galaxies: # maximal number of galaxies per plan has been reached break if nothing_to_observe: log.info("Nothing to observe.") send_mail( subject= f"[GW@Wise] {eventname} {telescopes[tel]} observing plan", text= "Nothing to observe for alert {}.\nEvent most probable at RA={}, Dec={}." .format( alertname, ra_event.to_string(unit=u.hourangle, sep=':', precision=2, pad=True), dec_event.to_string(sep=':', precision=2, alwayssign=True, pad=True))) else: rtml_filename = config.get( 'WISE', 'PATH') + alertname + '_' + telescopes[tel] + '.xml' rtml.write(root, rtml_filename) fid.close() log.info(f"Created observing plan for alert {alertname}.") send_mail( subject= f"[GW@Wise] {eventname} {telescopes[tel]} observing plan", text= "{} observing plan for alert {}.\nEvent most probable at RA={}, Dec={}." .format( telescopes[tel], alertname, ra_event.to_string(unit=u.hourangle, sep=':', precision=2, pad=True), dec_event.to_string(sep=':', precision=2, alwayssign=True, pad=True)), files=[rtml_filename, csv_filename]) # upload to remote Scheduler if not config.get(telescopes[tel], 'HOST'): log.info("No host name was provided, skipping plan upload.") else: result = rtml.import_to_remote_scheduler( rtml_filename, username=config.get(telescopes[tel], 'USER'), remote_host=config.get(telescopes[tel], 'HOST'), remote_path=config.get(telescopes[tel], 'PATH'), cygwin_path=config.get(telescopes[tel], 'CYGWIN_PATH')) log.info(result) return