astro-otter 0.6.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
otter/io/host.py ADDED
@@ -0,0 +1,186 @@
1
+ """
2
+ Host object that stores information on the Transient Host and provides utility methods
3
+ for pulling in data corresponding to that host
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ from urllib.request import urlopen
9
+ import json
10
+
11
+ import numpy as np
12
+ from astropy.coordinates import SkyCoord
13
+ from astropy import units as u
14
+
15
+ from .data_finder import DataFinder
16
+ from ..exceptions import OtterLimitationError
17
+
18
+ import logging
19
+
20
+ logger = logging.getLogger(__name__)
21
+
22
+
23
+ class Host(DataFinder):
24
+ def __init__(
25
+ self,
26
+ host_ra: str | float,
27
+ host_dec: str | float,
28
+ host_ra_units: str | u.Unit,
29
+ host_dec_units: str | u.Unit,
30
+ host_name: str = None,
31
+ host_redshift: float = None,
32
+ redshift_type: str = None,
33
+ reference: list[str] = None,
34
+ transient_name: str = None,
35
+ **kwargs,
36
+ ) -> None:
37
+ """
38
+ Object to store host information and query public data sources of host galaxies
39
+
40
+ Subclass of the data scraper class to allow for these queries to happen
41
+
42
+ Args:
43
+ host_ra (str|float) : The RA of the host to be passed to an astropy SkyCoord
44
+ host_dec (str|float) : The declination of the host to be passed to an
45
+ astropy SkyCoord
46
+ host_ra_units (str|astropy.units.Unit) : units of the RA, to be passed to
47
+ the unit keyword of SkyCoord
48
+ host_dec_units (str|astropy.units.Unit) : units of the declination, to be
49
+ passed to the unit keyword of
50
+ SkyCoord
51
+ host_name (str) : The name of the host galaxy
52
+ host_redshift (float) : The redshift of the host galaxy
53
+ redshift_type (str) : Either "phot" or "spec", tells you if the redshift
54
+ is a phot-z or spec-z.
55
+ reference (list[str]) : a list of bibcodes that found this to be the host
56
+ transient_name (str) : the name of the transient associated with this host
57
+ kwargs : Just here so we can pass **Transient['host'] into this constructor
58
+ and any extraneous properties will be ignored.
59
+ """
60
+ self.coord = SkyCoord(host_ra, host_dec, unit=(host_ra_units, host_dec_units))
61
+ self.name = host_name
62
+ self.z = host_redshift
63
+ self.redshift = host_redshift # just here for ease of use
64
+ self.bibcodes = reference
65
+ self.transient_name = transient_name
66
+ self.redshift_type = redshift_type
67
+
68
+ def pcc(self, transient_coord: SkyCoord, mag: float = None):
69
+ """
70
+ Compute the Probability of Chance Coincindence as described in
71
+ Bloom et al. (2002) "Offset Distribution of Gamma-Ray Bursts.
72
+
73
+ This computes the probability that this galaxy is by chance nearby to the
74
+ transient on the sky. Or, in simpler terms this essentially computes the
75
+ probability that we are wrong about this being the transient host. So, a
76
+ smaller probability is better!
77
+
78
+ Note: This probability was initially defined for GRB afterglows, which tend to
79
+ be redder transients (supernova too). So, be cautious when using this algorithm
80
+ for TDEs!
81
+
82
+ Args:
83
+ transient_coord (astropy.coordinates.SkyCoord) : The coordinates of the
84
+ transient object.
85
+ mag (float) : An r-band magnitude to compute from. Default is None which
86
+ will prompt us to check SDSS for one within 10".
87
+ Returns:
88
+ A float probability in the range [0,1]
89
+ """
90
+
91
+ # first get the separation r, in arcseconds
92
+ r = self.coord.separation(transient_coord).arcsec
93
+
94
+ # next get the host r magnitude
95
+ if mag is None:
96
+ res = self.query_vizier(radius=10 * u.arcsec)
97
+
98
+ if len(res) == 0:
99
+ raise OtterLimitationError(
100
+ "No magnitude found in SDSS! Please provide a magnitude via the \
101
+ `mag` keyword to make this calculation!"
102
+ )
103
+
104
+ sdss = [k for k in res.keys() if "sdss" in k]
105
+ use = max(sdss, key=lambda k: int(k.split("sdss")[-1]))
106
+ print(f"Using the r magnitude from the {use} table")
107
+ mag = res[use]["rmag"][0]
108
+
109
+ # then compute the probability
110
+ sigma_prefactor = 1 / (3600**2 * 0.334 * np.log(10))
111
+ sigma_pow = 0.334 * (mag - 22.963) + 4.320
112
+ sigma = sigma_prefactor * 10**sigma_pow
113
+
114
+ eta = np.pi * r**2 * sigma
115
+
116
+ prob = 1 - np.exp(-eta)
117
+
118
+ return prob
119
+
120
+ ###################################################################################
121
+ ######### METHODS FOR FINDING HOSTS ###########################
122
+ ###################################################################################
123
+ @staticmethod
124
+ def query_blast(tns_name: str) -> dict:
125
+ """
126
+ Query the BLAST host galaxy service
127
+
128
+ Args:
129
+ tns_name (str) : The TNS target name to grab the data from BLAST for
130
+
131
+ Returns:
132
+ best match BLAST ID, host ra, host dec, host redshift. Redshift will be
133
+ spectroscopic if available, otherwise photometric.
134
+ """
135
+
136
+ # clean up the input name a little
137
+ if tns_name[:2] == "AT":
138
+ tns_name = tns_name.replace("AT", "")
139
+
140
+ failed_query_res = None
141
+
142
+ # do the query
143
+ blast_base_url = "https://blast.scimma.org"
144
+ blast_query_url = f"{blast_base_url}/api/transient/?name={tns_name}&format=json"
145
+ with urlopen(blast_query_url) as response:
146
+ if response.status != 200:
147
+ logger.warn(f"BLAST query failed with response code {response.status}!")
148
+ return failed_query_res
149
+ else:
150
+ blast_data = json.loads(response.read())
151
+ if len(blast_data) == 0:
152
+ logger.warn("BLAST query returned no results!")
153
+ return failed_query_res
154
+
155
+ blast_data = blast_data[0]
156
+ if "host" not in blast_data:
157
+ logger.warn("BLAST query found the object but it has no host associated!")
158
+ return failed_query_res
159
+
160
+ blast_host = blast_data["host"]
161
+
162
+ if blast_host["redshift"] is not None:
163
+ # prefer spec-z over phot-z
164
+ z = blast_host["redshift"]
165
+ z_type = "spec"
166
+ else:
167
+ # well I guess we need to use phot-z
168
+ z = blast_host["photometric_redshift"]
169
+ z_type = "phot"
170
+
171
+ refs = [
172
+ "2021ApJ...908..170G", # GHOST citation
173
+ "2024arXiv241017322J", # BLAST citation
174
+ ]
175
+
176
+ return Host(
177
+ host_ra=blast_host["ra_deg"],
178
+ host_dec=blast_host["dec_deg"],
179
+ host_ra_units="deg",
180
+ host_dec_units="deg",
181
+ host_name=blast_host["id"],
182
+ host_redshift=z,
183
+ redshift_type=z_type,
184
+ reference=refs,
185
+ transient_name=tns_name,
186
+ )