|
| 1 | +"""This module defines the BSNIP Stahl20 API""" |
| 2 | + |
| 3 | +from typing import List |
| 4 | + |
| 5 | +from astropy import units as u |
| 6 | +from astropy.io import ascii |
| 7 | +from astropy.io.ascii.core import InconsistentTableError |
| 8 | +from astropy.table import Table, vstack |
| 9 | + |
| 10 | +from ..base_classes import SpectroscopicRelease |
| 11 | +from ..utils import unit_conversion, downloads, data_parsing |
| 12 | + |
| 13 | + |
| 14 | +class Stahl20(SpectroscopicRelease): |
| 15 | + """The second data release of the Berkeley Supernova Ia Program |
| 16 | + (BSNIP), including 637 low-redshift optical spectra collected between |
| 17 | + 2009 and 2018. Targets include 626 spectra (of 242 objects) that are |
| 18 | + unambiguously classified as belonging to Type Ia supernovae (SNe Ia). |
| 19 | + Of these, 70 spectra of 30 objects are classified as spectroscopically |
| 20 | + peculiar and 79 SNe Ia (covered by 328 spectra) have complementary |
| 21 | + photometric coverage. The median SN in the data set has one epoch of |
| 22 | + spectroscopy, a redshift of 0.0208 (with a low of 0.0007 and high of |
| 23 | + 0.1921), and is first observed spectroscopically 1.1 days after maximum |
| 24 | + light. (Source: Stahl et al. 2020) |
| 25 | +
|
| 26 | + Deviations from the standard UI: |
| 27 | + - Metadata such as object Ra, DEC, and redshifts are not included |
| 28 | + in the official data release files. |
| 29 | + - Reported error values may or may not be available depending on the |
| 30 | + particular published spectra. |
| 31 | +
|
| 32 | + Cuts on returned data: |
| 33 | + - None |
| 34 | + """ |
| 35 | + |
| 36 | + survey_name = 'Berkeley Supernova Ia Program' |
| 37 | + survey_abbrev = 'BSNIP' |
| 38 | + release = 'Stahl20' |
| 39 | + survey_url = 'http://heracles.astro.berkeley.edu/sndb/' |
| 40 | + publications = ('Stahl et al. 2020',) |
| 41 | + ads_url = 'https://ui.adsabs.harvard.edu/abs/2020MNRAS.492.4325S/abstract' |
| 42 | + |
| 43 | + def __init__(self): |
| 44 | + """Define local and remote paths of data""" |
| 45 | + |
| 46 | + super().__init__() |
| 47 | + self._spectra_dir = self._data_dir / 'spectra' |
| 48 | + self._tables_dir = self._data_dir / 'tables' |
| 49 | + self._meta_data_path = self._data_dir / 'meta_data.yml' |
| 50 | + |
| 51 | + # Define urls / path for remote / local data. |
| 52 | + self._spectra_url = 'http://heracles.astro.berkeley.edu/sndb/static/BSNIPdata2/spectra.tar.gz' |
| 53 | + self._tables_url = 'https://cdsarc.cds.unistra.fr/viz-bin/nph-Cat/tar.gz?J/MNRAS/492/4325' |
| 54 | + self._tables_dir = self._data_dir / 'tables' |
| 55 | + self._meta_table_url = 'http://heracles.astro.berkeley.edu/sndb/static/BSNIPdata2/spectra.csv' |
| 56 | + self._meta_table_path = self._data_dir / 'spectra.csv' |
| 57 | + |
| 58 | + def _get_available_tables(self) -> List[str]: |
| 59 | + """Get Ids for available vizier tables published by this data release""" |
| 60 | + |
| 61 | + tables = ['spectra'] |
| 62 | + for file in self._tables_dir.glob('table*.dat'): |
| 63 | + table_id = file.stem[5:] |
| 64 | + if table_id.isnumeric(): |
| 65 | + table_id = int(table_id) |
| 66 | + |
| 67 | + tables.append(table_id) |
| 68 | + |
| 69 | + return sorted(tables, key=str) |
| 70 | + |
| 71 | + def _load_table(self, table_id: str) -> Table: |
| 72 | + """Return a Vizier table published by this data release |
| 73 | +
|
| 74 | + Args: |
| 75 | + table_id: The published table number or table name |
| 76 | + """ |
| 77 | + |
| 78 | + if table_id == 'spectra': |
| 79 | + return Table.read(self._meta_table_path) |
| 80 | + |
| 81 | + readme_path = self._tables_dir / 'ReadMe' |
| 82 | + table_path = self._tables_dir / f'table{table_id}.dat' |
| 83 | + |
| 84 | + # The CDS readme has an incorrect data type for the second columns in tables a1 adn s1 |
| 85 | + # As a workaround, we parse the file manually |
| 86 | + if table_id == 'a1': |
| 87 | + data = Table.read( |
| 88 | + table_path, |
| 89 | + format='ascii.fixed_width_no_header', |
| 90 | + delimiter=' ', |
| 91 | + col_starts=[0, 24, 35, 44, 53, 60, 62, 68, 76, 79, 85, 91], |
| 92 | + units=[None, '"Y:M:D"', u.deg, u.deg, None, None, u.mag, None, None, u.day, u.day, None], |
| 93 | + names=['Name', 'Discov', 'RAdeg', 'DEdeg', 'z', 'r_z', 'E(B-V)', |
| 94 | + 'Subtype', 'Nsp', 'fepoch', 'lepoch', 'References']) |
| 95 | + |
| 96 | + elif table_id == 's1': |
| 97 | + p1nm = u.CompositeUnit(0.1, [u.nm], [1]) |
| 98 | + data = Table.read( |
| 99 | + table_path, |
| 100 | + format='ascii.fixed_width_no_header', |
| 101 | + delimiter=' ', |
| 102 | + col_starts=[0, 24, 39, 45, 47, 52, 58, 63, 68, 74, 79, 84, 90], |
| 103 | + units=[None, '"Y:M:D"', u.day, None, p1nm, p1nm, p1nm, p1nm, u.deg, None, u.s, None, None], |
| 104 | + names=['Name', 'UTDate', 'tLC', 'Inst', 'lambdamin', 'lambdamax', |
| 105 | + 'Resb', 'Resr', 'PA', 'Airmass', 'ExpTime', 'S/N', 'Ref']) |
| 106 | + |
| 107 | + else: |
| 108 | + data = ascii.read(str(table_path), format='cds', readme=str(readme_path)) |
| 109 | + |
| 110 | + description_dict = data_parsing.parse_vizier_table_descriptions(readme_path) |
| 111 | + data.meta['description'] = description_dict[table_id] |
| 112 | + return data |
| 113 | + |
| 114 | + def _get_available_ids(self) -> List[str]: |
| 115 | + """Return a list of target object IDs for the current survey""" |
| 116 | + |
| 117 | + obj_ids = self.load_table('spectra')['ObjName'] |
| 118 | + return sorted(set(obj_ids)) |
| 119 | + |
| 120 | + def _get_data_for_id(self, obj_id: str, format_table: bool = True) -> Table: |
| 121 | + """Returns data for a given object ID |
| 122 | +
|
| 123 | + Args: |
| 124 | + obj_id: The ID of the desired object |
| 125 | + format_table: Format for use with ``sncosmo`` (Default: True) |
| 126 | +
|
| 127 | + Returns: |
| 128 | + An astropy table of data for the given ID |
| 129 | + """ |
| 130 | + |
| 131 | + data_tables = [] |
| 132 | + all_spectra_inventory = self.load_table('spectra') |
| 133 | + object_spectra_inventory = all_spectra_inventory[all_spectra_inventory['ObjName'] == obj_id] |
| 134 | + for row in object_spectra_inventory: |
| 135 | + path = self._spectra_dir / row['Filename'] |
| 136 | + |
| 137 | + # Tables either have two or three columns |
| 138 | + try: |
| 139 | + table = Table.read( |
| 140 | + str(path), format='ascii', |
| 141 | + names=['wavelength', 'flux', 'fluxerr']) |
| 142 | + |
| 143 | + except InconsistentTableError: |
| 144 | + table = Table.read( |
| 145 | + str(path), format='ascii', |
| 146 | + names=['wavelength', 'flux']) |
| 147 | + |
| 148 | + if format_table: |
| 149 | + table['time'] = unit_conversion.convert_to_jd(row['UT_Date'], format='UT') |
| 150 | + table['instrument'] = row['Instrument'] |
| 151 | + |
| 152 | + data_tables.append(table) |
| 153 | + |
| 154 | + meta_data = self.load_table('a1') |
| 155 | + object_meta_data = meta_data[meta_data['Name'] == obj_id][0] |
| 156 | + |
| 157 | + all_data = vstack(data_tables) |
| 158 | + all_data.sort('wavelength') |
| 159 | + all_data.meta['obj_id'] = obj_id |
| 160 | + all_data.meta['ra'] = object_meta_data['RAdeg'] |
| 161 | + all_data.meta['dec'] = object_meta_data['DEdeg'] |
| 162 | + all_data.meta['z'] = object_meta_data['z'] |
| 163 | + all_data.meta['z_err'] = None |
| 164 | + |
| 165 | + # Return data with columns in a standard order |
| 166 | + return all_data |
| 167 | + |
| 168 | + def _download_module_data(self, force: bool = False, timeout: float = 15): |
| 169 | + """Download data for the current survey / data release |
| 170 | +
|
| 171 | + Args: |
| 172 | + force: Re-Download locally available data |
| 173 | + timeout: Seconds before timeout for individual files/archives |
| 174 | + """ |
| 175 | + |
| 176 | + downloads.download_file( |
| 177 | + url=self._meta_table_url, |
| 178 | + destination=self._meta_table_path, |
| 179 | + force=force, |
| 180 | + timeout=timeout |
| 181 | + ) |
| 182 | + |
| 183 | + downloads.download_tar( |
| 184 | + url=self._tables_url, |
| 185 | + out_dir=self._tables_dir, |
| 186 | + skip_exists=self._tables_dir, |
| 187 | + mode='r:gz', |
| 188 | + force=force, |
| 189 | + timeout=timeout |
| 190 | + ) |
| 191 | + |
| 192 | + downloads.download_tar( |
| 193 | + url=self._spectra_url, |
| 194 | + out_dir=self._data_dir, |
| 195 | + skip_exists=self._spectra_dir, |
| 196 | + mode='r:gz', |
| 197 | + force=force, |
| 198 | + timeout=timeout |
| 199 | + ) |
0 commit comments