Coverage for gwcelery/tasks/em_bright.py: 67%
57 statements
« prev ^ index » next coverage.py v7.4.4, created at 2024-11-14 05:52 +0000
« prev ^ index » next coverage.py v7.4.4, created at 2024-11-14 05:52 +0000
1"""Qualitative source properties for CBC events."""
2import io
3import json
5from celery.utils.log import get_task_logger
6from matplotlib import pyplot as plt
8from .. import app
9from ..util import NamedTemporaryFile, closing_figures
10from . import gracedb, igwn_alert
11from .p_astro import _format_prob
13log = get_task_logger(__name__)
16@igwn_alert.handler('superevent',
17 'mdc_superevent',
18 shared=False)
19def handle(alert):
20 """IGWN alert handler to plot and upload a visualization
21 of every ``em_bright.json``.
22 """
23 filename = 'em_bright.json'
24 graceid = alert['uid']
25 if alert['alert_type'] == 'log' and alert['data']['filename'] == filename:
26 (
27 gracedb.download.si(filename, graceid)
28 |
29 plot.s()
30 |
31 gracedb.upload.s(
32 filename.replace('.json', '.png'),
33 graceid,
34 message=(
35 'Source properties visualization from '
36 '<a href="/api/superevents/{graceid}/files/{filename}">'
37 '{filename}</a>').format(
38 graceid=graceid, filename=filename),
39 tags=['em_follow', 'em_bright', 'public']
40 )
41 ).delay()
44@app.task(shared=False)
45@closing_figures()
46def plot(contents):
47 """Make a visualization of the source properties.
49 Examples
50 --------
51 .. plot::
52 :include-source:
54 >>> from gwcelery.tasks import em_bright
55 >>> contents = '{"HasNS": 0.9137, "HasRemnant": 0.0, "HasMassGap": 0.0}' # noqa E501
56 >>> em_bright.plot(contents)
57 """
58 # Explicitly use a non-interactive Matplotlib backend.
59 plt.switch_backend('agg')
61 properties = json.loads(contents)
62 outfile = io.BytesIO()
64 properties = dict(sorted(properties.items(), reverse=True))
65 probs, names = list(properties.values()), list(properties.keys())
67 with plt.style.context('seaborn-v0_8-white'):
68 fig, ax = plt.subplots(figsize=(3, 1))
69 ax.barh(names, probs)
70 ax.barh(names, [1.0 - p for p in probs],
71 color='lightgray', left=probs)
72 for i, prob in enumerate(probs):
73 ax.annotate(_format_prob(prob), (0, i), (4, 0),
74 textcoords='offset points', ha='left', va='center')
75 ax.set_xlim(0, 1)
76 ax.set_xticks([])
77 ax.tick_params(left=False)
78 for side in ['top', 'bottom', 'right']:
79 ax.spines[side].set_visible(False)
80 fig.tight_layout()
81 fig.savefig(outfile, format='png')
82 return outfile.getvalue()
85@app.task(shared=False, queue='em-bright')
86def em_bright_posterior_samples(posterior_file_content):
87 """Returns the probability of having a NS component and remnant
88 using Bilby posterior samples.
90 Parameters
91 ----------
92 posterior_file_content : hdf5 posterior file content
94 Returns
95 -------
96 str
97 JSON formatted string storing ``HasNS``, ``HasRemnant``,
98 and ``HasMassGap`` probabilities
100 Examples
101 --------
102 >>> em_bright_posterior_samples(GraceDb().files('S190930s',
103 ... 'Bilby.posterior_samples.hdf5').read())
104 {"HasNS": 0.014904901243599122, "HasRemnant": 0.0, "HasMassGap": 0.0}
106 """
107 from ligo.em_bright import em_bright
108 with NamedTemporaryFile(content=posterior_file_content) as samplefile:
109 filename = samplefile.name
110 r = em_bright.source_classification_pe(
111 filename, num_eos_draws=10000, eos_seed=0
112 )
113 has_ssm, has_ns, has_remnant, has_massgap = r
114 data = json.dumps({
115 'HasNS': has_ns,
116 'HasRemnant': has_remnant,
117 'HasMassGap': has_massgap,
118 'HasSSM': has_ssm
119 })
120 return data
123@app.task(shared=False, queue='em-bright')
124def source_properties(mass1, mass2, spin1z, spin2z, snr,
125 pipeline='gstlal', search='allsky'):
126 """Returns the probability of having a NS component, the probability of
127 having non-zero disk mass, and the probability of any component being the
128 lower mass gap for the detected event.
130 Parameters
131 ----------
132 mass1 : float
133 Primary mass in solar masses
134 mass2 : float
135 Secondary mass in solar masses
136 spin1z : float
137 Dimensionless primary aligned spin component
138 spin2z : float
139 Dimensionless secondary aligned spin component
140 snr : float
141 Signal to noise ratio
142 pipeline_search : tuple
143 The pipeline and the search as a tuple. This is used
144 to select the appropriate classifiers in ``ligo.em-bright``
145 for ``SSM`` search only. This is unused for ``AllSky``
146 searches.
148 Returns
149 -------
150 str
151 JSON formatted string storing ``HasNS``, ``HasRemnant``,
152 ``HasMassGap`` probabilities for ``AllSky`` searches, and
153 ``HasSSM``, ``HasNS``, ``HasMassGap`` probabilities for
154 ``SSM`` searches.
156 Examples
157 --------
158 >>> em_bright.source_properties(2.0, 1.0, 0.0, 0.0, 10.)
159 '{"HasNS": 1.0, "HasRemnant": 1.0, "HasMassGap": 0.0}'
160 >>> em_bright.source_properties(2.0, 1.0, 0.0, 0.0, 10.,
161 ... pipeline='gstlal', search='ssm')
162 '{"HasSSM": 0.52, "HasNS": 0.9199999999999999, "HasMassGap": 0.08}'
163 """
164 from ligo.em_bright import em_bright
165 if search == 'ssm':
166 chirp_mass = (mass1 * mass2) ** (3. / 5.)
167 chirp_mass /= (mass1 + mass2) ** (1. / 5.)
168 p_ssm, p_ns, p_mg = em_bright.source_classification_ssm(
169 mass1, mass2, spin1z, spin2z, chirp_mass,
170 snr, pipeline
171 )
172 data = json.dumps({
173 'HasSSM': p_ssm,
174 'HasNS': p_ns,
175 'HasMassGap': p_mg,
176 })
177 else:
178 p_ns, p_em, p_mg = em_bright.source_classification(
179 mass1, mass2, spin1z, spin2z, snr
180 )
181 data = json.dumps({
182 'HasNS': p_ns,
183 'HasRemnant': p_em,
184 'HasMassGap': p_mg,
185 })
186 return data