From 28a01c2d0c0d5c858acd8b4ffc8ba0fb5233778c Mon Sep 17 00:00:00 2001 From: Brandon Piotrzkowski <brandon.piotrzkowski@ligo.org> Date: Fri, 26 Aug 2022 16:56:28 -0400 Subject: [PATCH] Use multi-ordered sky maps in RAVEN pipeline; fixes #408 --- CHANGES.rst | 6 + gwcelery/tasks/alerts.py | 55 ++++-- gwcelery/tasks/external_skymaps.py | 138 +++++++++++--- gwcelery/tasks/external_triggers.py | 68 ++++--- gwcelery/tasks/orchestrator.py | 84 ++++++++- .../data/gracedb_externaltrigger_log.json | 8 + .../tests/data/gracedb_setrigger_log.json | 2 +- gwcelery/tests/test_tasks_external_skymaps.py | 78 +++++++- .../tests/test_tasks_external_triggers.py | 53 +++--- gwcelery/tests/test_tasks_orchestrator.py | 174 ++++++++++++------ 10 files changed, 493 insertions(+), 173 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 110b05a41..4402ed2b1 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -9,6 +9,12 @@ Changelog - Allow alerts (using multi-order skymaps) for burst events. +- Add the ability to use multi-order GW sky maps to create combined sky maps + and include these in alerts. The presence of the COMBINEDSKYMAP_READY label + indicates the combined sky map is now available in that external event or + superevent. We will only copy a combined sky map to the superevent when + sending an alert once if the preferred external event has one available. + - Don't compute p-astro for PyCBC/AllSky because it now computes and uploads its own. diff --git a/gwcelery/tasks/alerts.py b/gwcelery/tasks/alerts.py index 64a3b73f4..774e6b065 100644 --- a/gwcelery/tasks/alerts.py +++ b/gwcelery/tasks/alerts.py @@ -11,8 +11,7 @@ from . import gracedb log = get_logger(__name__) -def _create_base_alert_dict(classification, superevent, alert_type, - raven_coinc=False): +def _create_base_alert_dict(classification, superevent, alert_type): '''Create the base of the alert dictionary, with all contents except the skymap and the external coinc information.''' # NOTE Everything that comes through this code path will be marked as @@ -59,9 +58,14 @@ def _create_base_alert_dict(classification, superevent, alert_type, @gracedb.task(shared=False) -def _add_external_coinc_to_alert(alert_dict, superevent): +def _add_external_coinc_to_alert(alert_dict, superevent, + combined_skymap_filename): external_event = gracedb.get_event(superevent['em_type']) - + if combined_skymap_filename: + combined_skymap = gracedb.download(combined_skymap_filename, + superevent['em_type']) + else: + combined_skymap = None alert_dict['external_coinc'] = { 'gcn_notice_id': external_event['extra_attributes']['GRB']['trigger_id'], @@ -74,7 +78,7 @@ def _add_external_coinc_to_alert(alert_dict, superevent): 'time_sky_position_coincidence_far': superevent['space_coinc_far'] } - return alert_dict + return alert_dict, combined_skymap @app.task(bind=True, shared=False, queue='kafka', ignore_result=True) @@ -101,7 +105,7 @@ def _upload_notice(self, payload, brokerhost, superevent_id): @app.task(bind=True, queue='kafka', shared=False) -def _send(self, alert_dict, skymap, brokerhost): +def _send(self, alert_dict, skymap, brokerhost, combined_skymap=None): """Write the alert to the Kafka topic""" # Copy the alert dictionary so we dont modify the original payload_dict = alert_dict.copy() @@ -116,6 +120,10 @@ def _send(self, alert_dict, skymap, brokerhost): encoder = config['skymap_encoder'] payload_dict['event']['skymap'] = encoder(skymap) + if combined_skymap: + payload_dict['external_coinc']['combined_skymap'] = \ + encoder(combined_skymap) + # Write to kafka topic serialization_model = \ self.app.conf['kafka_streams'][brokerhost].serialization_model @@ -129,9 +137,16 @@ def _send(self, alert_dict, skymap, brokerhost): return payload +@app.task(bind=True, queue='kafka', shared=False) +def _send_with_combined(self, alert_dict_combined_skymap, skymap, brokerhost): + alert_dict, combined_skymap = alert_dict_combined_skymap + return _send(alert_dict, skymap, brokerhost, + combined_skymap=combined_skymap) + + @app.task(bind=True, ignore_result=True, queue='kafka', shared=False) def send(self, skymap_and_classification, superevent, alert_type, - raven_coinc=False): + raven_coinc=False, combined_skymap_filename=None): """Send an public alert to all currently connected kafka brokers. Parameters @@ -151,7 +166,8 @@ def send(self, skymap_and_classification, superevent, alert_type, The alert type. raven_coinc: bool Is there a coincident external event processed by RAVEN? - + combined_skymap_filename : str + Combined skymap filename. Default None. """ if skymap_and_classification is not None: @@ -163,17 +179,20 @@ def send(self, skymap_and_classification, superevent, alert_type, alert_dict = _create_base_alert_dict( classification, superevent, - alert_type, - raven_coinc=raven_coinc + alert_type ) if raven_coinc and alert_type != 'retraction': canvas = ( - _add_external_coinc_to_alert.s(alert_dict, superevent) + _add_external_coinc_to_alert.si( + alert_dict, + superevent, + combined_skymap_filename + ) | group( ( - _send.s(skymap, brokerhost) + _send_with_combined.s(skymap, brokerhost) | _upload_notice.s(brokerhost, superevent['superevent_id']) ) for brokerhost in self.app.conf['kafka_streams'].keys() @@ -200,7 +219,8 @@ def _create_skymap_classification_tuple(skymap, classification): @app.task(shared=False, ignore_result=True) def download_skymap_and_send_alert(classification, superevent, alert_type, - skymap_filename=None, raven_coinc=False): + skymap_filename=None, raven_coinc=False, + combined_skymap_filename=None): """Wrapper for send function when caller has not already downloaded the skymap. @@ -221,7 +241,8 @@ def download_skymap_and_send_alert(classification, superevent, alert_type, The skymap filename. raven_coinc: bool Is there a coincident external event processed by RAVEN? - + combined_skymap_filename : str + The combined skymap filename. Default None """ if skymap_filename is not None and alert_type != 'retraction': @@ -233,14 +254,16 @@ def download_skymap_and_send_alert(classification, superevent, alert_type, | _create_skymap_classification_tuple.s(classification) | - send.s(superevent, alert_type, raven_coinc=raven_coinc) + send.s(superevent, alert_type, raven_coinc=raven_coinc, + combined_skymap_filename=combined_skymap_filename) ) else: canvas = send.s( (None, classification), superevent, alert_type, - raven_coinc=raven_coinc + raven_coinc=raven_coinc, + combined_skymap_filename=combined_skymap_filename ) canvas.apply_async() diff --git a/gwcelery/tasks/external_skymaps.py b/gwcelery/tasks/external_skymaps.py index dbef49883..c8fefd441 100644 --- a/gwcelery/tasks/external_skymaps.py +++ b/gwcelery/tasks/external_skymaps.py @@ -1,11 +1,13 @@ """Create and upload external sky maps.""" from astropy import units as u from astropy.coordinates import ICRS, SkyCoord +import astropy_healpix as ah from astropy_healpix import HEALPix, pixel_resolution_to_nside from celery import group # import astropy.utils.data import numpy as np from ligo.skymap.io import fits +from ligo.skymap.distance import parameters_to_marginal_moments from ligo.skymap.tool import ligo_skymap_combine import gcn import healpy as hp @@ -23,42 +25,51 @@ from ..util.tempfile import NamedTemporaryFile from ..import _version +@app.task(shared=False, + queue='exttrig') def create_combined_skymap(se_id, ext_id): - """Creates and uploads the combined LVC-Fermi skymap. - - This also uploads the external trigger skymap to the external trigger - GraceDB page. + """Creates and uploads the combined LVC-Fermi skymap, uploading to the + external trigger GraceDB page. """ se_skymap_filename = get_skymap_filename(se_id) ext_skymap_filename = get_skymap_filename(ext_id) - new_skymap_filename = re.findall(r'(.*).fits.gz', se_skymap_filename)[0] + # Determine whether GW sky map is multiordered or flat + gw_moc = '.multiorder.fits' in se_skymap_filename + + new_filename = \ + ('combined-ext.multiorder.fits' if gw_moc else 'combined-ext.fits.gz') - # FIXME: put download functions in canvas - se_skymap = gracedb.download(se_skymap_filename, se_id) - ext_skymap = gracedb.download(ext_skymap_filename, ext_id) message = 'Combined LVC-external sky map using {0} and {1}'.format( se_skymap_filename, ext_skymap_filename) message_png = ( 'Mollweide projection of <a href="/api/events/{graceid}/files/' '{filename}">{filename}</a>').format( - graceid=se_id, filename=new_skymap_filename + '-ext.fits.gz') + graceid=ext_id, + filename=new_filename) ( - combine_skymaps.si(se_skymap, ext_skymap) + _download_skymaps.si( + se_skymap_filename, ext_skymap_filename, se_id, ext_id + ) + | + combine_skymaps.s(gw_moc=gw_moc) | group( - gracedb.upload.s(new_skymap_filename + '-ext.fits.gz', se_id, - message, ['sky_loc', 'public']), + gracedb.upload.s(new_filename, ext_id, + message, ['sky_loc', 'ext_coinc']), skymaps.plot_allsky.s() | - gracedb.upload.s(new_skymap_filename + '-ext.png', se_id, - message_png, ['sky_loc', 'ext_coinc', 'public']) + gracedb.upload.s('combined-ext.png', ext_id, + message_png, ['sky_loc', 'ext_coinc']) ) + | + gracedb.create_label.si('COMBINEDSKYMAP_READY', ext_id) ).delay() @app.task(autoretry_for=(ValueError,), retry_backoff=10, + queue='exttrig', retry_backoff_max=600) def get_skymap_filename(graceid): """Get the skymap fits filename. @@ -68,31 +79,106 @@ def get_skymap_filename(graceid): """ gracedb_log = gracedb.get_log(graceid) if 'S' in graceid: + # Try first to get a multiordered sky map for message in reversed(gracedb_log): filename = message['filename'] - if filename.endswith('.multiorder.fits'): + if filename.endswith('.multiorder.fits') and \ + "combined-ext." not in filename: + return filename + # Try next to get a flattened sky map + for message in reversed(gracedb_log): + filename = message['filename'] + if filename.endswith('.fits.gz') and \ + "combined-ext." not in filename: return filename else: for message in reversed(gracedb_log): filename = message['filename'] if (filename.endswith('.fits') or filename.endswith('.fit') or - filename.endswith('.fits.gz')): + filename.endswith('.fits.gz')) and \ + "combined-ext." not in filename: return filename raise ValueError('No skymap available for {0} yet.'.format(graceid)) -@app.task(shared=False) -def combine_skymaps(skymap1filebytes, skymap2filebytes): +@app.task(shared=False, queue='exttrig') +def _download_skymaps(se_filename, ext_filename, se_id, ext_id): + """Download both superevent and external sky map to be combined.""" + se_skymap = gracedb.download(se_filename, se_id) + ext_skymap = gracedb.download(ext_filename, ext_id) + return se_skymap, ext_skymap + + +def combine_skymaps_moc_flat(gw_sky, ext_sky, ext_header): + """This function combines a multiordered (MOC) GW sky map with a flattened + external one by reweighting the MOC sky map using the values of the + flattened one. + + Header info is generally inherited from the GW sky map or recalculated + using the combined sky map values. + """ + # Find ra/dec of each GW pixel + level, ipix = ah.uniq_to_level_ipix(gw_sky["UNIQ"]) + nsides = ah.level_to_nside(level) + areas = ah.nside_to_pixel_area(nsides) + ra_gw, dec_gw = ah.healpix_to_lonlat(ipix, nsides, order='nested') + # Find corresponding external sky map indicies + ext_nside = ah.npix_to_nside(len(ext_sky)) + ext_ind = ah.lonlat_to_healpix( + ra_gw, dec_gw, ext_nside, + order='nested' if ext_header['nest'] else 'ring') + # Reweight GW prob density by external sky map probabilities + gw_sky['PROBDENSITY'] *= ext_sky[ext_ind] + gw_sky['PROBDENSITY'] /= \ + np.sum(gw_sky['PROBDENSITY'] * areas).value + # Modify GW sky map with new data + distmean, diststd = parameters_to_marginal_moments( + gw_sky['PROBDENSITY'] * areas.value, + gw_sky['DISTMU'], gw_sky['DISTSIGMA']) + gw_sky.meta['distmean'], gw_sky.meta['diststd'] = distmean, diststd + gw_sky.meta['instruments'].update(ext_header['instruments']) + gw_sky.meta['HISTORY'].extend([ + '', 'The values were reweighted by using data from {}'.format( + list(ext_header['instruments'])[0])]) + return gw_sky + + +@app.task(shared=False, queue='exttrig') +def combine_skymaps(skymapsbytes, gw_moc=True): """This task combines the two input skymaps, in this case the external trigger skymap and the LVC skymap and writes to a temporary output file. It then returns the contents of the file as a byte array. + + There are separate methods in case the GW sky map is multiordered (we just + reweight using the external sky map) or flattened (use standard + ligo.skymap.combine method). """ - with NamedTemporaryFile(mode='rb', suffix='.fits.gz') as combinedskymap, \ - NamedTemporaryFile(content=skymap1filebytes) as skymap1file, \ - NamedTemporaryFile(content=skymap2filebytes) as skymap2file, \ + gw_skymap_bytes, ext_skymap_bytes = skymapsbytes + suffix = ".fits" if gw_moc else ".fits.gz" + with NamedTemporaryFile(mode='rb', suffix=suffix) as combinedskymap, \ + NamedTemporaryFile(content=gw_skymap_bytes) as gw_skymap_file, \ + NamedTemporaryFile(content=ext_skymap_bytes) as ext_skymap_file, \ handling_system_exit(): - ligo_skymap_combine.main([skymap1file.name, - skymap2file.name, combinedskymap.name]) + + # If GW sky map is multiordered, use reweighting method + if gw_moc: + # FIXME: Use method that regrids the combined sky map e.g. mhealpy + # once this method is quicker and preserves the header + # Load sky maps + gw_skymap = fits.read_sky_map(gw_skymap_file.name, moc=True) + ext_skymap, ext_header = fits.read_sky_map(ext_skymap_file.name, + moc=False) + # Create and write combined sky map + combined_skymap = combine_skymaps_moc_flat(gw_skymap, ext_skymap, + ext_header) + fits.write_sky_map(combinedskymap.name, combined_skymap, moc=True) + # If GW sky map is flattened, use older method + else: + ligo_skymap_combine.main([gw_skymap_file.name, + ext_skymap_file.name, + combinedskymap.name]) + # FIXME: Add method for MOC-MOC if there is a switch to MOC external + # skymaps return combinedskymap.read() @@ -107,7 +193,7 @@ def external_trigger(graceid): raise ValueError('No associated GRB EM event(s) for {0}.'.format(graceid)) -@app.task(shared=False) +@app.task(shared=False, queue='exttrig') def external_trigger_heasarc(external_id): """Returns the HEASARC fits file link.""" gracedb_log = gracedb.get_log(external_id) @@ -125,6 +211,7 @@ def external_trigger_heasarc(external_id): @app.task(autoretry_for=(urllib.error.HTTPError,), retry_backoff=10, + queue='exttrig', retry_backoff_max=600) def get_external_skymap(link, search): """Download the Fermi sky map fits file and return the contents as a byte @@ -152,6 +239,7 @@ def get_external_skymap(link, search): @app.task(autoretry_for=(urllib.error.HTTPError, urllib.error.URLError,), + queue='exttrig', retry_backoff=10, retry_backoff_max=1200) def get_upload_external_skymap(event, skymap_link=None): """If a Fermi sky map is not uploaded yet, tries to download one and upload @@ -339,7 +427,7 @@ def write_to_fits(skymap, event, notice_type, notice_date): return file.read() -@app.task(shared=False) +@app.task(shared=False, queue='exttrig') def create_upload_external_skymap(event, notice_type, notice_date): """Create and upload external sky map using RA, dec, and error radius information. diff --git a/gwcelery/tasks/external_triggers.py b/gwcelery/tasks/external_triggers.py index 24bd76a0e..0aa3d4a5e 100644 --- a/gwcelery/tasks/external_triggers.py +++ b/gwcelery/tasks/external_triggers.py @@ -17,7 +17,6 @@ log = get_logger(__name__) REQUIRED_LABELS_BY_TASK = { 'compare': {'SKYMAP_READY', 'EXT_SKYMAP_READY', 'EM_COINC'}, - 'combine': {'SKYMAP_READY', 'EXT_SKYMAP_READY', 'RAVEN_ALERT'}, 'SoG': {'SKYMAP_READY', 'RAVEN_ALERT', 'ADVOK'} } """These labels should be present on an external event to consider it to @@ -233,9 +232,7 @@ def handle_grb_igwn_alert(alert): * When both a GW and GRB sky map are available during a coincidence, indicated by the labels ``SKYMAP_READY`` and ``EXT_SKYMAP_READY`` respectively on the external event, this triggers the spacetime coinc - FAR to be calculated. If an alert is triggered with these same - conditions, indicated by the ``RAVEN_ALERT`` label, a combined GW-GRB - sky map is created using + FAR to be calculated and a combined GW-GRB sky map is created using :meth:`gwcelery.tasks.external_skymaps.create_combined_skymap`. """ @@ -299,30 +296,35 @@ def handle_grb_igwn_alert(alert): raven.coincidence_search(graceid, alert['object'], group=gw_group, searches=['GRB'], se_searches=se_searches) - # rerun raven pipeline or created combined sky map when sky maps are - # available; trigger SoG manuscript once voevent is created + # rerun raven pipeline and create combined sky map (if not a Swift event) + # when sky maps are available elif alert['alert_type'] == 'label_added' and \ alert['object'].get('group') == 'External': if _skymaps_are_ready(alert['object'], alert['data']['name'], 'compare'): - # if both sky maps present and a coincidence, compare sky maps - superevent_id, ext_ids = _get_superevent_ext_ids( - graceid, alert['object'], 'compare') + # if both sky maps present and a coincidence, rreun RAVEN + # pipeline and create combined sky maps + event = alert['object'] + superevent_id, ext_id = _get_superevent_ext_ids( + graceid, event) superevent = gracedb.get_superevent(superevent_id) - preferred_event_id = superevent['preferred_event'] - gw_group = gracedb.get_group(preferred_event_id) + gw_group = superevent['preferred_event_data']['group'] tl, th = raven._time_window(graceid, gw_group, - [alert['object']['pipeline']], - [alert['object']['search']]) - raven.raven_pipeline([alert['object']], superevent_id, superevent, - tl, th, gw_group) - if _skymaps_are_ready(alert['object'], alert['data']['name'], - 'combine'): - # if both sky maps present and a raven alert, create combined - # skymap - superevent_id, ext_id = _get_superevent_ext_ids( - graceid, alert['object'], 'combine') - external_skymaps.create_combined_skymap(superevent_id, ext_id) + [event['pipeline']], + [event['search']]) + # FIXME: both overlap integral and combined sky map could be + # done by the same function since they are so similar + group_canvas = () + group_canvas += raven.raven_pipeline.si( + [event], superevent_id, + superevent, tl, th, gw_group), + # Swift localizations are incredibly well localized and require + # a different method from Fermi/Integral/AGILE + # FIXME: Add Swift localization information in the future + if event['pipeline'] != 'Swift': + group_canvas += external_skymaps.create_combined_skymap.si( + superevent_id, ext_id), + group(group_canvas).delay() elif 'EM_COINC' in alert['object']['labels']: # if not complete, check if GW sky map; apply label to external # event if GW sky map @@ -406,21 +408,13 @@ def _skymaps_are_ready(event, label, task): return required_labels.issubset(label_set) and label in required_labels -def _get_superevent_ext_ids(graceid, event, task): - if task in {'combine', 'SoG'}: - if 'S' in graceid: - se_id = event['superevent_id'] - ext_id = event['em_type'] - else: - se_id = event['superevent'] - ext_id = event['graceid'] - elif task == 'compare': - if 'S' in graceid: - se_id = event['superevent_id'] - ext_id = event['em_events'] - else: - se_id = event['superevent'] - ext_id = [event['graceid']] +def _get_superevent_ext_ids(graceid, event): + if 'S' in graceid: + se_id = event['superevent_id'] + ext_id = event['em_type'] + else: + se_id = event['superevent'] + ext_id = event['graceid'] return se_id, ext_id diff --git a/gwcelery/tasks/orchestrator.py b/gwcelery/tasks/orchestrator.py index 965395dbb..4d1515856 100644 --- a/gwcelery/tasks/orchestrator.py +++ b/gwcelery/tasks/orchestrator.py @@ -393,6 +393,7 @@ def _create_voevent(classification, *args, **kwargs): if classification is not None: # Merge source classification and source properties into kwargs. for text in classification: + # Ignore filenames, only load dict in bytes form if text is not None: kwargs.update(json.loads(text)) @@ -524,9 +525,12 @@ def _unpack_args_and_send_earlywarning_preliminary_alert(input_list, alert): [skymap, skymap_filename], [em_bright, em_bright_filename], \ [p_astro, p_astro_filename] = input_list + # Update to latest state after downloading files + superevent = gracedb.get_superevent(alert['object']['superevent_id']) + earlywarning_preliminary_initial_update_alert.delay( [skymap_filename, em_bright_filename, p_astro_filename], - alert['object'], + superevent, ('earlywarning' if superevents.EARLY_WARNING_LABEL in alert['object']['labels'] else 'preliminary'), filecontents=[skymap, em_bright, p_astro] @@ -768,10 +772,19 @@ def earlywarning_preliminary_initial_update_alert( assert alert_type == 'earlywarning' or alert_type == 'preliminary' skymap_filename, em_bright_filename, p_astro_filename = filenames + combined_skymap_filename = None + combined_skymap_needed = False skymap_needed = (skymap_filename is None) em_bright_needed = (em_bright_filename is None) p_astro_needed = (p_astro_filename is None) - if skymap_needed or em_bright_needed or p_astro_needed: + raven_coinc = ('RAVEN_ALERT' in labels and bool(superevent['em_type'])) + if raven_coinc: + ext_labels = gracedb.get_labels(superevent['em_type']) + combined_skymap_needed = \ + {"RAVEN_ALERT", "COMBINEDSKYMAP_READY"}.issubset(set(ext_labels)) + + if skymap_needed or em_bright_needed or p_astro_needed or \ + combined_skymap_needed: for message in gracedb.get_log(superevent_id): t = message['tag_names'] f = message['filename'] @@ -781,7 +794,8 @@ def earlywarning_preliminary_initial_update_alert( continue if skymap_needed \ and {'sky_loc', 'public'}.issubset(t) \ - and f.endswith('.multiorder.fits'): + and f.endswith('.multiorder.fits') \ + and 'combined' not in f: skymap_filename = fv if em_bright_needed \ and 'em_bright' in t \ @@ -791,9 +805,55 @@ def earlywarning_preliminary_initial_update_alert( and 'p_astro' in t \ and f.endswith('.json'): p_astro_filename = fv + if combined_skymap_needed \ + and {'sky_loc', 'ext_coinc'}.issubset(t) \ + and f.startswith('combined-ext.') \ + and 'fit' in f: + combined_skymap_filename = fv + # only download first sky map and prevent more downloads + # FIXME: remove if there exists a system to recalculate + # combined sky maps later + combined_skymap_needed = False + + if combined_skymap_needed: + # if no combined sky map present and needed, download sky map from + # external event + # FIXME: use file inheritance once available + ext_id = superevent['em_type'] + combined_skymap_filename = \ + ('combined-ext.multiorder.fits' if '.multiorder.fits' in + skymap_filename else 'combined-ext.fits.gz') + message = 'Combined LVC-external sky map using {0} and {1}'.format( + superevent_id, ext_id) + message_png = ( + 'Mollweide projection of <a href="/api/events/{se_id}/files/' + '{filename}">{filename}</a>, using {se_id} and {ext_id}').format( + se_id=superevent_id, + ext_id=ext_id, + filename=combined_skymap_filename) + + combined_skymap_canvas = group( + gracedb.download.si(combined_skymap_filename, ext_id) + | + gracedb.upload.s( + combined_skymap_filename, superevent_id, + message, ['sky_loc', 'ext_coinc', 'public']) + | + gracedb.create_label.si('COMBINEDSKYMAP_READY', superevent_id), + + gracedb.download.si('combined-ext.png', ext_id) + | + gracedb.upload.s( + 'combined-ext.png', superevent_id, + message_png, ['sky_loc', 'ext_coinc', 'public'] + ) + | + # Pass None to download_anor_expose group + identity.si() + ) if alert_type in {'earlywarning', 'preliminary', 'initial'}: - if 'RAVEN_ALERT' in labels: + if raven_coinc: circular_task = circulars.create_emcoinc_circular.si(superevent_id) circular_filename = '{}-emcoinc-circular.txt'.format(alert_type) tags = ['em_follow', 'ext_coinc'] @@ -816,7 +876,7 @@ def earlywarning_preliminary_initial_update_alert( else: circular_canvas = identity.si() - if filecontents: + if filecontents and not combined_skymap_filename: skymap, em_bright, p_astro = filecontents download_andor_expose_group = [] @@ -828,14 +888,15 @@ def earlywarning_preliminary_initial_update_alert( skymap_filename=skymap_filename, internal=False, open_alert=True, - raven_coinc=('RAVEN_ALERT' in labels) + raven_coinc=raven_coinc, + combined_skymap_filename=combined_skymap_filename ) kafka_alert_canvas = alerts.send.si( (skymap, em_bright, p_astro), superevent, alert_type, - raven_coinc=('RAVEN_ALERT' in labels) + raven_coinc=raven_coinc ) else: # Download em_bright and p_astro files here for voevent @@ -850,7 +911,8 @@ def earlywarning_preliminary_initial_update_alert( skymap_filename=skymap_filename, internal=False, open_alert=True, - raven_coinc=('RAVEN_ALERT' in labels) + raven_coinc=raven_coinc, + combined_skymap_filename=combined_skymap_filename ) # The skymap has not been downloaded at this point, so we need to @@ -859,7 +921,8 @@ def earlywarning_preliminary_initial_update_alert( superevent, alert_type, skymap_filename=skymap_filename, - raven_coinc=('RAVEN_ALERT' in labels) + raven_coinc=raven_coinc, + combined_skymap_filename=combined_skymap_filename ) download_andor_expose_group += [ @@ -881,6 +944,9 @@ def earlywarning_preliminary_initial_update_alert( gracedb.create_tag.s('public', superevent_id) ) + if combined_skymap_needed: + download_andor_expose_group += [combined_skymap_canvas] + canvas = ( group(download_andor_expose_group) | diff --git a/gwcelery/tests/data/gracedb_externaltrigger_log.json b/gwcelery/tests/data/gracedb_externaltrigger_log.json index 5990e53ed..e92d195b6 100644 --- a/gwcelery/tests/data/gracedb_externaltrigger_log.json +++ b/gwcelery/tests/data/gracedb_externaltrigger_log.json @@ -14,5 +14,13 @@ "filename": "nasa.gsfc.gcn_Fermi#GBM_Gnd_Pos_2017-08-17T12:41:06.47_524666471_57-431.xml", "tags": "https://gracedb.ligo.org/api/events/E12345/log/1/tag/", "N": 1 + }, + { + "comment": "Sky map created from GCN RA, dec, and error.", + "file_version": 0, + "file": "https://gracedb.ligo.org/api/events/E12345/files/fermi_skymap.fits.gz,0", + "filename": "fermi_skymap.fits.gz", + "tags": "https://gracedb.ligo.org/api/events/E12345/log/2/tag/", + "N": 2 } ] diff --git a/gwcelery/tests/data/gracedb_setrigger_log.json b/gwcelery/tests/data/gracedb_setrigger_log.json index 66149c1b0..1a4c33fac 100644 --- a/gwcelery/tests/data/gracedb_setrigger_log.json +++ b/gwcelery/tests/data/gracedb_setrigger_log.json @@ -8,6 +8,6 @@ "filename": "bayestar.multiorder.fits", "file_version": 0, "tag_names": ["sky_loc", "public"], - "file": "https://gracedb-dev1.ligo.org/api/superevents/S170817/files/bayestar.fits.gz,0" + "file": "https://gracedb-dev1.ligo.org/api/superevents/S170817/files/bayestar.multiorder.fits,0" } ] diff --git a/gwcelery/tests/test_tasks_external_skymaps.py b/gwcelery/tests/test_tasks_external_skymaps.py index 0ebe2d013..27a5dd833 100644 --- a/gwcelery/tests/test_tasks_external_skymaps.py +++ b/gwcelery/tests/test_tasks_external_skymaps.py @@ -1,6 +1,7 @@ from importlib import resources from unittest.mock import patch +from astropy.table import Table import numpy as np import pytest @@ -61,6 +62,23 @@ def mock_get_file_contents(monkeypatch, toy_fits_filecontents): # noqa: F811 'astropy.utils.data.get_file_contents', get_file_contents) +def get_gw_moc_skymap(): + array = [np.arange(12, dtype=np.float64)] * 5 + # Modify UNIQ table to be allowable values + array[4] = array[4] + 4 + table = Table( + array, + names=['PROBDENSITY', 'DISTMU', 'DISTSIGMA', 'DISTNORM', 'UNIQ']) + table.meta['comment'] = 'This is a comment.' + table.meta['HISTORY'] = \ + ['This is a history line. <This should be escaped.>'] + table.meta['OBJECT'] = 'T12345' + table.meta['LOGBCI'] = 3.5 + table.meta['ORDERING'] = 'NESTED' + table.meta['instruments'] = {'L1', 'H1', 'V1'} + return table + + @patch('gwcelery.tasks.skymaps.plot_allsky.run') @patch('gwcelery.tasks.gracedb.upload.run') @patch('gwcelery.tasks.external_skymaps.combine_skymaps.run') @@ -78,10 +96,64 @@ def test_create_combined_skymap(mock_get_skymap_filename, mock_upload.assert_called() -@patch('gwcelery.tasks.gracedb.get_log', mock_get_log) -def test_get_skymap_filename(): +def _mock_read_sky_map(filename, moc=True): + if moc: + return get_gw_moc_skymap() + else: + ext_sky = np.full(12, 1 / 12) + ext_header = {'instruments': set({'Fermi'}), 'nest': True} + return ext_sky, ext_header + + +@pytest.mark.parametrize('gw_moc', + [True, False]) +@patch('ligo.skymap.tool.ligo_skymap_combine.main') +@patch('gwcelery.tasks.external_skymaps.combine_skymaps_moc_flat') +@patch('ligo.skymap.io.fits.read_sky_map', side_effect=_mock_read_sky_map) +@patch('ligo.skymap.io.fits.write_sky_map') +def test_combine_skymaps(mock_write_sky_map, + mock_read_sky_map, + mock_skymap_combine_moc_flat, + mock_skymap_combine_flat_flat, + gw_moc): + """Test using our internal MOC-flat sky map combination gives back the + input using a uniform sky map, ensuring the test is giving a sane result + and is at least running to completion. + """ + external_skymaps.combine_skymaps((b'', b''), gw_moc=gw_moc) + if gw_moc: + mock_read_sky_map.assert_called() + mock_skymap_combine_moc_flat.assert_called_once() + mock_write_sky_map.assert_called_once() + else: + mock_skymap_combine_flat_flat.assert_called() + + +def test_create_combined_skymap_moc_flat(): + """Test using our internal MOC-flat sky map combination gives back the + input using a uniform sky map, ensuring the test is giving a sane result + and is at least running to completion. + """ + # Run function under test + gw_sky = get_gw_moc_skymap() + ext_sky = np.full(12, 1 / 12) + ext_header = {'instruments': set({'Fermi'}), 'nest': True} + combined_sky = external_skymaps.combine_skymaps_moc_flat(gw_sky, ext_sky, + ext_header) + assert all(combined_sky['PROBDENSITY'] == gw_sky['PROBDENSITY']) + assert 'Fermi' in combined_sky.meta['instruments'] + + +@pytest.mark.parametrize('graceid', + ['S12345', 'E12345']) +@patch('gwcelery.tasks.gracedb.get_log', side_effect=mock_get_log) +def test_get_skymap_filename(mock_get_logs, graceid): """Test getting the LVC skymap fits filename""" - external_skymaps.get_skymap_filename('S12345') + filename = external_skymaps.get_skymap_filename(graceid) + if 'S' in graceid: + assert filename == 'bayestar.multiorder.fits' + elif 'E' in graceid: + assert filename == 'fermi_skymap.fits.gz' @patch('gwcelery.tasks.gracedb.get_event', mock_get_event) diff --git a/gwcelery/tests/test_tasks_external_triggers.py b/gwcelery/tests/test_tasks_external_triggers.py index 4637e12e4..a4cd9612b 100644 --- a/gwcelery/tests/test_tasks_external_triggers.py +++ b/gwcelery/tests/test_tasks_external_triggers.py @@ -260,20 +260,26 @@ def test_handle_create_skymap_label_from_superevent(mock_create_label, mock_create_label.assert_called_once_with('SKYMAP_READY', 'E1212') -@patch('gwcelery.tasks.gracedb.get_group', return_value='CBC') -@patch('gwcelery.tasks.raven.raven_pipeline') +@pytest.mark.parametrize('pipeline', + ['Fermi', 'Swift']) +@patch('gwcelery.tasks.raven.raven_pipeline.run') +@patch('gwcelery.tasks.external_skymaps.create_combined_skymap.run') @patch('gwcelery.tasks.gracedb.get_superevent', return_value={ 'superevent_id': 'S1234', - 'preferred_event': 'G1234' - }) -@patch('gwcelery.tasks.gracedb.get_event', - return_value={ - 'graceid': 'G1234', - 'group': 'CBC' + 'preferred_event': 'G1234', + 'preferred_event_data': + {'group': 'CBC'} }) -def test_handle_skymap_comparison(mock_get_event, mock_get_superevent, - mock_raven_pipeline, mock_get_group): +def test_handle_skymaps_ready(mock_get_superevent, + mock_create_combined_skymap, + mock_raven_pipeline, + pipeline): + """This test makes sure that once sky maps are available for a coincidence + that the RAVEN pipeline is rerun to calculate the joint FAR with sky map + information and check publishing conditions, as well as creating a + combined sky map. + """ alert = {"uid": "E1212", "alert_type": "label_added", "data": {"name": "SKYMAP_READY"}, @@ -282,15 +288,22 @@ def test_handle_skymap_comparison(mock_get_event, mock_get_superevent, "group": "External", "labels": ["EM_COINC", "EXT_SKYMAP_READY", "SKYMAP_READY"], "superevent": "S1234", - "pipeline": "Fermi", + "pipeline": pipeline, "search": "GRB" } } external_triggers.handle_grb_igwn_alert(alert) mock_raven_pipeline.assert_called_once_with([alert['object']], 'S1234', {'superevent_id': 'S1234', - 'preferred_event': 'G1234'}, + 'preferred_event': 'G1234', + 'preferred_event_data': + {'group': 'CBC'}}, -5, 1, 'CBC') + if pipeline != 'Swift': + mock_create_combined_skymap.assert_called_once_with( + 'S1234', 'E1212') + else: + mock_create_combined_skymap.assert_not_called() @patch('gwcelery.tasks.raven.trigger_raven_alert') @@ -332,22 +345,6 @@ def test_handle_label_removed(mock_get_superevent, ) -@patch('gwcelery.tasks.external_skymaps.create_combined_skymap') -def test_handle_skymap_combine(mock_create_combined_skymap): - alert = {"uid": "E1212", - "alert_type": "label_added", - "data": {"name": "RAVEN_ALERT"}, - "object": { - "graceid": "E1212", - "group": "External", - "labels": ["EM_COINC", "EXT_SKYMAP_READY", "SKYMAP_READY", - "RAVEN_ALERT"], - "superevent": "S1234"} - } - external_triggers.handle_grb_igwn_alert(alert) - mock_create_combined_skymap.assert_called_once_with('S1234', 'E1212') - - @pytest.mark.parametrize('labels', [["EM_COINC", "SKYMAP_READY", "RAVEN_ALERT", "ADVOK"], ["EM_COINC", "SKYMAP_READY", "ADVOK"]]) diff --git a/gwcelery/tests/test_tasks_orchestrator.py b/gwcelery/tests/test_tasks_orchestrator.py index cfa8697bb..ae7dbad4c 100644 --- a/gwcelery/tests/test_tasks_orchestrator.py +++ b/gwcelery/tests/test_tasks_orchestrator.py @@ -13,33 +13,41 @@ from . import data @pytest.mark.parametrize( # noqa: F811 - 'alert_type,label,group,pipeline,offline,far,instruments', + ('alert_type,label,group,pipeline,offline,far,instruments,superevent_id,' + + 'superevent_labels'), [['label_added', 'EM_Selected', 'CBC', 'gstlal', False, 1.e-9, - ['H1']], + ['H1'], 'S1234', ['EM_Selected']], ['label_added', 'EM_Selected', 'CBC', 'gstlal', False, 1.e-9, - ['H1', 'L1']], + ['H1', 'L1'], 'S1234', ['EM_Selected']], ['label_added', 'EM_Selected', 'CBC', 'gstlal', False, 1.e-9, - ['H1', 'L1', 'V1']], + ['H1', 'L1', 'V1'], 'S1234', ['EM_Selected']], + ['label_added', 'EM_Selected', 'CBC', 'gstlal', False, 1.e-9, + ['H1', 'L1', 'V1'], 'S2468', + ['EM_Selected', 'COMBINEDSKYMAP_READY', 'RAVEN_ALERT', 'EM_COINC']], ['label_added', 'EM_Selected', 'Burst', 'CWB', False, 1.e-9, - ['H1', 'L1', 'V1']], + ['H1', 'L1', 'V1'], 'S1234', ['EM_Selected']], ['label_added', 'EM_Selected', 'Burst', 'oLIB', False, 1.e-9, - ['H1', 'L1', 'V1']], + ['H1', 'L1', 'V1'], 'S1234', ['EM_Selected']], ['label_added', 'GCN_PRELIM_SENT', 'CBC', 'gstlal', False, 1.e-9, - ['H1', 'L1', 'V1']], - ['new', '', 'CBC', 'gstlal', False, 1.e-9, ['H1', 'L1']]]) + ['H1', 'L1', 'V1'], 'S1234', ['EM_Selected']], + ['new', '', 'CBC', 'gstlal', False, 1.e-9, ['H1', 'L1'], 'S1234', + ['EM_Selected']]]) def test_handle_superevent(monkeypatch, toy_3d_fits_filecontents, # noqa: F811 alert_type, label, group, pipeline, - offline, far, instruments): + offline, far, instruments, superevent_id, + superevent_labels): """Test a superevent is dispatched to the correct annotation task based on its preferred event's search group. """ def get_superevent(superevent_id): - assert superevent_id == 'S1234' return { 'preferred_event': 'G1234', 'gw_events': ['G1234'], 'preferred_event_data': get_event('G1234'), - 'category': "Production" + 'category': "Production", + 'labels': superevent_labels, + 'superevent_id': superevent_id, + 'em_type': None if superevent_id == 'S1234' else 'E1234' } def get_event(graceid): @@ -68,6 +76,9 @@ def test_handle_superevent(monkeypatch, toy_3d_fits_filecontents, # noqa: F811 event['instruments'] = ','.join(instruments) return event + def get_labels(graceid): + return ['COMBINEDSKYMAP_READY'] + def download(filename, graceid): if '.fits' in filename: return toy_3d_fits_filecontents @@ -86,9 +97,9 @@ def test_handle_superevent(monkeypatch, toy_3d_fits_filecontents, # noqa: F811 alert = { 'alert_type': alert_type, - 'uid': 'S1234', + 'uid': superevent_id, 'object': { - 'superevent_id': 'S1234', + 'superevent_id': superevent_id, 't_start': 1214714160, 't_0': 1214714162, 't_end': 1214714164, @@ -105,7 +116,13 @@ def test_handle_superevent(monkeypatch, toy_3d_fits_filecontents, # noqa: F811 else: alert['data'] = {'name': label} + if superevent_id == 'S1234': + raven_coinc = False + else: + raven_coinc = True + create_initial_circular = Mock() + create_emcoinc_circular = Mock() expose = Mock() annotate_fits = Mock(return_value=None) proceed_if_no_advocate_action = Mock( @@ -145,6 +162,7 @@ def test_handle_superevent(monkeypatch, toy_3d_fits_filecontents, # noqa: F811 monkeypatch.setattr('gwcelery.tasks.gracedb.expose._orig_run', expose) monkeypatch.setattr('gwcelery.tasks.gracedb.get_event._orig_run', get_event) + monkeypatch.setattr('gwcelery.tasks.gracedb.get_labels', get_labels) # FIXME: should test gracedb.create_voevent instead monkeypatch.setattr('gwcelery.tasks.orchestrator._create_voevent.run', create_voevent) @@ -152,6 +170,8 @@ def test_handle_superevent(monkeypatch, toy_3d_fits_filecontents, # noqa: F811 get_superevent) monkeypatch.setattr('gwcelery.tasks.circulars.create_initial_circular.run', create_initial_circular) + monkeypatch.setattr('gwcelery.tasks.circulars.create_emcoinc_circular.run', + create_emcoinc_circular) monkeypatch.setattr('gwcelery.tasks.inference.query_data.run', query_data) monkeypatch.setattr('gwcelery.tasks.inference._setup_dag_for_bilby.run', @@ -178,7 +198,7 @@ def test_handle_superevent(monkeypatch, toy_3d_fits_filecontents, # noqa: F811 if label == 'GCN_PRELIM_SENT': dqr_request_label = list( filter( - lambda x: x.args == ('DQR_REQUEST', 'S1234'), + lambda x: x.args == ('DQR_REQUEST', superevent_id), create_label.call_args_list ) ) @@ -189,12 +209,12 @@ def test_handle_superevent(monkeypatch, toy_3d_fits_filecontents, # noqa: F811 annotate_fits.assert_called_once() _event_info = get_event('G1234') # this gets the preferred event info assert superevents.should_publish(_event_info) - expose.assert_called_once_with('S1234') + expose.assert_called_once_with(superevent_id) create_tag.assert_has_calls( - [call('S1234-1-Preliminary.xml', 'public', 'S1234'), - call('em-bright-filename', 'public', 'S1234'), - call('p-astro-filename', 'public', 'S1234'), - call('skymap-filename', 'public', 'S1234')], + [call('S1234-1-Preliminary.xml', 'public', superevent_id), + call('em-bright-filename', 'public', superevent_id), + call('p-astro-filename', 'public', superevent_id), + call('skymap-filename', 'public', superevent_id)], any_order=True ) # FIXME: uncomment block below when patching @@ -207,7 +227,10 @@ def test_handle_superevent(monkeypatch, toy_3d_fits_filecontents, # noqa: F811 # skymap_filename='bayestar.fits.gz', skymap_type='bayestar') gcn_send.assert_called_once() alerts_send.assert_called_once() - create_initial_circular.assert_called_once() + if raven_coinc: + create_emcoinc_circular.assert_called_once() + else: + create_initial_circular.assert_called_once() if alert_type == 'new' and group == 'CBC': query_data.assert_called_once() @@ -220,7 +243,7 @@ def test_handle_superevent(monkeypatch, toy_3d_fits_filecontents, # noqa: F811 call_args = [ call_args.args[1:] for call_args in start_pe.call_args_list] assert all( - [(get_event('G1234'), 'S1234', pipeline) in call_args + [(get_event('G1234'), superevent_id, pipeline) in call_args for pipeline in ('bilby', 'rapidpe')]) else: start_pe.assert_not_called() @@ -261,31 +284,57 @@ def superevent_initial_alert_download(filename, graceid): elif filename == 'em_bright.json,0': return json.dumps({'HasNS': 0.0, 'HasRemnant': 0.0}) elif filename == 'p_astro.json,0': - return json.dumps( - dict(BNS=0.94, NSBH=0.03, BBH=0.02, Terrestrial=0.01)) + return b'{"BNS": 0.94, "NSBH": 0.03, "BBH": 0.02, "Terrestrial": 0.01}' elif filename == 'foobar.multiorder.fits,0': return 'contents of foobar.multiorder.fits,0' + elif 'combined-ext.multiorder.fits' in filename: + return 'contents of combined-ext.multiorder.fits' + elif 'combined-ext.png' in filename: + return 'contents of combined-ext.png' else: raise ValueError +def _mock_get_labels(ext_id): + if ext_id == 'E1': + return [] + elif ext_id == 'E2': + return ['EM_COINC', 'RAVEN_ALERT'] + elif ext_id == 'E3': + return ['EM_COINC', 'RAVEN_ALERT', 'COMBINEDSKYMAP_READY'] + + +def _mock_get_log(se_id): + logs = [{'tag_names': ['sky_loc', 'public'], + 'filename': 'foobar.multiorder.fits', + 'file_version': 0}, + {'tag_names': ['em_bright'], + 'filename': 'em_bright.json', + 'file_version': 0}, + {'tag_names': ['p_astro'], + 'filename': 'p_astro.json', + 'file_version': 0}] + if se_id == 'S2468': + logs.append({'tag_names': ['sky_loc', 'ext_coinc'], + 'filename': 'combined-ext.multiorder.fits', + 'file_version': 0}) + return logs + + @pytest.mark.parametrize( # noqa: F811 - 'labels', - [[], ['EM_COINC', 'RAVEN_ALERT']]) + 'labels,superevent_id,ext_id', + [[[], 'S1234', 'E1'], + [['EM_COINC', 'RAVEN_ALERT'], 'S1234', 'E2'], + [['EM_COINC', 'RAVEN_ALERT', 'COMBINEDSKYMAP_READY'], 'S1234', 'E3'], + [['EM_COINC', 'RAVEN_ALERT', 'COMBINEDSKYMAP_READY'], 'S2468', 'E3']]) @patch('gwcelery.tasks.gracedb.expose._orig_run', return_value=None) @patch('gwcelery.tasks.gracedb.get_log', - return_value=[{'tag_names': ['sky_loc', 'public'], - 'filename': 'foobar.multiorder.fits', - 'file_version': 0}, - {'tag_names': ['em_bright'], - 'filename': 'em_bright.json', - 'file_version': 0}, - {'tag_names': ['p_astro'], - 'filename': 'p_astro.json', - 'file_version': 0}]) + side_effect=_mock_get_log) @patch('gwcelery.tasks.gracedb.create_tag._orig_run', return_value=None) @patch('gwcelery.tasks.gracedb.create_voevent._orig_run', return_value='S1234-Initial-1.xml') +@patch('gwcelery.tasks.gracedb.get_labels', + side_effect=_mock_get_labels) @patch('gwcelery.tasks.gracedb.download._orig_run', superevent_initial_alert_download) @patch('gwcelery.tasks.gcn.send.run') @@ -296,46 +345,63 @@ def test_handle_superevent_initial_alert(mock_create_initial_circular, mock_create_emcoinc_circular, mock_alerts_send, mock_gcn_send, + mock_get_labels, mock_create_voevent, mock_create_tag, mock_get_log, - mock_expose, labels): - """Test that the ``ADVOK`` label triggers an initial alert.""" + mock_expose, labels, + superevent_id, ext_id): + """Test that the ``ADVOK`` label triggers an initial alert. + This test varies the labels in the superevent and external event in order + to test the non-RAVEN alerts, RAVEN alerts without a combined sky map, and + RAVEN alerts with a combined sky map respectively.""" alert = { 'alert_type': 'label_added', - 'uid': 'S1234', + 'uid': superevent_id, 'data': {'name': 'ADVOK'}, 'object': { 'labels': labels, - 'superevent_id': 'S1234' - } + 'superevent_id': superevent_id, + 'em_type': ext_id if labels else ''} } + combined_skymap_needed = ('COMBINEDSKYMAP_READY' in labels) + if combined_skymap_needed: + combined_skymap_filename = \ + ('combined-ext.multiorder.fits' + + (',0' if superevent_id == 'S2468' else '')) + else: + combined_skymap_filename = None # Run function under test orchestrator.handle_superevent(alert) mock_create_voevent.assert_called_once_with( - 'S1234', 'initial', BBH=0.02, BNS=0.94, NSBH=0.03, ProbHasNS=0.0, + superevent_id, 'initial', BBH=0.02, BNS=0.94, NSBH=0.03, ProbHasNS=0.0, ProbHasRemnant=0.0, Terrestrial=0.01, internal=False, open_alert=True, skymap_filename='foobar.multiorder.fits,0', skymap_type='foobar', - raven_coinc='RAVEN_ALERT' in labels) - mock_alerts_send.assert_called_once_with(( - superevent_initial_alert_download('foobar.multiorder.fits,0', 'S1234'), - superevent_initial_alert_download('em_bright.json,0', 'S1234'), - superevent_initial_alert_download('p_astro.json,0', 'S1234'), - None, None, None, None), alert['object'], 'initial', - raven_coinc='RAVEN_ALERT' in labels) + raven_coinc='RAVEN_ALERT' in labels, + combined_skymap_filename=(combined_skymap_filename if + combined_skymap_needed else None)) + mock_alerts_send.assert_called_once_with( + (superevent_initial_alert_download('foobar.multiorder.fits,0', + superevent_id), + superevent_initial_alert_download('em_bright.json,0', superevent_id), + superevent_initial_alert_download('p_astro.json,0', superevent_id)) + + ((6 if combined_skymap_needed and superevent_id == 'S1234' else 4) + * (None,)), + alert['object'], 'initial', raven_coinc='RAVEN_ALERT' in labels, + combined_skymap_filename=combined_skymap_filename) mock_gcn_send.assert_called_once_with('contents of S1234-Initial-1.xml') if 'RAVEN_ALERT' in labels: - mock_create_emcoinc_circular.assert_called_once_with('S1234') + mock_create_emcoinc_circular.assert_called_once_with(superevent_id) else: - mock_create_initial_circular.assert_called_once_with('S1234') + mock_create_initial_circular.assert_called_once_with(superevent_id) mock_create_tag.assert_has_calls( - [call('foobar.multiorder.fits,0', 'public', 'S1234'), - call('em_bright.json,0', 'public', 'S1234'), - call('p_astro.json,0', 'public', 'S1234'), - call('S1234-Initial-1.xml', 'public', 'S1234')], + [call('foobar.multiorder.fits,0', 'public', superevent_id), + call('em_bright.json,0', 'public', superevent_id), + call('p_astro.json,0', 'public', superevent_id), + call('S1234-Initial-1.xml', 'public', superevent_id)], any_order=True) - mock_expose.assert_called_once_with('S1234') + mock_expose.assert_called_once_with(superevent_id) def superevent_retraction_alert_download(filename, graceid): -- GitLab